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/gate.h>
29#include <promeki/timestamp.h>
30
31PROMEKI_NAMESPACE_BEGIN
32
34static consteval const char *sourceFileName(const char *path) {
35 const char *ret = path;
36 for (const char *p = path; *p; ++p) {
37 if (*p == '/' || *p == '\\') ret = p + 1;
38 }
39 return ret;
40}
41
42#ifdef __FILE_NAME__
43#define PROMEKI_SOURCE_FILE __FILE_NAME__
44#else
45#define PROMEKI_SOURCE_FILE promeki::sourceFileName(__FILE__)
46#endif
47
48// Variadic macros for logging at various levels
49
50#define PROMEKI_DEBUG(name) \
51 namespace { \
52 [[maybe_unused]] static const char *_promeki_debug_name = PROMEKI_STRINGIFY(name); \
53 [[maybe_unused]] static bool _promeki_debug_enabled = promekiRegisterDebug( \
54 &_promeki_debug_enabled, PROMEKI_STRINGIFY(name), PROMEKI_SOURCE_FILE, __LINE__); \
55 }
56
57#define promekiLogImpl(_plevel, format, ...) \
58 do { \
59 if (!(_plevel) || (_plevel) >= Logger::defaultLogger().level()) \
60 Logger::defaultLogger().log(_plevel, PROMEKI_SOURCE_FILE, __LINE__, \
61 String::sprintf(format, ##__VA_ARGS__)); \
62 } while (0)
63#define promekiLog(level, format, ...) promekiLogImpl(level, format, ##__VA_ARGS__)
64#define promekiLogSync() Logger::defaultLogger().sync()
65#define promekiLogStackTrace(_plevel) \
66 do { \
67 if (!(_plevel) || (_plevel) >= Logger::defaultLogger().level()) \
68 Logger::defaultLogger().log(_plevel, PROMEKI_SOURCE_FILE, __LINE__, promekiStackTrace()); \
69 } while (0)
70
71#ifdef PROMEKI_DEBUG_ENABLE
72#define promekiDebug(format, ...) \
73 if (_promeki_debug_enabled) { \
74 Logger::defaultLogger().log(Logger::LogLevel::Debug, PROMEKI_SOURCE_FILE, __LINE__, \
75 String::sprintf(format, ##__VA_ARGS__)); \
76 }
77#else
78#define promekiDebug(format, ...)
79#endif
80
81#define promekiInfo(format, ...) promekiLog(Logger::LogLevel::Info, format, ##__VA_ARGS__)
82#define promekiWarn(format, ...) promekiLog(Logger::LogLevel::Warn, format, ##__VA_ARGS__)
83#define promekiErr(format, ...) promekiLog(Logger::LogLevel::Err, format, ##__VA_ARGS__)
84
93#define promekiInfoOnce(format, ...) \
94 PROMEKI_ONCE { promekiInfo(format, ##__VA_ARGS__); }
95
97#define promekiWarnOnce(format, ...) \
98 PROMEKI_ONCE { promekiWarn(format, ##__VA_ARGS__); }
99
101#define promekiErrOnce(format, ...) \
102 PROMEKI_ONCE { promekiErr(format, ##__VA_ARGS__); }
103
111#define promekiInfoThrottled(intervalMs, format, ...) \
112 PROMEKI_THROTTLED(intervalMs) { promekiInfo(format, ##__VA_ARGS__); }
113
115#define promekiWarnThrottled(intervalMs, format, ...) \
116 PROMEKI_THROTTLED(intervalMs) { promekiWarn(format, ##__VA_ARGS__); }
117
119#define promekiErrThrottled(intervalMs, format, ...) \
120 PROMEKI_THROTTLED(intervalMs) { promekiErr(format, ##__VA_ARGS__); }
121
122bool promekiRegisterDebug(bool *enabler, const char *name, const char *file, int line);
123
124#define PROMEKI_BENCHMARK_BEGIN(name) \
125 TimeStamp _promeki_debug_timestamp_##name; \
126 if (_promeki_debug_enabled) { \
127 _promeki_debug_timestamp_##name = TimeStamp::now(); \
128 }
129
130#define PROMEKI_BENCHMARK_END(name) \
131 if (_promeki_debug_enabled) { \
132 Logger::defaultLogger().log(Logger::LogLevel::Debug, PROMEKI_SOURCE_FILE, __LINE__, \
133 String::sprintf("[%s] %s took %.9lf sec", _promeki_debug_name, \
134 PROMEKI_STRINGIFY(name), \
135 _promeki_debug_timestamp_##name.elapsedSeconds())); \
136 }
137
164class Logger {
165 public:
167 enum LogLevel {
168 Force = 0,
169 Debug = 1,
170 Info = 2,
171 Warn = 3,
172 Err = 4
173 };
174
176 struct LogEntry {
177 DateTime ts;
178 LogLevel level;
179 const char *file;
180 int line;
181 uint64_t threadId;
182 String msg;
183 };
184
186 struct LogFormat {
187 const LogEntry *entry;
188 const String *threadName;
189 };
190
197 using LogFormatter = Function<String(const LogFormat &fmt)>;
198
207 using ListenerHandle = uint64_t;
208
223 using LogListener = Function<void(const LogEntry &entry, const String &threadName)>;
224
226 static constexpr size_t DefaultHistorySize = 1024;
227
232 static Logger &defaultLogger();
233
239 static char levelToChar(LogLevel level);
240
251 static void setThreadName(const String &name);
252
254 struct DebugChannel {
255 String name;
256 String file;
257 int line = 0;
258 bool enabled = false;
259
260 using List = ::promeki::List<DebugChannel>;
261 };
262
273 static DebugChannel::List debugChannels();
274
290 static bool setDebugChannel(const String &name, bool enabled);
291
298 static LogFormatter defaultFileFormatter();
299
305 static LogFormatter defaultConsoleFormatter();
306
308 Logger();
309
317 ~Logger();
318
323 int level() const { return _level.value(); }
324
336 void log(LogLevel loglevel, const char *file, int line, const String &msg);
337
349 void log(LogLevel loglevel, const char *file, int line, const StringList &lines);
350
355 void setLogFile(const String &filename) {
356 if (_terminating.value()) return;
357 _queue.emplace(CmdSetFile{filename});
358 }
359
364 void setLogLevel(LogLevel level) {
365 _level.setValue(level);
366 log(Force, "LOGGER", 0, String::sprintf("Logging Level Changed to %d", level));
367 return;
368 }
369
374 bool consoleLoggingEnabled() const { return _consoleLogging.value(); }
375
380 void setConsoleLoggingEnabled(bool val) {
381 _consoleLogging.setValue(val);
382 return;
383 }
384
389 bool consoleUseStderr() const { return _consoleUseStderr.value(); }
390
402 void setConsoleUseStderr(bool val) {
403 _consoleUseStderr.setValue(val);
404 return;
405 }
406
410 LogFormatter fileFormatter() const {
411 Mutex::Locker lock(_formatterMutex);
412 return _fileFormatter;
413 }
414
418 LogFormatter consoleFormatter() const {
419 Mutex::Locker lock(_formatterMutex);
420 return _consoleFormatter;
421 }
422
428 void setFileFormatter(LogFormatter formatter) {
429 {
430 Mutex::Locker lock(_formatterMutex);
431 _fileFormatter = formatter ? formatter : defaultFileFormatter();
432 }
433 if (_terminating.value()) return;
434 _queue.emplace(CmdSetFormatter{std::move(formatter), false});
435 }
436
442 void setConsoleFormatter(LogFormatter formatter) {
443 {
444 Mutex::Locker lock(_formatterMutex);
445 _consoleFormatter = formatter ? formatter : defaultConsoleFormatter();
446 }
447 if (_terminating.value()) return;
448 _queue.emplace(CmdSetFormatter{std::move(formatter), true});
449 }
450
476 ListenerHandle installListener(LogListener listener, size_t replayCount = 0);
477
487 void removeListener(ListenerHandle handle);
488
499 void setHistorySize(size_t n) { _historySize.setValue(n); }
500
502 size_t historySize() const { return _historySize.value(); }
503
511 Error sync(unsigned int timeoutMs = 0) {
512 if (_terminating.value()) return Error::Ok;
513 auto p = std::make_shared<Promise<void>>();
514 Future<void> f = p->future();
515 _queue.emplace(CmdSync{std::move(p)});
516 if (timeoutMs == 0) {
517 f.waitForFinished();
518 return Error::Ok;
519 }
520 return f.waitForFinished(timeoutMs);
521 }
522
523 private:
524 struct CmdSetThreadName {
525 uint64_t threadId;
526 String name;
527 };
528
529 struct CmdSetFile {
530 String filename;
531 };
532
533 struct CmdSetFormatter {
534 LogFormatter formatter;
535 bool console;
536 };
537
538 struct CmdSync {
539 std::shared_ptr<Promise<void>> promise;
540 };
541
542 struct CmdInstallListener {
543 LogListener listener;
544 size_t replayCount;
545 std::shared_ptr<Promise<ListenerHandle>> promise;
546 };
547
548 struct CmdRemoveListener {
549 ListenerHandle handle;
550 std::shared_ptr<Promise<void>> promise;
551 };
552
553 struct CmdTerminate {};
554
555 using Command = std::variant<LogEntry, CmdSetThreadName, CmdSetFile, CmdSetFormatter, CmdSync,
556 CmdInstallListener, CmdRemoveListener, CmdTerminate>;
557
558 struct ListenerEntry {
559 ListenerHandle handle;
560 LogListener fn;
561 };
562
563 struct HistoryEntry {
564 LogEntry entry;
565 String threadName;
566 };
567
568 BasicThread _thread;
569 Atomic<int> _level;
570 Atomic<bool> _consoleLogging;
571 Atomic<bool> _consoleUseStderr;
572 Atomic<bool> _terminating{false};
573 Atomic<size_t> _historySize{DefaultHistorySize};
574 Atomic<uint64_t> _nextListenerHandle{0};
575 Queue<Command> _queue;
576 mutable Mutex _formatterMutex;
577 LogFormatter _fileFormatter;
578 LogFormatter _consoleFormatter;
579 Map<uint64_t, String> _threadNames;
580 List<ListenerEntry> _listeners;
581 Deque<HistoryEntry> _history;
582
583 void worker();
584 void writeLog(const LogEntry &cmd, class FileIODevice *logFile);
585 class FileIODevice *openLogFile(const String &filename, class FileIODevice *existing);
586};
587
588PROMEKI_NAMESPACE_END
589
590#endif // PROMEKI_ENABLE_CORE