11#include <promeki/config.h>
12#if PROMEKI_ENABLE_CORE
30PROMEKI_NAMESPACE_BEGIN
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;
42#define PROMEKI_SOURCE_FILE __FILE_NAME__
44#define PROMEKI_SOURCE_FILE promeki::sourceFileName(__FILE__)
49#define PROMEKI_DEBUG(name) \
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__); \
56#define promekiLogImpl(_plevel, format, ...) \
58 if (!(_plevel) || (_plevel) >= Logger::defaultLogger().level()) \
59 Logger::defaultLogger().log(_plevel, PROMEKI_SOURCE_FILE, __LINE__, \
60 String::sprintf(format, ##__VA_ARGS__)); \
62#define promekiLog(level, format, ...) promekiLogImpl(level, format, ##__VA_ARGS__)
63#define promekiLogSync() Logger::defaultLogger().sync()
64#define promekiLogStackTrace(_plevel) \
66 if (!(_plevel) || (_plevel) >= Logger::defaultLogger().level()) \
67 Logger::defaultLogger().log(_plevel, PROMEKI_SOURCE_FILE, __LINE__, promekiStackTrace()); \
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__)); \
77#define promekiDebug(format, ...)
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__)
84bool promekiRegisterDebug(
bool *enabler,
const char *name,
const char *file,
int line);
86#define PROMEKI_BENCHMARK_BEGIN(name) \
87 TimeStamp _promeki_debug_timestamp_##name; \
88 if (_promeki_debug_enabled) { \
89 _promeki_debug_timestamp_##name = TimeStamp::now(); \
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())); \
149 const LogEntry *entry;
150 const String *threadName;
159 using LogFormatter = Function<String(
const LogFormat &fmt)>;
169 using ListenerHandle = uint64_t;
185 using LogListener = Function<void(
const LogEntry &entry,
const String &threadName)>;
188 static constexpr size_t DefaultHistorySize = 1024;
194 static Logger &defaultLogger();
201 static char levelToChar(LogLevel level);
213 static void setThreadName(
const String &name);
216 struct DebugChannel {
220 bool enabled =
false;
222 using List = ::promeki::List<DebugChannel>;
235 static DebugChannel::List debugChannels();
252 static bool setDebugChannel(
const String &name,
bool enabled);
260 static LogFormatter defaultFileFormatter();
267 static LogFormatter defaultConsoleFormatter();
285 int level()
const {
return _level.value(); }
298 void log(LogLevel loglevel,
const char *file,
int line,
const String &msg);
311 void log(LogLevel loglevel,
const char *file,
int line,
const StringList &lines);
317 void setLogFile(
const String &filename) {
318 if (_terminating.value())
return;
319 _queue.emplace(CmdSetFile{filename});
326 void setLogLevel(LogLevel level) {
327 _level.setValue(level);
328 log(Force,
"LOGGER", 0, String::sprintf(
"Logging Level Changed to %d", level));
336 bool consoleLoggingEnabled()
const {
return _consoleLogging.value(); }
342 void setConsoleLoggingEnabled(
bool val) {
343 _consoleLogging.setValue(val);
350 LogFormatter fileFormatter()
const {
351 Mutex::Locker lock(_formatterMutex);
352 return _fileFormatter;
358 LogFormatter consoleFormatter()
const {
359 Mutex::Locker lock(_formatterMutex);
360 return _consoleFormatter;
368 void setFileFormatter(LogFormatter formatter) {
370 Mutex::Locker lock(_formatterMutex);
371 _fileFormatter = formatter ? formatter : defaultFileFormatter();
373 if (_terminating.value())
return;
374 _queue.emplace(CmdSetFormatter{std::move(formatter),
false});
382 void setConsoleFormatter(LogFormatter formatter) {
384 Mutex::Locker lock(_formatterMutex);
385 _consoleFormatter = formatter ? formatter : defaultConsoleFormatter();
387 if (_terminating.value())
return;
388 _queue.emplace(CmdSetFormatter{std::move(formatter),
true});
416 ListenerHandle installListener(LogListener listener,
size_t replayCount = 0);
427 void removeListener(ListenerHandle handle);
439 void setHistorySize(
size_t n) { _historySize.setValue(n); }
442 size_t historySize()
const {
return _historySize.value(); }
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) {
460 return f.waitForFinished(timeoutMs);
464 struct CmdSetThreadName {
473 struct CmdSetFormatter {
474 LogFormatter formatter;
479 std::shared_ptr<Promise<void>> promise;
482 struct CmdInstallListener {
483 LogListener listener;
485 std::shared_ptr<Promise<ListenerHandle>> promise;
488 struct CmdRemoveListener {
489 ListenerHandle handle;
490 std::shared_ptr<Promise<void>> promise;
493 struct CmdTerminate {};
495 using Command = std::variant<LogEntry, CmdSetThreadName, CmdSetFile, CmdSetFormatter, CmdSync,
496 CmdInstallListener, CmdRemoveListener, CmdTerminate>;
498 struct ListenerEntry {
499 ListenerHandle handle;
503 struct HistoryEntry {
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;
523 void writeLog(
const LogEntry &cmd,
class FileIODevice *logFile);
524 class FileIODevice *openLogFile(
const String &filename,
class FileIODevice *existing);