Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ocudu/lib/ocudulog/ocudulog.cpp
Line
Count
Source
1
// SPDX-FileCopyrightText: Copyright (C) 2021-2026 Software Radio Systems Limited
2
// SPDX-License-Identifier: BSD-3-Clause-Open-MPI
3
4
#include "ocudu/ocudulog/ocudulog.h"
5
#include "formatters/json_formatter.h"
6
#include "ocudulog_instance.h"
7
#include "sinks/file_sink.h"
8
#include "sinks/syslog_sink.h"
9
#include "sinks/udp_sink.h"
10
11
using namespace ocudulog;
12
13
/// Returns a copy of the input string with any occurrences of the '#' character
14
/// removed.
15
static std::string remove_sharp_chars(const std::string& s)
16
0
{
17
0
  std::string result(s);
18
0
  result.erase(std::remove(result.begin(), result.end(), '#'), result.end());
19
0
  return result;
20
0
}
21
22
/// Generic argument function that fetches a log channel from the repository.
23
template <typename... Args>
24
static log_channel& fetch_log_channel_helper(const std::string& id, Args&&... args)
25
128
{
26
128
  return ocudulog_instance::get().get_channel_repo().emplace(
27
128
      std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(id, std::forward<Args>(args)...));
28
128
}
Unexecuted instantiation: ocudulog.cpp:ocudulog::log_channel& fetch_log_channel_helper<ocudulog::sink&, ocudulog::detail::log_backend&>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, ocudulog::sink&, ocudulog::detail::log_backend&)
ocudulog.cpp:ocudulog::log_channel& fetch_log_channel_helper<ocudulog::sink&, ocudulog::detail::log_backend&, ocudulog::log_channel_config>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, ocudulog::sink&, ocudulog::detail::log_backend&, ocudulog::log_channel_config&&)
Line
Count
Source
25
128
{
26
128
  return ocudulog_instance::get().get_channel_repo().emplace(
27
128
      std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(id, std::forward<Args>(args)...));
28
128
}
29
30
///
31
/// Log channel management function implementations.
32
///
33
34
log_channel* ocudulog::find_log_channel(const std::string& id)
35
0
{
36
0
  return ocudulog_instance::get().get_channel_repo().find(id);
37
0
}
38
39
log_channel& ocudulog::fetch_log_channel(const std::string& id)
40
0
{
41
0
  assert(!id.empty() && "Empty id string");
42
43
0
  std::string clean_id = remove_sharp_chars(id);
44
45
0
  if (auto* c = find_log_channel(clean_id)) {
46
0
    return *c;
47
0
  }
48
49
0
  ocudulog_instance& instance = ocudulog_instance::get();
50
0
  return fetch_log_channel_helper(clean_id, instance.get_default_sink(), instance.get_backend());
51
0
}
52
53
log_channel& ocudulog::fetch_log_channel(const std::string& id, log_channel_config config)
54
0
{
55
0
  assert(!id.empty() && "Empty id string");
56
57
0
  std::string clean_id = remove_sharp_chars(id);
58
59
0
  if (auto* c = find_log_channel(clean_id)) {
60
0
    return *c;
61
0
  }
62
63
0
  ocudulog_instance& instance = ocudulog_instance::get();
64
0
  return fetch_log_channel_helper(clean_id, instance.get_default_sink(), instance.get_backend(), std::move(config));
65
0
}
66
67
log_channel& ocudulog::fetch_log_channel(const std::string& id, sink& s, log_channel_config config)
68
0
{
69
0
  assert(!id.empty() && "Empty id string");
70
71
0
  std::string clean_id = remove_sharp_chars(id);
72
73
0
  if (auto* c = find_log_channel(clean_id)) {
74
0
    return *c;
75
0
  }
76
77
0
  ocudulog_instance& instance = ocudulog_instance::get();
78
0
  return fetch_log_channel_helper(clean_id, s, instance.get_backend(), std::move(config));
79
0
}
80
81
///
82
/// Formatter management functions.
83
///
84
85
void ocudulog::set_default_log_formatter(std::unique_ptr<log_formatter> f)
86
0
{
87
0
  ocudulog_instance::get().set_default_formatter(std::move(f));
88
0
}
89
90
std::unique_ptr<log_formatter> ocudulog::get_default_log_formatter()
91
0
{
92
0
  return ocudulog_instance::get().get_default_formatter();
93
0
}
94
95
std::unique_ptr<log_formatter> ocudulog::create_text_formatter()
96
0
{
97
0
  return std::unique_ptr<log_formatter>(new text_formatter);
98
0
}
99
100
std::unique_ptr<log_formatter> ocudulog::create_contextual_text_formatter()
101
0
{
102
0
  return std::unique_ptr<log_formatter>(new contextual_text_formatter);
103
0
}
104
105
std::unique_ptr<log_formatter> ocudulog::create_json_formatter()
106
0
{
107
0
  return std::unique_ptr<log_formatter>(new json_formatter);
108
0
}
109
110
///
111
/// Sink management function implementations.
112
///
113
114
void ocudulog::set_default_sink(sink& s)
115
0
{
116
0
  ocudulog_instance::get().set_default_sink(s);
117
0
}
118
119
sink& ocudulog::get_default_sink()
120
0
{
121
0
  return ocudulog_instance::get().get_default_sink();
122
0
}
123
124
sink* ocudulog::find_sink(const std::string& id)
125
0
{
126
0
  auto ptr = ocudulog_instance::get().get_sink_repo().find(id);
127
0
  return (ptr) ? ptr->get() : nullptr;
128
0
}
129
130
sink& ocudulog::fetch_stdout_sink(const std::string& id, std::unique_ptr<log_formatter> f)
131
0
{
132
0
  assert(!id.empty() && "Empty id string");
133
134
0
  if (auto* s = find_sink(id)) {
135
0
    return *s;
136
0
  }
137
138
0
  auto& s = ocudulog_instance::get().get_sink_repo().emplace(
139
0
      std::piecewise_construct,
140
0
      std::forward_as_tuple(id),
141
0
      std::forward_as_tuple(new stream_sink(sink_stream_type::stdout, std::move(f))));
142
143
0
  return *s;
144
0
}
145
146
sink& ocudulog::fetch_stderr_sink(const std::string& id, std::unique_ptr<log_formatter> f)
147
0
{
148
0
  assert(!id.empty() && "Empty id string");
149
150
0
  if (auto* s = find_sink(id)) {
151
0
    return *s;
152
0
  }
153
154
0
  auto& s = ocudulog_instance::get().get_sink_repo().emplace(
155
0
      std::piecewise_construct,
156
0
      std::forward_as_tuple(id),
157
0
      std::forward_as_tuple(new stream_sink(sink_stream_type::stderr, std::move(f))));
158
159
0
  return *s;
160
0
}
161
162
sink& ocudulog::fetch_file_sink(const std::string&             path,
163
                                size_t                         max_size,
164
                                bool                           mark_eof,
165
                                bool                           force_flush,
166
                                std::unique_ptr<log_formatter> f)
167
0
{
168
0
  assert(!path.empty() && "Empty path string");
169
170
0
  if (auto* s = find_sink(path)) {
171
0
    return *s;
172
0
  }
173
174
  //: TODO: GCC5 or lower versions emits an error if we use the new() expression
175
  // directly, use redundant piecewise_construct instead.
176
0
  auto& s = ocudulog_instance::get().get_sink_repo().emplace(
177
0
      std::piecewise_construct,
178
0
      std::forward_as_tuple(path),
179
0
      std::forward_as_tuple(new file_sink(path, max_size, mark_eof, force_flush, std::move(f))));
180
181
0
  return *s;
182
0
}
183
184
sink& ocudulog::fetch_syslog_sink(const std::string&             preamble_,
185
                                  syslog_local_type              log_local_,
186
                                  std::unique_ptr<log_formatter> f)
187
0
{
188
0
  std::string sink_id = preamble_ + std::to_string(static_cast<int>(log_local_));
189
0
  if (auto* s = find_sink(sink_id)) {
190
0
    return *s;
191
0
  }
192
193
  //: TODO: GCC5 or lower versions emits an error if we use the new() expression
194
  // directly, use redundant piecewise_construct instead.
195
0
  auto& s = ocudulog_instance::get().get_sink_repo().emplace(
196
0
      std::piecewise_construct,
197
0
      std::forward_as_tuple(sink_id),
198
0
      std::forward_as_tuple(new syslog_sink(std::move(f), preamble_, log_local_)));
199
200
0
  return *s;
201
0
}
202
203
sink& ocudulog::fetch_udp_sink(const std::string& remote_ip, unsigned port, std::unique_ptr<log_formatter> f)
204
0
{
205
0
  std::string id = remote_ip + ':' + std::to_string(port);
206
0
  if (auto* s = find_sink(id)) {
207
0
    return *s;
208
0
  }
209
210
0
  auto& s = ocudulog_instance::get().get_sink_repo().emplace(
211
0
      std::piecewise_construct,
212
0
      std::forward_as_tuple(id),
213
0
      std::forward_as_tuple(new udp_sink(remote_ip, port, std::move(f))));
214
215
0
  return *s;
216
0
}
217
218
bool ocudulog::install_custom_sink(const std::string& id, std::unique_ptr<sink> s)
219
0
{
220
0
  assert(!id.empty() && "Empty path string");
221
222
0
  sink* input_sink    = s.get();
223
0
  sink* returned_sink = ocudulog_instance::get().get_sink_repo().emplace(id, std::move(s)).get();
224
225
  // Successful insertion occurs when the returned object is the same one as the
226
  // input object.
227
0
  return input_sink == returned_sink;
228
0
}
229
230
///
231
/// Framework configuration and control function implementations.
232
///
233
234
void ocudulog::init(backend_priority priority)
235
0
{
236
0
  ocudulog_instance::get().get_backend().start(priority);
237
0
}
238
239
void ocudulog::flush()
240
0
{
241
0
  ocudulog_instance& instance = ocudulog_instance::get();
242
243
  // Nothing to do when the backend is not running yet.
244
0
  if (!instance.get_backend().is_running()) {
245
0
    return;
246
0
  }
247
248
  // The backend will set this shared variable when done.
249
0
  detail::shared_variable<bool> completion_flag(false);
250
251
0
  auto               sink_ptrs = instance.get_sink_repo().contents();
252
0
  std::vector<sink*> sinks;
253
0
  sinks.reserve(sink_ptrs.size());
254
0
  for (const auto& s : sink_ptrs) {
255
0
    sinks.push_back(s->get());
256
0
  }
257
258
  // Make sure the flush command gets into the backend, otherwise we will be stuck waiting forever for the command to
259
  // succeed.
260
0
  for (;;) {
261
0
    detail::log_entry cmd;
262
0
    cmd.metadata.store = nullptr;
263
0
    cmd.flush_cmd      = std::make_unique<detail::flush_backend_cmd>(detail::flush_backend_cmd{completion_flag, sinks});
264
0
    if (instance.get_backend().push(std::move(cmd))) {
265
0
      break;
266
0
    }
267
0
  }
268
269
  // Block the caller thread until we are signaled that the flush is completed.
270
0
  while (!completion_flag) {
271
0
    std::this_thread::sleep_for(std::chrono::microseconds(100));
272
0
  }
273
0
}
274
275
void ocudulog::set_error_handler(error_handler handler)
276
0
{
277
0
  ocudulog_instance::get().set_error_handler(std::move(handler));
278
0
}
279
280
///
281
/// Logger management function implementations.
282
///
283
284
detail::any* ocudulog::detail::find_logger(const std::string& id)
285
429k
{
286
429k
  return ocudulog_instance::get().get_logger_repo().find(id);
287
429k
}
288
289
detail::any* ocudulog::detail::fetch_logger(const std::string& id, detail::any&& logger)
290
32
{
291
32
  assert(!id.empty() && "Empty id string");
292
32
  return &ocudulog_instance::get().get_logger_repo().emplace(id, std::move(logger));
293
32
}
294
295
/// Builds a logger name out of the id and tag.
296
static std::string build_logger_name(const std::string& id, char tag)
297
128
{
298
128
  return fmt::format("{}#{}", id, tag);
299
128
}
300
301
/// Fetches a logger with all its log channels.
302
static basic_logger& fetch_basic_logger_helper(const std::string& id, sink& s, bool should_print_context)
303
32
{
304
32
  static constexpr char basic_logger_chan_tags[] = {'E', 'W', 'I', 'D'};
305
306
32
  ocudulog_instance& instance = ocudulog_instance::get();
307
308
  // User created log channels cannot have ids with a # character, encode the
309
  // ids here with a # to ensure all channels are unique.
310
311
32
  log_channel& error =
312
32
      fetch_log_channel_helper(build_logger_name(id, basic_logger_chan_tags[0]),
313
32
                               s,
314
32
                               instance.get_backend(),
315
32
                               log_channel_config{id, basic_logger_chan_tags[0], should_print_context});
316
32
  log_channel& warning =
317
32
      fetch_log_channel_helper(build_logger_name(id, basic_logger_chan_tags[1]),
318
32
                               s,
319
32
                               instance.get_backend(),
320
32
                               log_channel_config{id, basic_logger_chan_tags[1], should_print_context});
321
32
  log_channel& info = fetch_log_channel_helper(build_logger_name(id, basic_logger_chan_tags[2]),
322
32
                                               s,
323
32
                                               instance.get_backend(),
324
32
                                               log_channel_config{id, basic_logger_chan_tags[2], should_print_context});
325
32
  log_channel& debug =
326
32
      fetch_log_channel_helper(build_logger_name(id, basic_logger_chan_tags[3]),
327
32
                               s,
328
32
                               instance.get_backend(),
329
32
                               log_channel_config{id, basic_logger_chan_tags[3], should_print_context});
330
331
32
  return fetch_logger<basic_logger>(id, error, warning, info, debug);
332
32
}
333
334
basic_logger& ocudulog::fetch_basic_logger(const std::string& id, bool should_print_context)
335
429k
{
336
429k
  assert(!id.empty() && "Empty id string");
337
338
429k
  if (auto* logger = find_logger<basic_logger>(id)) {
339
429k
    return *logger;
340
429k
  }
341
342
32
  return fetch_basic_logger_helper(id, ocudulog_instance::get().get_default_sink(), should_print_context);
343
429k
}
344
345
basic_logger& ocudulog::fetch_basic_logger(const std::string& id, sink& s, bool should_print_context)
346
0
{
347
0
  assert(!id.empty() && "Empty id string");
348
349
0
  if (auto* logger = find_logger<basic_logger>(id)) {
350
0
    return *logger;
351
0
  }
352
353
0
  return fetch_basic_logger_helper(id, s, should_print_context);
354
0
}
355
356
///
357
/// Deprecated functions to be removed.
358
///
359
360
/// Creates and registers a log channel. Returns a pointer to the newly created
361
/// channel on success, otherwise nullptr.
362
static log_channel* create_and_register_log_channel(const std::string& id, sink& s)
363
0
{
364
0
  assert(!id.empty() && "Empty id string");
365
366
0
  ocudulog_instance& instance = ocudulog_instance::get();
367
368
0
  auto& p = instance.get_channel_repo().emplace(
369
0
      std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(id, s, instance.get_backend()));
370
371
0
  return &p;
372
0
}
373
374
static log_channel* create_and_register_log_channel(const std::string& id, log_channel_config config, sink& s)
375
0
{
376
0
  assert(!id.empty() && "Empty id string");
377
378
0
  ocudulog_instance& instance = ocudulog_instance::get();
379
380
0
  auto& p =
381
0
      instance.get_channel_repo().emplace(std::piecewise_construct,
382
0
                                          std::forward_as_tuple(id),
383
0
                                          std::forward_as_tuple(id, s, instance.get_backend(), std::move(config)));
384
385
0
  return &p;
386
0
}
387
388
/// Returns true if the input string contains a sharp character, otherwise
389
/// returns false.
390
static bool contains_sharp_char(const std::string& s)
391
0
{
392
0
  return s.find('#') != std::string::npos;
393
0
}
394
395
log_channel* ocudulog::create_log_channel(const std::string& id, sink& s)
396
0
{
397
0
  if (contains_sharp_char(id)) {
398
0
    return nullptr;
399
0
  }
400
401
0
  return create_and_register_log_channel(id, s);
402
0
}
403
404
sink* ocudulog::create_stdout_sink(const std::string& name)
405
0
{
406
0
  return ocudulog_instance::get().get_sink_repo().find("stdout")->get();
407
0
}
408
409
sink* ocudulog::create_stderr_sink(const std::string& name)
410
0
{
411
0
  return ocudulog_instance::get().get_sink_repo().find("stderr")->get();
412
0
}
413
414
sink* ocudulog::create_file_sink(const std::string& path, size_t max_size, bool mark_eof)
415
0
{
416
  //: TODO: GCC5 or lower versions emits an error if we use the new() expression
417
  // directly, use redundant piecewise_construct instead.
418
0
  return ocudulog_instance::get()
419
0
      .get_sink_repo()
420
0
      .emplace(std::piecewise_construct,
421
0
               std::forward_as_tuple(path),
422
0
               std::forward_as_tuple(new file_sink(
423
0
                   path, max_size, mark_eof, false, std::unique_ptr<log_formatter>(new contextual_text_formatter))))
424
0
      .get();
425
0
}
426
427
basic_logger* ocudulog::create_basic_logger(const std::string& id, sink& s, bool should_print_context)
428
0
{
429
0
  assert(!id.empty() && "Empty id string");
430
431
0
  static constexpr char basic_logger_chan_tags[] = {'E', 'W', 'I', 'D'};
432
433
0
  auto& logger_repo = ocudulog_instance::get().get_logger_repo();
434
435
  // Nothing to do when the logger already exists.
436
0
  if (logger_repo.find(id)) {
437
0
    return nullptr;
438
0
  }
439
440
  // User created log channels cannot have ids with a # character, encode the
441
  // ids here with a # to ensure all channel creations will be successful
442
  // without any id clashes.
443
444
0
  log_channel* error = create_and_register_log_channel(
445
0
      build_logger_name(id, basic_logger_chan_tags[0]), {id, basic_logger_chan_tags[0], should_print_context}, s);
446
0
  assert(error && "Could not create channel");
447
0
  log_channel* warning = create_and_register_log_channel(
448
0
      build_logger_name(id, basic_logger_chan_tags[1]), {id, basic_logger_chan_tags[1], should_print_context}, s);
449
0
  assert(warning && "Could not create channel");
450
0
  log_channel* info = create_and_register_log_channel(
451
0
      build_logger_name(id, basic_logger_chan_tags[2]), {id, basic_logger_chan_tags[2], should_print_context}, s);
452
0
  assert(info && "Could not create channel");
453
0
  log_channel* debug = create_and_register_log_channel(
454
0
      build_logger_name(id, basic_logger_chan_tags[3]), {id, basic_logger_chan_tags[3], should_print_context}, s);
455
0
  assert(debug && "Could not create channel");
456
457
0
  return create_logger<basic_logger>(id, *error, *warning, *info, *debug);
458
0
}