• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MikkelSchubert / adapterremoval / #36

22 Jul 2024 09:33AM UTC coverage: 87.26% (-12.7%) from 100.0%
#36

push

travis-ci

MikkelSchubert
remove duplicate tests

2185 of 2504 relevant lines covered (87.26%)

16293.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.67
/src/logging.cpp
1
/*************************************************************************\
2
 * AdapterRemoval - cleaning next-generation sequencing reads            *
3
 *                                                                       *
4
 * Copyright (C) 2022 by Mikkel Schubert - mikkelsch@gmail.com           *
5
 *                                                                       *
6
 * This program is free software: you can redistribute it and/or modify  *
7
 * it under the terms of the GNU General Public License as published by  *
8
 * the Free Software Foundation, either version 3 of the License, or     *
9
 * (at your option) any later version.                                   *
10
 *                                                                       *
11
 * This program is distributed in the hope that it will be useful,       *
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 * GNU General Public License for more details.                          *
15
 *                                                                       *
16
 * You should have received a copy of the GNU General Public License     *
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. *
18
\*************************************************************************/
19
#include "logging.hpp"
20
#include "debug.hpp"    // for AR_REQUIRE, AR_FAIL
21
#include "main.hpp"     // for NAME, VERSION
22
#include "strutils.hpp" // for split_lines, cli_formatter, string_vec
23
#include <algorithm>    // for max, min
24
#include <iomanip>      // for operator<<, put_time
25
#include <iostream>     // for cerr
26
#include <limits>       // for numeric_limits
27
#include <mutex>        // for mutex, unique_lock
28
#include <sys/ioctl.h>  // for ioctl, winsize, TIOCGWINSZ
29
#include <unistd.h>     // for size_t, STDERR_FILENO
30
#include <vector>       // for vector
31

32
namespace adapterremoval {
33

34
namespace log {
35

36
namespace {
37

38
//! Shared mutex for STDOUT / STDERR
39
std::recursive_mutex g_log_mutex;
40
//! Pointer to logging stream; normally this will be std::cerr;
41
std::ostream* g_log_out = &std::cerr;
42

43
//! Minimum log-level to print
44
level g_log_level = level::debug;
45
//! Include timestamps when writing log lines
46
bool g_log_timestamps = true;
47
//! Use ANSI colors when writing log lines
48
bool g_log_colors = false;
49
//! Indicates if the last message was transient
50
bool g_log_transient = false;
51
//! Indicates that the preamble has been logged
52
bool g_preamble_logged = false;
53

54
//! ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
55
enum class color
56
{
57
  black = 30,
58
  red,
59
  green,
60
  yellow,
61
  blue,
62
  magenta,
63
  cyan,
64
  white,
65
  reset
66
};
67

68
/** Returns the color associated with a given log level */
69
color
70
level_color(level l)
1✔
71
{
72
  switch (l) {
1✔
73
    case level::debug:
74
      return color::cyan;
75
    case level::info:
76
      return color::green;
77
    case level::warning:
78
      return color::yellow;
79
    case level::error:
80
      return color::red;
81
    default:
×
82
      AR_FAIL("invalid logging level");
×
83
  }
84
}
85

86
/** Writes the name of a given log level */
87
std::ostream&
88
operator<<(std::ostream& out, level l)
36✔
89
{
90
  switch (l) {
36✔
91
    case level::debug:
2✔
92
      return out << "DEBUG";
2✔
93
    case level::info:
12✔
94
      return out << "INFO";
12✔
95
    case level::warning:
8✔
96
      return out << "WARNING";
8✔
97
    case level::error:
14✔
98
      return out << "ERROR";
14✔
99
    default:
×
100
      AR_FAIL("invalid logging level");
×
101
  }
102
}
103

104
/** Writes a color control code */
105
std::ostream&
106
operator<<(std::ostream& out, color c)
2✔
107
{
108
  if (c == color::reset) {
1✔
109
    out << "\033[0m";
1✔
110
  } else {
111
    out << "\033[0;" << static_cast<int>(c) << "m";
1✔
112
  }
113

114
  return out;
2✔
115
}
116

117
/** Create timestamp+level prefix for logs */
118
std::string
119
log_header(level l, bool colors = false)
36✔
120
{
121
  std::ostringstream header;
36✔
122
  if (g_log_timestamps) {
36✔
123
    header << timestamp("%Y-%m-%d %X", true) << " ";
9✔
124
  }
125

126
  if (colors) {
36✔
127
    header << "[" << level_color(l) << l << color::reset << "] ";
2✔
128
  } else {
129
    header << "[" << l << "] ";
35✔
130
  }
131

132
  return header.str();
72✔
133
}
36✔
134

135
size_t
136
log_linewidth(const std::ostream& out)
36✔
137
{
138
  // Piped logs are not pretty-printed, to make analyses easier
139
  if (&out == &std::cerr) {
36✔
140
    return get_terminal_width();
×
141
  }
142

143
  return std::numeric_limits<size_t>::max();
144
}
145

146
void
147
log_print_lines(std::ostream& out,
36✔
148
                const std::string& head,
149
                const std::string& msg)
150
{
151
  // Account for unprinted color codes:
152
  // Size of color ("\033[0;XXm" = 7) + reset ("\033[0m" = 4)
153
  const int color_width = g_log_colors ? 11 : 0;
36✔
154
  const int head_width = head.size() - color_width;
36✔
155
  const int line_width = log_linewidth(out);
36✔
156

157
  for (const auto& line : split_lines(msg)) {
288✔
158
    const auto indent = line.find_first_not_of(' ');
36✔
159
    if (indent == std::string::npos) {
36✔
160
      out << head << "\n";
8✔
161
      continue;
4✔
162
    }
163

164
    cli_formatter fmt;
32✔
165
    fmt.set_ljust(2);
32✔
166
    fmt.set_indent(indent);
32✔
167
    fmt.set_column_width(line_width - indent - head_width);
32✔
168

169
    bool first_line = true;
32✔
170
    for (const auto& fragment : split_lines(fmt.format(line))) {
288✔
171
      if (first_line) {
32✔
172
        out << head + fragment << "\n";
64✔
173
        first_line = false;
32✔
174
      } else {
175
        out << std::string(head_width, ' ') << fragment << "\n";
×
176
      }
177
    }
32✔
178
  }
36✔
179
}
36✔
180

181
} // namespace
182

183
////////////////////////////////////////////////////////////////////////////////
184

185
void
186
log_preamble()
71✔
187
{
188
  std::unique_lock<std::recursive_mutex> lock(g_log_mutex);
71✔
189
  if (!g_preamble_logged) {
71✔
190
    g_preamble_logged = true;
1✔
191
    log::info() << NAME << " " << VERSION;
5✔
192
  }
193
}
142✔
194

195
void
196
set_level(level l)
4✔
197
{
198
  std::unique_lock<std::recursive_mutex> lock(g_log_mutex);
4✔
199

200
  g_log_level = l;
4✔
201
}
8✔
202

203
void
204
set_colors(bool enabled)
3✔
205
{
206
  std::unique_lock<std::recursive_mutex> lock(g_log_mutex);
3✔
207

208
  g_log_colors = enabled;
3✔
209
}
6✔
210

211
void
212
set_timestamps(bool enabled)
4✔
213
{
214
  std::unique_lock<std::recursive_mutex> lock(g_log_mutex);
4✔
215

216
  g_log_timestamps = enabled;
4✔
217
}
8✔
218

219
size_t
220
get_terminal_width()
×
221
{
222
  struct winsize params = {};
×
223
  // Attempt to retrieve the number of columns in the terminal
224
  if (ioctl(STDERR_FILENO, TIOCGWINSZ, &params) == 0) {
×
225
    return std::min<size_t>(120, std::max<size_t>(60, params.ws_col));
×
226
  }
227

228
  return std::numeric_limits<size_t>::max();
229
}
230

231
////////////////////////////////////////////////////////////////////////////////
232

233
log_stream::log_stream(level lvl)
71✔
234
  : m_level(lvl)
71✔
235
{
236
  AR_REQUIRE(lvl < level::none);
71✔
237
}
70✔
238

239
log_stream::~log_stream()
71✔
240
{
241
  std::unique_lock<std::recursive_mutex> lock(g_log_mutex);
71✔
242
  log_preamble();
71✔
243

244
  if (g_log_transient) {
71✔
245
    *g_log_out << "\r\033[K";
3✔
246
    g_log_transient = false;
3✔
247
  }
248

249
  if (m_level == level::cerr) {
71✔
250
    *g_log_out << m_stream.str();
132✔
251
    g_log_out->flush();
33✔
252
    g_log_transient = m_transient;
33✔
253
  } else if (m_level >= g_log_level) {
38✔
254
    const auto head = log_header(m_level, g_log_colors);
36✔
255

256
    // Trailing newlines are permitted for ease of use, e.g. when writing
257
    // complex, multi-line log messages
258
    auto msg = m_stream.str();
36✔
259
    while (!msg.empty() && msg.back() == '\n') {
68✔
260
      msg.pop_back();
36✔
261
    }
262

263
    log_print_lines(*g_log_out, head, msg);
36✔
264

265
    g_log_out->flush();
36✔
266
  }
72✔
267
}
142✔
268

269
log_stream&
270
log_stream::transient()
7✔
271
{
272
  AR_REQUIRE(m_level == level::cerr);
23✔
273
  m_transient = true;
3✔
274

275
  return *this;
3✔
276
}
277

278
////////////////////////////////////////////////////////////////////////////////
279

280
log_capture::log_capture()
44✔
281
{
282
  std::unique_lock<std::recursive_mutex> lock(g_log_mutex);
44✔
283

284
  AR_REQUIRE(g_log_out == &std::cerr);
44✔
285
  g_log_out = &m_stream;
44✔
286

287
  m_level = g_log_level;
44✔
288
  m_timestamps = g_log_timestamps;
44✔
289
  m_colors = g_log_colors;
44✔
290
}
88✔
291

292
log_capture::~log_capture()
44✔
293
{
294
  std::unique_lock<std::recursive_mutex> lock(g_log_mutex);
44✔
295

296
  AR_REQUIRE(g_log_out == &m_stream);
44✔
297
  g_log_out = &std::cerr;
44✔
298
  g_log_level = m_level;
44✔
299
  g_log_timestamps = m_colors;
44✔
300
  g_log_colors = m_colors;
44✔
301
}
88✔
302

303
} // namespace log
304

305
} // namespace adapterremoval
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc