libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
logger.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_CORE
13#include <variant>
14#include <functional>
15#include <promeki/basicthread.h>
16#include <promeki/function.h>
17#include <promeki/namespace.h>
18#include <promeki/string.h>
19#include <promeki/stringlist.h>
20#include <promeki/queue.h>
21#include <promeki/deque.h>
22#include <promeki/list.h>
23#include <promeki/map.h>
24#include <promeki/mutex.h>
25#include <promeki/atomic.h>
26#include <promeki/promise.h>
27#include <promeki/datetime.h>
28#include <promeki/timestamp.h>
29
30PROMEKI_NAMESPACE_BEGIN
31
33static consteval const char *sourceFileName(const char *path) {
34 const char *ret = path;
35 for (const char *p = path; *p; ++p) {
36 if (*p == '/' || *p == '\\') ret = p + 1;
37 }
38 return ret;
39}
40
41#ifdef __FILE_NAME__
42#define PROMEKI_SOURCE_FILE __FILE_NAME__
43#else
44#define PROMEKI_SOURCE_FILE promeki::sourceFileName(__FILE__)
45#endif
46
47// Variadic macros for logging at various levels
48
49#define PROMEKI_DEBUG(name) \
50 namespace { \
51 [[maybe_unused]] static const char *_promeki_debug_name = PROMEKI_STRINGIFY(name); \
52 [[maybe_unused]] static bool _promeki_debug_enabled = promekiRegisterDebug( \
53 &_promeki_debug_enabled, PROMEKI_STRINGIFY(name), PROMEKI_SOURCE_FILE, __LINE__); \
54 }
55
56#define promekiLogImpl(_plevel, format, ...) \
57 do { \
58 if (!(_plevel) || (_plevel) >= Logger::defaultLogger().level()) \
59 Logger::defaultLogger().log(_plevel, PROMEKI_SOURCE_FILE, __LINE__, \
60 String::sprintf(format, ##__VA_ARGS__)); \
61 } while (0)
62#define promekiLog(level, format, ...) promekiLogImpl(level, format, ##__VA_ARGS__)
63#define promekiLogSync() Logger::defaultLogger().sync()
64#define promekiLogStackTrace(_plevel) \
65 do { \
66 if (!(_plevel) || (_plevel) >= Logger::defaultLogger().level()) \
67 Logger::defaultLogger().log(_plevel, PROMEKI_SOURCE_FILE, __LINE__, promekiStackTrace()); \
68 } while (0)
69
70#ifdef PROMEKI_DEBUG_ENABLE
71#define promekiDebug(format, ...) \
72 if (_promeki_debug_enabled) { \
73 Logger::defaultLogger().log(Logger::LogLevel::Debug, PROMEKI_SOURCE_FILE, __LINE__, \
74 String::sprintf(format, ##__VA_ARGS__)); \
75 }
76#else
77#define promekiDebug(format, ...)
78#endif
79
80#define promekiInfo(format, ...) promekiLog(Logger::LogLevel::Info, format, ##__VA_ARGS__)
81#define promekiWarn(format, ...) promekiLog(Logger::LogLevel::Warn, format, ##__VA_ARGS__)
82#define promekiErr(format, ...) promekiLog(Logger::LogLevel::Err, format, ##__VA_ARGS__)
83
84bool promekiRegisterDebug(bool *enabler, const char *name, const char *file, int line);
85
86#define PROMEKI_BENCHMARK_BEGIN(name) \
87 TimeStamp _promeki_debug_timestamp_##name; \
88 if (_promeki_debug_enabled) { \
89 _promeki_debug_timestamp_##name = TimeStamp::now(); \
90 }
91
92#define PROMEKI_BENCHMARK_END(name) \
93 if (_promeki_debug_enabled) { \
94 Logger::defaultLogger().log(Logger::LogLevel::Debug, __FILE__, __LINE__, \
95 String::sprintf("[%s] %s took %.9lf sec", _promeki_debug_name, \
96 PROMEKI_STRINGIFY(name), \
97 _promeki_debug_timestamp_##name.elapsedSeconds())); \
98 }
99
126class Logger {
127 public:
129 enum LogLevel {
130 Force = 0,
131 Debug = 1,
132 Info = 2,
133 Warn = 3,
134 Err = 4
135 };
136
138 struct LogEntry {
139 DateTime ts;
140 LogLevel level;
141 const char *file;
142 int line;
143 uint64_t threadId;
144 String msg;
145 };
146
148 struct LogFormat {
149 const LogEntry *entry;
150 const String *threadName;
151 };
152
159 using LogFormatter = Function<String(const LogFormat &fmt)>;
160
169 using ListenerHandle = uint64_t;
170
185 using LogListener = Function<void(const LogEntry &entry, const String &threadName)>;
186
188 static constexpr size_t DefaultHistorySize = 1024;
189
194 static Logger &defaultLogger();
195
201 static char levelToChar(LogLevel level);
202
213 static void setThreadName(const String &name);
214
216 struct DebugChannel {
217 String name;
218 String file;
219 int line = 0;
220 bool enabled = false;
221
222 using List = ::promeki::List<DebugChannel>;
223 };
224
235 static DebugChannel::List debugChannels();
236
252 static bool setDebugChannel(const String &name, bool enabled);
253
260 static LogFormatter defaultFileFormatter();
261
267 static LogFormatter defaultConsoleFormatter();
268
270 Logger();
271
279 ~Logger();
280
285 int level() const { return _level.value(); }
286
298 void log(LogLevel loglevel, const char *file, int line, const String &msg);
299
311 void log(LogLevel loglevel, const char *file, int line, const StringList &lines);
312
317 void setLogFile(const String &filename) {
318 if (_terminating.value()) return;
319 _queue.emplace(CmdSetFile{filename});
320 }
321
326 void setLogLevel(LogLevel level) {
327 _level.setValue(level);
328 log(Force, "LOGGER", 0, String::sprintf("Logging Level Changed to %d", level));
329 return;
330 }
331
336 bool consoleLoggingEnabled() const { return _consoleLogging.value(); }
337
342 void setConsoleLoggingEnabled(bool val) {
343 _consoleLogging.setValue(val);
344 return;
345 }
346
350 LogFormatter fileFormatter() const {
351 Mutex::Locker lock(_formatterMutex);
352 return _fileFormatter;
353 }
354
358 LogFormatter consoleFormatter() const {
359 Mutex::Locker lock(_formatterMutex);
360 return _consoleFormatter;
361 }
362
368 void setFileFormatter(LogFormatter formatter) {
369 {
370 Mutex::Locker lock(_formatterMutex);
371 _fileFormatter = formatter ? formatter : defaultFileFormatter();
372 }
373 if (_terminating.value()) return;
374 _queue.emplace(CmdSetFormatter{std::move(formatter), false});
375 }
376
382 void setConsoleFormatter(LogFormatter formatter) {
383 {
384 Mutex::Locker lock(_formatterMutex);
385 _consoleFormatter = formatter ? formatter : defaultConsoleFormatter();
386 }
387 if (_terminating.value()) return;
388 _queue.emplace(CmdSetFormatter{std::move(formatter), true});
389 }
390
416 ListenerHandle installListener(LogListener listener, size_t replayCount = 0);
417
427 void removeListener(ListenerHandle handle);
428
439 void setHistorySize(size_t n) { _historySize.setValue(n); }
440
442 size_t historySize() const { return _historySize.value(); }
443
451 Error sync(unsigned int timeoutMs = 0) {
452 if (_terminating.value()) return Error::Ok;
453 auto p = std::make_shared<Promise<void>>();
454 Future<void> f = p->future();
455 _queue.emplace(CmdSync{std::move(p)});
456 if (timeoutMs == 0) {
457 f.waitForFinished();
458 return Error::Ok;
459 }
460 return f.waitForFinished(timeoutMs);
461 }
462
463 private:
464 struct CmdSetThreadName {
465 uint64_t threadId;
466 String name;
467 };
468
469 struct CmdSetFile {
470 String filename;
471 };
472
473 struct CmdSetFormatter {
474 LogFormatter formatter;
475 bool console;
476 };
477
478 struct CmdSync {
479 std::shared_ptr<Promise<void>> promise;
480 };
481
482 struct CmdInstallListener {
483 LogListener listener;
484 size_t replayCount;
485 std::shared_ptr<Promise<ListenerHandle>> promise;
486 };
487
488 struct CmdRemoveListener {
489 ListenerHandle handle;
490 std::shared_ptr<Promise<void>> promise;
491 };
492
493 struct CmdTerminate {};
494
495 using Command = std::variant<LogEntry, CmdSetThreadName, CmdSetFile, CmdSetFormatter, CmdSync,
496 CmdInstallListener, CmdRemoveListener, CmdTerminate>;
497
498 struct ListenerEntry {
499 ListenerHandle handle;
500 LogListener fn;
501 };
502
503 struct HistoryEntry {
504 LogEntry entry;
505 String threadName;
506 };
507
508 BasicThread _thread;
509 Atomic<int> _level;
510 Atomic<bool> _consoleLogging;
511 Atomic<bool> _terminating{false};
512 Atomic<size_t> _historySize{DefaultHistorySize};
513 Atomic<uint64_t> _nextListenerHandle{0};
514 Queue<Command> _queue;
515 mutable Mutex _formatterMutex;
516 LogFormatter _fileFormatter;
517 LogFormatter _consoleFormatter;
518 Map<uint64_t, String> _threadNames;
519 List<ListenerEntry> _listeners;
520 Deque<HistoryEntry> _history;
521
522 void worker();
523 void writeLog(const LogEntry &cmd, class FileIODevice *logFile);
524 class FileIODevice *openLogFile(const String &filename, class FileIODevice *existing);
525};
526
527PROMEKI_NAMESPACE_END
528
529#endif // PROMEKI_ENABLE_CORE