aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/include
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-09 10:50:47 +0100
committerGitHub Enterprise <[email protected]>2026-03-09 10:50:47 +0100
commit19a117889c2db6b817af9458c04c04f324162e75 (patch)
treefbbd0d01c5bf40be90cec88e1d02c6a3c529a2f5 /src/zencore/include
parentzenstore bug-fixes from static analysis pass (#815) (diff)
downloadzen-19a117889c2db6b817af9458c04c04f324162e75.tar.xz
zen-19a117889c2db6b817af9458c04c04f324162e75.zip
Eliminate spdlog dependency (#773)
Removes the vendored spdlog library (~12,000 lines) and replaces it with a purpose-built logging system in zencore (~1,800 lines). The new implementation provides the same functionality with fewer abstractions, no shared_ptr overhead, and full control over the logging pipeline. ### What changed **New logging core in zencore/logging/:** - LogMessage, Formatter, Sink, Logger, Registry - core abstractions matching spdlog's model but simplified - AnsiColorStdoutSink - ANSI color console output (replaces spdlog stdout_color_sink) - MsvcSink - OutputDebugString on Windows (replaces spdlog msvc_sink) - AsyncSink - async logging via BlockingQueue worker thread (replaces spdlog async_logger) - NullSink, MessageOnlyFormatter - utility types - Thread-safe timestamp caching in formatters using RwLock **Moved to zenutil/logging/:** - FullFormatter - full log formatting with timestamp, logger name, level, source location, multiline alignment - JsonFormatter - structured JSON log output - RotatingFileSink - rotating file sink with atomic size tracking **API changes:** - Log levels are now an enum (LogLevel) instead of int, eliminating the zen::logging::level namespace - LoggerRef no longer wraps shared_ptr - it holds a raw pointer with the registry owning lifetime - Logger error handler is wired through Registry and propagated to all loggers on registration - Logger::Log() now populates ThreadId on every message **Cleanup:** - Deleted thirdparty/spdlog/ entirely (110+ files) - Deleted full_test_formatter (was ~80% duplicate of FullFormatter) - Renamed snake_case classes to PascalCase (full_formatter -> FullFormatter, json_formatter -> JsonFormatter, sentry_sink -> SentrySink) - Removed spdlog from xmake dependency graph ### Build / test impact - zencore no longer depends on spdlog - zenutil and zenvfs xmake.lua updated to drop spdlog dep - zentelemetry xmake.lua updated to drop spdlog dep - All existing tests pass, no test changes required beyond formatter class renames
Diffstat (limited to 'src/zencore/include')
-rw-r--r--src/zencore/include/zencore/blockingqueue.h2
-rw-r--r--src/zencore/include/zencore/logbase.h113
-rw-r--r--src/zencore/include/zencore/logging.h214
-rw-r--r--src/zencore/include/zencore/logging/ansicolorsink.h26
-rw-r--r--src/zencore/include/zencore/logging/asyncsink.h30
-rw-r--r--src/zencore/include/zencore/logging/formatter.h20
-rw-r--r--src/zencore/include/zencore/logging/helpers.h122
-rw-r--r--src/zencore/include/zencore/logging/logger.h63
-rw-r--r--src/zencore/include/zencore/logging/logmsg.h66
-rw-r--r--src/zencore/include/zencore/logging/memorybuffer.h11
-rw-r--r--src/zencore/include/zencore/logging/messageonlyformatter.h22
-rw-r--r--src/zencore/include/zencore/logging/msvcsink.h30
-rw-r--r--src/zencore/include/zencore/logging/nullsink.h17
-rw-r--r--src/zencore/include/zencore/logging/registry.h70
-rw-r--r--src/zencore/include/zencore/logging/sink.h34
-rw-r--r--src/zencore/include/zencore/logging/tracesink.h23
-rw-r--r--src/zencore/include/zencore/sentryintegration.h8
17 files changed, 683 insertions, 188 deletions
diff --git a/src/zencore/include/zencore/blockingqueue.h b/src/zencore/include/zencore/blockingqueue.h
index e91fdc659..b6c93e937 100644
--- a/src/zencore/include/zencore/blockingqueue.h
+++ b/src/zencore/include/zencore/blockingqueue.h
@@ -2,6 +2,8 @@
#pragma once
+#include <zencore/zencore.h> // For ZEN_ASSERT
+
#include <atomic>
#include <condition_variable>
#include <deque>
diff --git a/src/zencore/include/zencore/logbase.h b/src/zencore/include/zencore/logbase.h
index 00af68b0a..ece17a85e 100644
--- a/src/zencore/include/zencore/logbase.h
+++ b/src/zencore/include/zencore/logbase.h
@@ -4,96 +4,85 @@
#include <string_view>
-#define ZEN_LOG_LEVEL_TRACE 0
-#define ZEN_LOG_LEVEL_DEBUG 1
-#define ZEN_LOG_LEVEL_INFO 2
-#define ZEN_LOG_LEVEL_WARN 3
-#define ZEN_LOG_LEVEL_ERROR 4
-#define ZEN_LOG_LEVEL_CRITICAL 5
-#define ZEN_LOG_LEVEL_OFF 6
-
-#define ZEN_LEVEL_NAME_TRACE std::string_view("trace", 5)
-#define ZEN_LEVEL_NAME_DEBUG std::string_view("debug", 5)
-#define ZEN_LEVEL_NAME_INFO std::string_view("info", 4)
-#define ZEN_LEVEL_NAME_WARNING std::string_view("warning", 7)
-#define ZEN_LEVEL_NAME_ERROR std::string_view("error", 5)
-#define ZEN_LEVEL_NAME_CRITICAL std::string_view("critical", 8)
-#define ZEN_LEVEL_NAME_OFF std::string_view("off", 3)
-
-namespace zen::logging::level {
+namespace zen::logging {
enum LogLevel : int
{
- Trace = ZEN_LOG_LEVEL_TRACE,
- Debug = ZEN_LOG_LEVEL_DEBUG,
- Info = ZEN_LOG_LEVEL_INFO,
- Warn = ZEN_LOG_LEVEL_WARN,
- Err = ZEN_LOG_LEVEL_ERROR,
- Critical = ZEN_LOG_LEVEL_CRITICAL,
- Off = ZEN_LOG_LEVEL_OFF,
+ Trace,
+ Debug,
+ Info,
+ Warn,
+ Err,
+ Critical,
+ Off,
LogLevelCount
};
LogLevel ParseLogLevelString(std::string_view String);
std::string_view ToStringView(LogLevel Level);
-} // namespace zen::logging::level
-
-namespace zen::logging {
-
-void SetLogLevel(level::LogLevel NewLogLevel);
-level::LogLevel GetLogLevel();
+void SetLogLevel(LogLevel NewLogLevel);
+LogLevel GetLogLevel();
-} // namespace zen::logging
+struct SourceLocation
+{
+ constexpr SourceLocation() = default;
+ constexpr SourceLocation(const char* InFilename, int InLine) : Filename(InFilename), Line(InLine) {}
-namespace spdlog {
-class logger;
-}
+ constexpr operator bool() const noexcept { return Line != 0; }
-namespace zen::logging {
+ const char* Filename{nullptr};
+ int Line{0};
+};
-struct SourceLocation
+/** This encodes the constant parts of a log message which can be emitted once
+ * and then referred to by log events.
+ *
+ * It's *critical* that instances of this struct are permanent and never
+ * destroyed, as log messages will refer to them by pointer. The easiest way
+ * to ensure this is to create them as function-local statics.
+ *
+ * The logging macros already do this for you so this should not be something
+ * you normally would need to worry about.
+ */
+struct LogPoint
{
- constexpr SourceLocation() = default;
- constexpr SourceLocation(const char* filename_in, int line_in, const char* funcname_in)
- : filename(filename_in)
- , line(line_in)
- , funcname(funcname_in)
- {
- }
-
- constexpr bool empty() const noexcept { return line == 0; }
-
- // IMPORTANT NOTE: the layout of this class must match the spdlog::source_loc class
- // since we currently pass a pointer to it into spdlog after casting it to
- // spdlog::source_loc*
- //
- // This is intended to be an intermediate state, before we (probably) transition off
- // spdlog entirely
-
- const char* filename{nullptr};
- int line{0};
- const char* funcname{nullptr};
+ SourceLocation Location;
+ LogLevel Level;
+ std::string_view FormatString;
};
+class Logger;
+
} // namespace zen::logging
namespace zen {
+// Lightweight non-owning handle to a Logger. Loggers are owned by the Registry
+// via Ref<Logger>; LoggerRef exists as a cheap (raw pointer) handle that can be
+// stored in members and passed through logging macros without requiring the
+// complete Logger type or incurring refcount overhead on every log call.
struct LoggerRef
{
LoggerRef() = default;
- LoggerRef(spdlog::logger& InLogger) : SpdLogger(&InLogger) {}
+ LoggerRef(logging::Logger& InLogger) : m_Logger(&InLogger) {}
+ // This exists so that logging macros can pass LoggerRef or LogCategory
+ // to ZEN_LOG without needing to know which one it is
LoggerRef Logger() { return *this; }
- bool ShouldLog(int Level) const;
- inline operator bool() const { return SpdLogger != nullptr; }
+ bool ShouldLog(logging::LogLevel Level) const;
+ inline operator bool() const { return m_Logger != nullptr; }
+
+ inline logging::Logger* operator->() const { return m_Logger; }
+ inline logging::Logger& operator*() const { return *m_Logger; }
- void SetLogLevel(logging::level::LogLevel NewLogLevel);
- logging::level::LogLevel GetLogLevel();
+ void SetLogLevel(logging::LogLevel NewLogLevel);
+ logging::LogLevel GetLogLevel();
+ void Flush();
- spdlog::logger* SpdLogger = nullptr;
+private:
+ logging::Logger* m_Logger = nullptr;
};
} // namespace zen
diff --git a/src/zencore/include/zencore/logging.h b/src/zencore/include/zencore/logging.h
index 74a44d028..4b593c19e 100644
--- a/src/zencore/include/zencore/logging.h
+++ b/src/zencore/include/zencore/logging.h
@@ -9,16 +9,9 @@
#if ZEN_PLATFORM_WINDOWS
# define ZEN_LOG_SECTION(Id) ZEN_DATA_SECTION(Id)
-# pragma section(".zlog$f", read)
# pragma section(".zlog$l", read)
-# pragma section(".zlog$m", read)
-# pragma section(".zlog$s", read)
-# define ZEN_DECLARE_FUNCTION static constinit ZEN_LOG_SECTION(".zlog$f") char FuncName[] = __FUNCTION__;
-# define ZEN_LOG_FUNCNAME FuncName
#else
# define ZEN_LOG_SECTION(Id)
-# define ZEN_DECLARE_FUNCTION
-# define ZEN_LOG_FUNCNAME static_cast<const char*>(__func__)
#endif
namespace zen::logging {
@@ -37,34 +30,29 @@ LoggerRef ErrorLog();
void SetErrorLog(std::string_view LoggerId);
LoggerRef Get(std::string_view Name);
-void ConfigureLogLevels(level::LogLevel Level, std::string_view Loggers);
+void ConfigureLogLevels(LogLevel Level, std::string_view Loggers);
void RefreshLogLevels();
-void RefreshLogLevels(level::LogLevel DefaultLevel);
-
+void RefreshLogLevels(LogLevel DefaultLevel);
+
+/** LogCategory allows for the creation of log categories that can be used with
+ * the logging macros just like a logger reference. The main purpose of this is
+ * to allow for static log categories in global scope where we can't actually
+ * go ahead and instantiate a logger immediately because the logging system may
+ * not be initialized yet.
+ */
struct LogCategory
{
- inline LogCategory(std::string_view InCategory) : CategoryName(InCategory) {}
-
- inline zen::LoggerRef Logger()
- {
- if (LoggerRef)
- {
- return LoggerRef;
- }
+ inline LogCategory(std::string_view InCategory) : m_CategoryName(InCategory) {}
- LoggerRef = zen::logging::Get(CategoryName);
- return LoggerRef;
- }
+ LoggerRef Logger();
- std::string CategoryName;
- zen::LoggerRef LoggerRef;
+private:
+ std::string m_CategoryName;
+ LoggerRef m_LoggerRef;
};
-void EmitConsoleLogMessage(int LogLevel, std::string_view Format, fmt::format_args Args);
-void EmitLogMessage(LoggerRef& Logger, int LogLevel, std::string_view Message);
-void EmitLogMessage(LoggerRef& Logger, const SourceLocation& Location, int LogLevel, std::string_view Message);
-void EmitLogMessage(LoggerRef& Logger, int LogLevel, std::string_view Format, fmt::format_args Args);
-void EmitLogMessage(LoggerRef& Logger, const SourceLocation& Location, int LogLevel, std::string_view Format, fmt::format_args Args);
+void EmitConsoleLogMessage(const LogPoint& Lp, fmt::format_args Args);
+void EmitLogMessage(LoggerRef& Logger, const LogPoint& Lp, fmt::format_args Args);
template<typename... T>
auto
@@ -79,15 +67,14 @@ namespace zen {
extern LoggerRef TheDefaultLogger;
-inline LoggerRef
-Log()
-{
- if (TheDefaultLogger)
- {
- return TheDefaultLogger;
- }
- return zen::logging::ConsoleLog();
-}
+/**
+ * This is the default logger, which any ZEN_INFO et al will get if there's
+ * no Log() function declared in the current scope.
+ *
+ * Typically, classes which want to log to its own channel will declare a Log()
+ * member function which returns a LoggerRef created at construction time.
+ */
+LoggerRef Log();
using logging::ConsoleLog;
using logging::ErrorLog;
@@ -98,12 +85,6 @@ using zen::ConsoleLog;
using zen::ErrorLog;
using zen::Log;
-inline consteval bool
-LogIsErrorLevel(int LogLevel)
-{
- return (LogLevel == zen::logging::level::Err || LogLevel == zen::logging::level::Critical);
-};
-
#if ZEN_BUILD_DEBUG
# define ZEN_CHECK_FORMAT_STRING(fmtstr, ...) \
while (false) \
@@ -117,75 +98,66 @@ LogIsErrorLevel(int LogLevel)
}
#endif
-#define ZEN_LOG_WITH_LOCATION(InLogger, InLevel, fmtstr, ...) \
- do \
- { \
- using namespace std::literals; \
- ZEN_DECLARE_FUNCTION \
- static constinit ZEN_LOG_SECTION(".zlog$s") char FileName[] = __FILE__; \
- static constinit ZEN_LOG_SECTION(".zlog$m") char FormatString[] = fmtstr; \
- static constinit ZEN_LOG_SECTION(".zlog$l") zen::logging::SourceLocation Location{FileName, __LINE__, ZEN_LOG_FUNCNAME}; \
- zen::LoggerRef Logger = InLogger; \
- ZEN_CHECK_FORMAT_STRING(fmtstr##sv, ##__VA_ARGS__); \
- if (Logger.ShouldLog(InLevel)) \
- { \
- zen::logging::EmitLogMessage(Logger, \
- Location, \
- InLevel, \
- std::string_view(FormatString, sizeof FormatString - 1), \
- zen::logging::LogCaptureArguments(__VA_ARGS__)); \
- } \
+#define ZEN_LOG_WITH_LOCATION(InLogger, InLevel, fmtstr, ...) \
+ do \
+ { \
+ using namespace std::literals; \
+ static constinit ZEN_LOG_SECTION(".zlog$l") \
+ zen::logging::LogPoint LogPoint{zen::logging::SourceLocation{__FILE__, __LINE__}, InLevel, std::string_view(fmtstr)}; \
+ zen::LoggerRef Logger = InLogger; \
+ ZEN_CHECK_FORMAT_STRING(fmtstr##sv, ##__VA_ARGS__); \
+ if (Logger.ShouldLog(InLevel)) \
+ { \
+ zen::logging::EmitLogMessage(Logger, LogPoint, zen::logging::LogCaptureArguments(__VA_ARGS__)); \
+ } \
} while (false);
-#define ZEN_LOG(InLogger, InLevel, fmtstr, ...) \
- do \
- { \
- using namespace std::literals; \
- static constinit ZEN_LOG_SECTION(".zlog$m") char FormatString[] = fmtstr; \
- zen::LoggerRef Logger = InLogger; \
- ZEN_CHECK_FORMAT_STRING(fmtstr##sv, ##__VA_ARGS__); \
- if (Logger.ShouldLog(InLevel)) \
- { \
- zen::logging::EmitLogMessage(Logger, \
- InLevel, \
- std::string_view(FormatString, sizeof FormatString - 1), \
- zen::logging::LogCaptureArguments(__VA_ARGS__)); \
- } \
+#define ZEN_LOG(InLogger, InLevel, fmtstr, ...) \
+ do \
+ { \
+ using namespace std::literals; \
+ static constinit ZEN_LOG_SECTION(".zlog$l") zen::logging::LogPoint LogPoint{{}, InLevel, std::string_view(fmtstr)}; \
+ zen::LoggerRef Logger = InLogger; \
+ ZEN_CHECK_FORMAT_STRING(fmtstr##sv, ##__VA_ARGS__); \
+ if (Logger.ShouldLog(InLevel)) \
+ { \
+ zen::logging::EmitLogMessage(Logger, LogPoint, zen::logging::LogCaptureArguments(__VA_ARGS__)); \
+ } \
} while (false);
#define ZEN_DEFINE_LOG_CATEGORY_STATIC(Category, Name) \
static zen::logging::LogCategory Category { Name }
-#define ZEN_LOG_TRACE(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::level::Trace, fmtstr, ##__VA_ARGS__)
-#define ZEN_LOG_DEBUG(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::level::Debug, fmtstr, ##__VA_ARGS__)
-#define ZEN_LOG_INFO(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::level::Info, fmtstr, ##__VA_ARGS__)
-#define ZEN_LOG_WARN(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::level::Warn, fmtstr, ##__VA_ARGS__)
-#define ZEN_LOG_ERROR(Category, fmtstr, ...) ZEN_LOG_WITH_LOCATION(Category.Logger(), zen::logging::level::Err, fmtstr, ##__VA_ARGS__)
-#define ZEN_LOG_CRITICAL(Category, fmtstr, ...) \
- ZEN_LOG_WITH_LOCATION(Category.Logger(), zen::logging::level::Critical, fmtstr, ##__VA_ARGS__)
-
-#define ZEN_TRACE(fmtstr, ...) ZEN_LOG(Log(), zen::logging::level::Trace, fmtstr, ##__VA_ARGS__)
-#define ZEN_DEBUG(fmtstr, ...) ZEN_LOG(Log(), zen::logging::level::Debug, fmtstr, ##__VA_ARGS__)
-#define ZEN_INFO(fmtstr, ...) ZEN_LOG(Log(), zen::logging::level::Info, fmtstr, ##__VA_ARGS__)
-#define ZEN_WARN(fmtstr, ...) ZEN_LOG(Log(), zen::logging::level::Warn, fmtstr, ##__VA_ARGS__)
-#define ZEN_ERROR(fmtstr, ...) ZEN_LOG_WITH_LOCATION(Log(), zen::logging::level::Err, fmtstr, ##__VA_ARGS__)
-#define ZEN_CRITICAL(fmtstr, ...) ZEN_LOG_WITH_LOCATION(Log(), zen::logging::level::Critical, fmtstr, ##__VA_ARGS__)
-
-#define ZEN_CONSOLE_LOG(InLevel, fmtstr, ...) \
- do \
- { \
- using namespace std::literals; \
- ZEN_CHECK_FORMAT_STRING(fmtstr##sv, ##__VA_ARGS__); \
- zen::logging::EmitConsoleLogMessage(InLevel, fmtstr, zen::logging::LogCaptureArguments(__VA_ARGS__)); \
+#define ZEN_LOG_TRACE(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::Trace, fmtstr, ##__VA_ARGS__)
+#define ZEN_LOG_DEBUG(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::Debug, fmtstr, ##__VA_ARGS__)
+#define ZEN_LOG_INFO(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::Info, fmtstr, ##__VA_ARGS__)
+#define ZEN_LOG_WARN(Category, fmtstr, ...) ZEN_LOG(Category.Logger(), zen::logging::Warn, fmtstr, ##__VA_ARGS__)
+#define ZEN_LOG_ERROR(Category, fmtstr, ...) ZEN_LOG_WITH_LOCATION(Category.Logger(), zen::logging::Err, fmtstr, ##__VA_ARGS__)
+#define ZEN_LOG_CRITICAL(Category, fmtstr, ...) ZEN_LOG_WITH_LOCATION(Category.Logger(), zen::logging::Critical, fmtstr, ##__VA_ARGS__)
+
+#define ZEN_TRACE(fmtstr, ...) ZEN_LOG(Log(), zen::logging::Trace, fmtstr, ##__VA_ARGS__)
+#define ZEN_DEBUG(fmtstr, ...) ZEN_LOG(Log(), zen::logging::Debug, fmtstr, ##__VA_ARGS__)
+#define ZEN_INFO(fmtstr, ...) ZEN_LOG(Log(), zen::logging::Info, fmtstr, ##__VA_ARGS__)
+#define ZEN_WARN(fmtstr, ...) ZEN_LOG(Log(), zen::logging::Warn, fmtstr, ##__VA_ARGS__)
+#define ZEN_ERROR(fmtstr, ...) ZEN_LOG_WITH_LOCATION(Log(), zen::logging::Err, fmtstr, ##__VA_ARGS__)
+#define ZEN_CRITICAL(fmtstr, ...) ZEN_LOG_WITH_LOCATION(Log(), zen::logging::Critical, fmtstr, ##__VA_ARGS__)
+
+#define ZEN_CONSOLE_LOG(InLevel, fmtstr, ...) \
+ do \
+ { \
+ using namespace std::literals; \
+ static constinit ZEN_LOG_SECTION(".zlog$l") zen::logging::LogPoint LogPoint{{}, InLevel, std::string_view(fmtstr)}; \
+ ZEN_CHECK_FORMAT_STRING(fmtstr##sv, ##__VA_ARGS__); \
+ zen::logging::EmitConsoleLogMessage(LogPoint, zen::logging::LogCaptureArguments(__VA_ARGS__)); \
} while (false)
-#define ZEN_CONSOLE(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::level::Info, fmtstr, ##__VA_ARGS__)
-#define ZEN_CONSOLE_TRACE(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::level::Trace, fmtstr, ##__VA_ARGS__)
-#define ZEN_CONSOLE_DEBUG(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::level::Debug, fmtstr, ##__VA_ARGS__)
-#define ZEN_CONSOLE_INFO(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::level::Info, fmtstr, ##__VA_ARGS__)
-#define ZEN_CONSOLE_WARN(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::level::Warn, fmtstr, ##__VA_ARGS__)
-#define ZEN_CONSOLE_ERROR(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::level::Err, fmtstr, ##__VA_ARGS__)
-#define ZEN_CONSOLE_CRITICAL(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::level::Critical, fmtstr, ##__VA_ARGS__)
+#define ZEN_CONSOLE(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::Info, fmtstr, ##__VA_ARGS__)
+#define ZEN_CONSOLE_TRACE(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::Trace, fmtstr, ##__VA_ARGS__)
+#define ZEN_CONSOLE_DEBUG(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::Debug, fmtstr, ##__VA_ARGS__)
+#define ZEN_CONSOLE_INFO(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::Info, fmtstr, ##__VA_ARGS__)
+#define ZEN_CONSOLE_WARN(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::Warn, fmtstr, ##__VA_ARGS__)
+#define ZEN_CONSOLE_ERROR(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::Err, fmtstr, ##__VA_ARGS__)
+#define ZEN_CONSOLE_CRITICAL(fmtstr, ...) ZEN_CONSOLE_LOG(zen::logging::Critical, fmtstr, ##__VA_ARGS__)
//////////////////////////////////////////////////////////////////////////
@@ -240,28 +212,28 @@ std::string_view EmitActivitiesForLogging(StringBuilderBase& OutString);
#define ZEN_LOG_SCOPE(...) ScopedLazyActivity $Activity##__LINE__([&](StringBuilderBase& Out) { Out << fmt::format(__VA_ARGS__); })
-#define ZEN_SCOPED_WARN(fmtstr, ...) \
- do \
- { \
- ExtendableStringBuilder<256> ScopeString; \
- const std::string_view Scopes = EmitActivitiesForLogging(ScopeString); \
- ZEN_LOG(Log(), zen::logging::level::Warn, fmtstr "{}", ##__VA_ARGS__, Scopes); \
+#define ZEN_SCOPED_WARN(fmtstr, ...) \
+ do \
+ { \
+ ExtendableStringBuilder<256> ScopeString; \
+ const std::string_view Scopes = EmitActivitiesForLogging(ScopeString); \
+ ZEN_LOG(Log(), zen::logging::Warn, fmtstr "{}", ##__VA_ARGS__, Scopes); \
} while (false)
-#define ZEN_SCOPED_ERROR(fmtstr, ...) \
- do \
- { \
- ExtendableStringBuilder<256> ScopeString; \
- const std::string_view Scopes = EmitActivitiesForLogging(ScopeString); \
- ZEN_LOG_WITH_LOCATION(Log(), zen::logging::level::Err, fmtstr "{}", ##__VA_ARGS__, Scopes); \
+#define ZEN_SCOPED_ERROR(fmtstr, ...) \
+ do \
+ { \
+ ExtendableStringBuilder<256> ScopeString; \
+ const std::string_view Scopes = EmitActivitiesForLogging(ScopeString); \
+ ZEN_LOG_WITH_LOCATION(Log(), zen::logging::Err, fmtstr "{}", ##__VA_ARGS__, Scopes); \
} while (false)
-#define ZEN_SCOPED_CRITICAL(fmtstr, ...) \
- do \
- { \
- ExtendableStringBuilder<256> ScopeString; \
- const std::string_view Scopes = EmitActivitiesForLogging(ScopeString); \
- ZEN_LOG_WITH_LOCATION(Log(), zen::logging::level::Critical, fmtstr "{}", ##__VA_ARGS__, Scopes); \
+#define ZEN_SCOPED_CRITICAL(fmtstr, ...) \
+ do \
+ { \
+ ExtendableStringBuilder<256> ScopeString; \
+ const std::string_view Scopes = EmitActivitiesForLogging(ScopeString); \
+ ZEN_LOG_WITH_LOCATION(Log(), zen::logging::Critical, fmtstr "{}", ##__VA_ARGS__, Scopes); \
} while (false)
ScopedActivityBase* GetThreadActivity();
diff --git a/src/zencore/include/zencore/logging/ansicolorsink.h b/src/zencore/include/zencore/logging/ansicolorsink.h
new file mode 100644
index 000000000..9f859e8d7
--- /dev/null
+++ b/src/zencore/include/zencore/logging/ansicolorsink.h
@@ -0,0 +1,26 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging/sink.h>
+
+#include <memory>
+
+namespace zen::logging {
+
+class AnsiColorStdoutSink : public Sink
+{
+public:
+ AnsiColorStdoutSink();
+ ~AnsiColorStdoutSink() override;
+
+ void Log(const LogMessage& Msg) override;
+ void Flush() override;
+ void SetFormatter(std::unique_ptr<Formatter> InFormatter) override;
+
+private:
+ struct Impl;
+ std::unique_ptr<Impl> m_Impl;
+};
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/asyncsink.h b/src/zencore/include/zencore/logging/asyncsink.h
new file mode 100644
index 000000000..c49a1ccce
--- /dev/null
+++ b/src/zencore/include/zencore/logging/asyncsink.h
@@ -0,0 +1,30 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging/sink.h>
+
+#include <memory>
+#include <vector>
+
+namespace zen::logging {
+
+class AsyncSink : public Sink
+{
+public:
+ explicit AsyncSink(std::vector<SinkPtr> InSinks);
+ ~AsyncSink() override;
+
+ AsyncSink(const AsyncSink&) = delete;
+ AsyncSink& operator=(const AsyncSink&) = delete;
+
+ void Log(const LogMessage& Msg) override;
+ void Flush() override;
+ void SetFormatter(std::unique_ptr<Formatter> InFormatter) override;
+
+private:
+ struct Impl;
+ std::unique_ptr<Impl> m_Impl;
+};
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/formatter.h b/src/zencore/include/zencore/logging/formatter.h
new file mode 100644
index 000000000..11904d71d
--- /dev/null
+++ b/src/zencore/include/zencore/logging/formatter.h
@@ -0,0 +1,20 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging/logmsg.h>
+#include <zencore/logging/memorybuffer.h>
+
+#include <memory>
+
+namespace zen::logging {
+
+class Formatter
+{
+public:
+ virtual ~Formatter() = default;
+ virtual void Format(const LogMessage& Msg, MemoryBuffer& Dest) = 0;
+ virtual std::unique_ptr<Formatter> Clone() const = 0;
+};
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/helpers.h b/src/zencore/include/zencore/logging/helpers.h
new file mode 100644
index 000000000..ce021e1a5
--- /dev/null
+++ b/src/zencore/include/zencore/logging/helpers.h
@@ -0,0 +1,122 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logbase.h>
+#include <zencore/logging/memorybuffer.h>
+
+#include <chrono>
+#include <ctime>
+#include <string_view>
+
+namespace zen::logging::helpers {
+
+inline void
+AppendStringView(std::string_view Sv, MemoryBuffer& Dest)
+{
+ Dest.append(Sv.data(), Sv.data() + Sv.size());
+}
+
+inline void
+AppendInt(int N, MemoryBuffer& Dest)
+{
+ fmt::format_int Formatted(N);
+ Dest.append(Formatted.data(), Formatted.data() + Formatted.size());
+}
+
+inline void
+Pad2(int N, MemoryBuffer& Dest)
+{
+ if (N >= 0 && N < 100)
+ {
+ Dest.push_back(static_cast<char>('0' + N / 10));
+ Dest.push_back(static_cast<char>('0' + N % 10));
+ }
+ else
+ {
+ fmt::format_int Formatted(N);
+ Dest.append(Formatted.data(), Formatted.data() + Formatted.size());
+ }
+}
+
+inline void
+Pad3(uint32_t N, MemoryBuffer& Dest)
+{
+ if (N < 1000)
+ {
+ Dest.push_back(static_cast<char>('0' + N / 100));
+ Dest.push_back(static_cast<char>('0' + (N / 10) % 10));
+ Dest.push_back(static_cast<char>('0' + N % 10));
+ }
+ else
+ {
+ AppendInt(static_cast<int>(N), Dest);
+ }
+}
+
+inline void
+PadUint(size_t N, unsigned int Width, MemoryBuffer& Dest)
+{
+ fmt::format_int Formatted(N);
+ auto StrLen = static_cast<unsigned int>(Formatted.size());
+ if (Width > StrLen)
+ {
+ for (unsigned int Pad = 0; Pad < Width - StrLen; ++Pad)
+ {
+ Dest.push_back('0');
+ }
+ }
+ Dest.append(Formatted.data(), Formatted.data() + Formatted.size());
+}
+
+template<typename ToDuration>
+inline ToDuration
+TimeFraction(std::chrono::system_clock::time_point Tp)
+{
+ using std::chrono::duration_cast;
+ using std::chrono::seconds;
+ auto Duration = Tp.time_since_epoch();
+ auto Secs = duration_cast<seconds>(Duration);
+ return duration_cast<ToDuration>(Duration) - duration_cast<ToDuration>(Secs);
+}
+
+inline std::tm
+SafeLocaltime(std::time_t Time)
+{
+ std::tm Result{};
+#if defined(_WIN32)
+ localtime_s(&Result, &Time);
+#else
+ localtime_r(&Time, &Result);
+#endif
+ return Result;
+}
+
+inline const char*
+ShortFilename(const char* Path)
+{
+ if (Path == nullptr)
+ {
+ return Path;
+ }
+
+ const char* It = Path;
+ const char* LastSep = Path;
+ while (*It)
+ {
+ if (*It == '/' || *It == '\\')
+ {
+ LastSep = It + 1;
+ }
+ ++It;
+ }
+ return LastSep;
+}
+
+inline std::string_view
+LevelToShortString(LogLevel Level)
+{
+ return ToStringView(Level);
+}
+
+} // namespace zen::logging::helpers
diff --git a/src/zencore/include/zencore/logging/logger.h b/src/zencore/include/zencore/logging/logger.h
new file mode 100644
index 000000000..39d1139a5
--- /dev/null
+++ b/src/zencore/include/zencore/logging/logger.h
@@ -0,0 +1,63 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging/sink.h>
+
+#include <atomic>
+#include <memory>
+#include <string_view>
+
+namespace zen::logging {
+
+class ErrorHandler
+{
+public:
+ virtual ~ErrorHandler() = default;
+ virtual void HandleError(const std::string_view& Msg) = 0;
+};
+
+class Logger : public RefCounted
+{
+public:
+ Logger(std::string_view InName, SinkPtr InSink);
+ Logger(std::string_view InName, std::span<const SinkPtr> InSinks);
+ ~Logger();
+
+ Logger(const Logger&) = delete;
+ Logger& operator=(const Logger&) = delete;
+
+ void Log(const LogPoint& Point, fmt::format_args Args);
+
+ bool ShouldLog(LogLevel InLevel) const { return InLevel >= m_Level.load(std::memory_order_relaxed); }
+
+ void SetLevel(LogLevel InLevel) { m_Level.store(InLevel, std::memory_order_relaxed); }
+ LogLevel GetLevel() const { return m_Level.load(std::memory_order_relaxed); }
+
+ void SetFlushLevel(LogLevel InLevel) { m_FlushLevel.store(InLevel, std::memory_order_relaxed); }
+ LogLevel GetFlushLevel() const { return m_FlushLevel.load(std::memory_order_relaxed); }
+
+ std::string_view Name() const;
+
+ void SetSinks(std::vector<SinkPtr> InSinks);
+ void AddSink(SinkPtr InSink);
+
+ void SetFormatter(std::unique_ptr<Formatter> InFormatter);
+
+ void SetErrorHandler(ErrorHandler* Handler);
+
+ void Flush();
+
+ Ref<Logger> Clone(std::string_view NewName) const;
+
+private:
+ void SinkIt(const LogMessage& Msg);
+ void FlushIfNeeded(LogLevel InLevel);
+
+ struct Impl;
+ std::unique_ptr<Impl> m_Impl;
+ std::atomic<LogLevel> m_Level{Info};
+ std::atomic<LogLevel> m_FlushLevel{Off};
+};
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/logmsg.h b/src/zencore/include/zencore/logging/logmsg.h
new file mode 100644
index 000000000..1d8b6b1b7
--- /dev/null
+++ b/src/zencore/include/zencore/logging/logmsg.h
@@ -0,0 +1,66 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logbase.h>
+
+#include <chrono>
+#include <string_view>
+
+namespace zen::logging {
+
+using LogClock = std::chrono::system_clock;
+
+struct LogMessage
+{
+ LogMessage() = default;
+
+ LogMessage(const LogPoint& InPoint, std::string_view InLoggerName, std::string_view InPayload)
+ : m_LoggerName(InLoggerName)
+ , m_Level(InPoint.Level)
+ , m_Time(LogClock::now())
+ , m_Source(InPoint.Location)
+ , m_Payload(InPayload)
+ , m_Point(&InPoint)
+ {
+ }
+
+ std::string_view GetPayload() const { return m_Payload; }
+ int GetThreadId() const { return m_ThreadId; }
+ LogClock::time_point GetTime() const { return m_Time; }
+ LogLevel GetLevel() const { return m_Level; }
+ std::string_view GetLoggerName() const { return m_LoggerName; }
+ const SourceLocation& GetSource() const { return m_Source; }
+ const LogPoint& GetLogPoint() const { return *m_Point; }
+
+ void SetThreadId(int InThreadId) { m_ThreadId = InThreadId; }
+ void SetPayload(std::string_view InPayload) { m_Payload = InPayload; }
+ void SetLoggerName(std::string_view InName) { m_LoggerName = InName; }
+ void SetLevel(LogLevel InLevel) { m_Level = InLevel; }
+ void SetTime(LogClock::time_point InTime) { m_Time = InTime; }
+ void SetSource(const SourceLocation& InSource) { m_Source = InSource; }
+
+ mutable size_t ColorRangeStart = 0;
+ mutable size_t ColorRangeEnd = 0;
+
+private:
+ static constexpr LogPoint s_DefaultPoints[LogLevelCount] = {
+ {{}, Trace, {}},
+ {{}, Debug, {}},
+ {{}, Info, {}},
+ {{}, Warn, {}},
+ {{}, Err, {}},
+ {{}, Critical, {}},
+ {{}, Off, {}},
+ };
+
+ std::string_view m_LoggerName;
+ LogLevel m_Level = Off;
+ std::chrono::system_clock::time_point m_Time;
+ SourceLocation m_Source;
+ std::string_view m_Payload;
+ const LogPoint* m_Point = &s_DefaultPoints[Off];
+ int m_ThreadId = 0;
+};
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/memorybuffer.h b/src/zencore/include/zencore/logging/memorybuffer.h
new file mode 100644
index 000000000..cd0ff324f
--- /dev/null
+++ b/src/zencore/include/zencore/logging/memorybuffer.h
@@ -0,0 +1,11 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <fmt/format.h>
+
+namespace zen::logging {
+
+using MemoryBuffer = fmt::basic_memory_buffer<char, 250>;
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/messageonlyformatter.h b/src/zencore/include/zencore/logging/messageonlyformatter.h
new file mode 100644
index 000000000..ce25fe9a6
--- /dev/null
+++ b/src/zencore/include/zencore/logging/messageonlyformatter.h
@@ -0,0 +1,22 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging/formatter.h>
+#include <zencore/logging/helpers.h>
+
+namespace zen::logging {
+
+class MessageOnlyFormatter : public Formatter
+{
+public:
+ void Format(const LogMessage& Msg, MemoryBuffer& Dest) override
+ {
+ helpers::AppendStringView(Msg.GetPayload(), Dest);
+ Dest.push_back('\n');
+ }
+
+ std::unique_ptr<Formatter> Clone() const override { return std::make_unique<MessageOnlyFormatter>(); }
+};
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/msvcsink.h b/src/zencore/include/zencore/logging/msvcsink.h
new file mode 100644
index 000000000..48ea1b915
--- /dev/null
+++ b/src/zencore/include/zencore/logging/msvcsink.h
@@ -0,0 +1,30 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging/sink.h>
+
+#if ZEN_PLATFORM_WINDOWS
+
+# include <mutex>
+
+namespace zen::logging {
+
+class MsvcSink : public Sink
+{
+public:
+ MsvcSink();
+ ~MsvcSink() override = default;
+
+ void Log(const LogMessage& Msg) override;
+ void Flush() override;
+ void SetFormatter(std::unique_ptr<Formatter> InFormatter) override;
+
+private:
+ std::mutex m_Mutex;
+ std::unique_ptr<Formatter> m_Formatter;
+};
+
+} // namespace zen::logging
+
+#endif // ZEN_PLATFORM_WINDOWS
diff --git a/src/zencore/include/zencore/logging/nullsink.h b/src/zencore/include/zencore/logging/nullsink.h
new file mode 100644
index 000000000..7ac5677c6
--- /dev/null
+++ b/src/zencore/include/zencore/logging/nullsink.h
@@ -0,0 +1,17 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging/sink.h>
+
+namespace zen::logging {
+
+class NullSink : public Sink
+{
+public:
+ void Log(const LogMessage& /*Msg*/) override {}
+ void Flush() override {}
+ void SetFormatter(std::unique_ptr<Formatter> /*InFormatter*/) override {}
+};
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/registry.h b/src/zencore/include/zencore/logging/registry.h
new file mode 100644
index 000000000..a4d3692d2
--- /dev/null
+++ b/src/zencore/include/zencore/logging/registry.h
@@ -0,0 +1,70 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging/logger.h>
+
+#include <chrono>
+#include <memory>
+#include <span>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+namespace zen::logging {
+
+class Registry
+{
+public:
+ using LogLevels = std::span<const std::pair<std::string, LogLevel>>;
+
+ static Registry& Instance();
+ void Shutdown();
+
+ void Register(Ref<Logger> InLogger);
+ void Drop(const std::string& Name);
+ Ref<Logger> Get(const std::string& Name);
+
+ void SetDefaultLogger(Ref<Logger> InLogger);
+ Logger* DefaultLoggerRaw();
+ Ref<Logger> DefaultLogger();
+
+ void SetGlobalLevel(LogLevel Level);
+ LogLevel GetGlobalLevel() const;
+ void SetLevels(LogLevels Levels, LogLevel* DefaultLevel);
+
+ void FlushAll();
+ void FlushOn(LogLevel Level);
+ void FlushEvery(std::chrono::seconds Interval);
+
+ // Change formatter on all registered loggers
+ void SetFormatter(std::unique_ptr<Formatter> InFormatter);
+
+ // Apply function to all registered loggers. Note that the function will
+ // be called while the registry mutex is held, so it should be fast and
+ // not attempt to call back into the registry.
+ template<typename Func>
+ void ApplyAll(Func&& F)
+ {
+ ApplyAllImpl([](void* Ctx, Ref<Logger> L) { (*static_cast<std::decay_t<Func>*>(Ctx))(std::move(L)); }, &F);
+ }
+
+ // Set error handler for all loggers in the registry. The handler is called
+ // if any logger encounters an error during logging or flushing.
+ // The caller must ensure the handler outlives the registry.
+ void SetErrorHandler(ErrorHandler* Handler);
+
+private:
+ void ApplyAllImpl(void (*Func)(void*, Ref<Logger>), void* Context);
+
+ Registry();
+ ~Registry();
+
+ Registry(const Registry&) = delete;
+ Registry& operator=(const Registry&) = delete;
+
+ struct Impl;
+ std::unique_ptr<Impl> m_Impl;
+};
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/sink.h b/src/zencore/include/zencore/logging/sink.h
new file mode 100644
index 000000000..172176a4e
--- /dev/null
+++ b/src/zencore/include/zencore/logging/sink.h
@@ -0,0 +1,34 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zenbase/refcount.h>
+#include <zencore/logging/formatter.h>
+#include <zencore/logging/logmsg.h>
+
+#include <atomic>
+#include <memory>
+
+namespace zen::logging {
+
+class Sink : public RefCounted
+{
+public:
+ virtual ~Sink() = default;
+
+ virtual void Log(const LogMessage& Msg) = 0;
+ virtual void Flush() = 0;
+
+ virtual void SetFormatter(std::unique_ptr<Formatter> InFormatter) = 0;
+
+ bool ShouldLog(LogLevel InLevel) const { return InLevel >= m_Level.load(std::memory_order_relaxed); }
+ void SetLevel(LogLevel InLevel) { m_Level.store(InLevel, std::memory_order_relaxed); }
+ LogLevel GetLevel() const { return m_Level.load(std::memory_order_relaxed); }
+
+protected:
+ std::atomic<LogLevel> m_Level{Trace};
+};
+
+using SinkPtr = Ref<Sink>;
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/logging/tracesink.h b/src/zencore/include/zencore/logging/tracesink.h
new file mode 100644
index 000000000..e63d838b4
--- /dev/null
+++ b/src/zencore/include/zencore/logging/tracesink.h
@@ -0,0 +1,23 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/logging/sink.h>
+
+namespace zen::logging {
+
+/**
+ * A logging sink that forwards log messages to the trace system.
+ *
+ * Work-in-progress, not fully implemented.
+ */
+
+class TraceSink : public Sink
+{
+public:
+ void Log(const LogMessage& Msg) override;
+ void Flush() override;
+ void SetFormatter(std::unique_ptr<Formatter> InFormatter) override;
+};
+
+} // namespace zen::logging
diff --git a/src/zencore/include/zencore/sentryintegration.h b/src/zencore/include/zencore/sentryintegration.h
index faf1238b7..a4e33d69e 100644
--- a/src/zencore/include/zencore/sentryintegration.h
+++ b/src/zencore/include/zencore/sentryintegration.h
@@ -11,11 +11,9 @@
#if ZEN_USE_SENTRY
-# include <memory>
+# include <zencore/logging/logger.h>
-ZEN_THIRD_PARTY_INCLUDES_START
-# include <spdlog/logger.h>
-ZEN_THIRD_PARTY_INCLUDES_END
+# include <memory>
namespace sentry {
@@ -53,7 +51,7 @@ private:
std::string m_SentryUserName;
std::string m_SentryHostName;
std::string m_SentryId;
- std::shared_ptr<spdlog::logger> m_SentryLogger;
+ Ref<logging::Logger> m_SentryLogger;
};
} // namespace zen