11#include <promeki/config.h>
12#if PROMEKI_ENABLE_CORE
31PROMEKI_NAMESPACE_BEGIN
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;
43#define PROMEKI_SOURCE_FILE __FILE_NAME__
45#define PROMEKI_SOURCE_FILE promeki::sourceFileName(__FILE__)
50#define PROMEKI_DEBUG(name) \
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__); \
57#define promekiLogImpl(_plevel, format, ...) \
59 if (!(_plevel) || (_plevel) >= Logger::defaultLogger().level()) \
60 Logger::defaultLogger().log(_plevel, PROMEKI_SOURCE_FILE, __LINE__, \
61 String::sprintf(format, ##__VA_ARGS__)); \
63#define promekiLog(level, format, ...) promekiLogImpl(level, format, ##__VA_ARGS__)
64#define promekiLogSync() Logger::defaultLogger().sync()
65#define promekiLogStackTrace(_plevel) \
67 if (!(_plevel) || (_plevel) >= Logger::defaultLogger().level()) \
68 Logger::defaultLogger().log(_plevel, PROMEKI_SOURCE_FILE, __LINE__, promekiStackTrace()); \
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__)); \
78#define promekiDebug(format, ...)
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__)
93#define promekiInfoOnce(format, ...) \
94 PROMEKI_ONCE { promekiInfo(format, ##__VA_ARGS__); }
97#define promekiWarnOnce(format, ...) \
98 PROMEKI_ONCE { promekiWarn(format, ##__VA_ARGS__); }
101#define promekiErrOnce(format, ...) \
102 PROMEKI_ONCE { promekiErr(format, ##__VA_ARGS__); }
111#define promekiInfoThrottled(intervalMs, format, ...) \
112 PROMEKI_THROTTLED(intervalMs) { promekiInfo(format, ##__VA_ARGS__); }
115#define promekiWarnThrottled(intervalMs, format, ...) \
116 PROMEKI_THROTTLED(intervalMs) { promekiWarn(format, ##__VA_ARGS__); }
119#define promekiErrThrottled(intervalMs, format, ...) \
120 PROMEKI_THROTTLED(intervalMs) { promekiErr(format, ##__VA_ARGS__); }
122bool promekiRegisterDebug(
bool *enabler,
const char *name,
const char *file,
int line);
124#define PROMEKI_BENCHMARK_BEGIN(name) \
125 TimeStamp _promeki_debug_timestamp_##name; \
126 if (_promeki_debug_enabled) { \
127 _promeki_debug_timestamp_##name = TimeStamp::now(); \
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())); \
187 const LogEntry *entry;
188 const String *threadName;
197 using LogFormatter = Function<String(
const LogFormat &fmt)>;
207 using ListenerHandle = uint64_t;
223 using LogListener = Function<void(
const LogEntry &entry,
const String &threadName)>;
226 static constexpr size_t DefaultHistorySize = 1024;
232 static Logger &defaultLogger();
239 static char levelToChar(LogLevel level);
251 static void setThreadName(
const String &name);
254 struct DebugChannel {
258 bool enabled =
false;
260 using List = ::promeki::List<DebugChannel>;
273 static DebugChannel::List debugChannels();
290 static bool setDebugChannel(
const String &name,
bool enabled);
298 static LogFormatter defaultFileFormatter();
305 static LogFormatter defaultConsoleFormatter();
323 int level()
const {
return _level.value(); }
336 void log(LogLevel loglevel,
const char *file,
int line,
const String &msg);
349 void log(LogLevel loglevel,
const char *file,
int line,
const StringList &lines);
355 void setLogFile(
const String &filename) {
356 if (_terminating.value())
return;
357 _queue.emplace(CmdSetFile{filename});
364 void setLogLevel(LogLevel level) {
365 _level.setValue(level);
366 log(Force,
"LOGGER", 0, String::sprintf(
"Logging Level Changed to %d", level));
374 bool consoleLoggingEnabled()
const {
return _consoleLogging.value(); }
380 void setConsoleLoggingEnabled(
bool val) {
381 _consoleLogging.setValue(val);
389 bool consoleUseStderr()
const {
return _consoleUseStderr.value(); }
402 void setConsoleUseStderr(
bool val) {
403 _consoleUseStderr.setValue(val);
410 LogFormatter fileFormatter()
const {
411 Mutex::Locker lock(_formatterMutex);
412 return _fileFormatter;
418 LogFormatter consoleFormatter()
const {
419 Mutex::Locker lock(_formatterMutex);
420 return _consoleFormatter;
428 void setFileFormatter(LogFormatter formatter) {
430 Mutex::Locker lock(_formatterMutex);
431 _fileFormatter = formatter ? formatter : defaultFileFormatter();
433 if (_terminating.value())
return;
434 _queue.emplace(CmdSetFormatter{std::move(formatter),
false});
442 void setConsoleFormatter(LogFormatter formatter) {
444 Mutex::Locker lock(_formatterMutex);
445 _consoleFormatter = formatter ? formatter : defaultConsoleFormatter();
447 if (_terminating.value())
return;
448 _queue.emplace(CmdSetFormatter{std::move(formatter),
true});
476 ListenerHandle installListener(LogListener listener,
size_t replayCount = 0);
487 void removeListener(ListenerHandle handle);
499 void setHistorySize(
size_t n) { _historySize.setValue(n); }
502 size_t historySize()
const {
return _historySize.value(); }
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) {
520 return f.waitForFinished(timeoutMs);
524 struct CmdSetThreadName {
533 struct CmdSetFormatter {
534 LogFormatter formatter;
539 std::shared_ptr<Promise<void>> promise;
542 struct CmdInstallListener {
543 LogListener listener;
545 std::shared_ptr<Promise<ListenerHandle>> promise;
548 struct CmdRemoveListener {
549 ListenerHandle handle;
550 std::shared_ptr<Promise<void>> promise;
553 struct CmdTerminate {};
555 using Command = std::variant<LogEntry, CmdSetThreadName, CmdSetFile, CmdSetFormatter, CmdSync,
556 CmdInstallListener, CmdRemoveListener, CmdTerminate>;
558 struct ListenerEntry {
559 ListenerHandle handle;
563 struct HistoryEntry {
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;
584 void writeLog(
const LogEntry &cmd,
class FileIODevice *logFile);
585 class FileIODevice *openLogFile(
const String &filename,
class FileIODevice *existing);