// Copyright Epic Games, Inc. All Rights Reserved. #include "zencore/logging.h" #include #include #include ZEN_THIRD_PARTY_INCLUDES_START #include #include #include #include ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_PLATFORM_WINDOWS # pragma section(".zlog$a", read) # pragma section(".zlog$f", read) # pragma section(".zlog$m", read) # pragma section(".zlog$s", read) # pragma section(".zlog$z", read) #endif namespace zen { // We shadow the underlying spdlog default logger, in order to avoid a bunch of overhead LoggerRef TheDefaultLogger; } // namespace zen namespace zen::logging { using MemoryBuffer_t = fmt::basic_memory_buffer; struct LoggingContext { inline LoggingContext(); inline ~LoggingContext(); zen::logging::MemoryBuffer_t MessageBuffer; inline std::string_view Message() const { return std::string_view(MessageBuffer.data(), MessageBuffer.size()); } }; LoggingContext::LoggingContext() { } LoggingContext::~LoggingContext() { } ////////////////////////////////////////////////////////////////////////// static inline bool IsErrorLevel(int LogLevel) { return (LogLevel == zen::logging::level::Err || LogLevel == zen::logging::level::Critical); }; static_assert(sizeof(spdlog::source_loc) == sizeof(SourceLocation)); static_assert(offsetof(spdlog::source_loc, filename) == offsetof(SourceLocation, filename)); static_assert(offsetof(spdlog::source_loc, line) == offsetof(SourceLocation, line)); static_assert(offsetof(spdlog::source_loc, funcname) == offsetof(SourceLocation, funcname)); void EmitLogMessage(LoggerRef& Logger, int LogLevel, const std::string_view Message) { const spdlog::level::level_enum InLevel = (spdlog::level::level_enum)LogLevel; Logger.SpdLogger->log(InLevel, Message); if (IsErrorLevel(LogLevel)) { if (LoggerRef ErrLogger = zen::logging::ErrorLog()) { ErrLogger.SpdLogger->log(InLevel, Message); } } } void EmitLogMessage(LoggerRef& Logger, int LogLevel, std::string_view Format, fmt::format_args Args) { zen::logging::LoggingContext LogCtx; fmt::vformat_to(fmt::appender(LogCtx.MessageBuffer), Format, Args); zen::logging::EmitLogMessage(Logger, LogLevel, LogCtx.Message()); } void EmitLogMessage(LoggerRef& Logger, const SourceLocation& InLocation, int LogLevel, const std::string_view Message) { const spdlog::source_loc& Location = *reinterpret_cast(&InLocation); const spdlog::level::level_enum InLevel = (spdlog::level::level_enum)LogLevel; Logger.SpdLogger->log(Location, InLevel, Message); if (IsErrorLevel(LogLevel)) { if (LoggerRef ErrLogger = zen::logging::ErrorLog()) { ErrLogger.SpdLogger->log(Location, InLevel, Message); } } } void EmitLogMessage(LoggerRef& Logger, const SourceLocation& InLocation, int LogLevel, std::string_view Format, fmt::format_args Args) { zen::logging::LoggingContext LogCtx; fmt::vformat_to(fmt::appender(LogCtx.MessageBuffer), Format, Args); zen::logging::EmitLogMessage(Logger, InLocation, LogLevel, LogCtx.Message()); } void EmitConsoleLogMessage(int LogLevel, const std::string_view Message) { const spdlog::level::level_enum InLevel = (spdlog::level::level_enum)LogLevel; ConsoleLog().SpdLogger->log(InLevel, Message); } void EmitConsoleLogMessage(int LogLevel, std::string_view Format, fmt::format_args Args) { zen::logging::LoggingContext LogCtx; fmt::vformat_to(fmt::appender(LogCtx.MessageBuffer), Format, Args); zen::logging::EmitConsoleLogMessage(LogLevel, LogCtx.Message()); } } // namespace zen::logging namespace zen::logging::level { spdlog::level::level_enum to_spdlog_level(LogLevel NewLogLevel) { return static_cast((int)NewLogLevel); } LogLevel to_logging_level(spdlog::level::level_enum NewLogLevel) { return static_cast((int)NewLogLevel); } constinit std::string_view LevelNames[] = {ZEN_LEVEL_NAME_TRACE, ZEN_LEVEL_NAME_DEBUG, ZEN_LEVEL_NAME_INFO, ZEN_LEVEL_NAME_WARNING, ZEN_LEVEL_NAME_ERROR, ZEN_LEVEL_NAME_CRITICAL, ZEN_LEVEL_NAME_OFF}; level::LogLevel ParseLogLevelString(std::string_view Name) { for (int Level = 0; Level < level::LogLevelCount; ++Level) { if (LevelNames[Level] == Name) return static_cast(Level); } if (Name == "warn") { return level::Warn; } if (Name == "err") { return level::Err; } return level::Off; } std::string_view ToStringView(level::LogLevel Level) { if (int(Level) < level::LogLevelCount) { return LevelNames[int(Level)]; } return "None"; } } // namespace zen::logging::level ////////////////////////////////////////////////////////////////////////// namespace zen::logging { RwLock LogLevelsLock; std::string LogLevels[level::LogLevelCount]; void ConfigureLogLevels(level::LogLevel Level, std::string_view Loggers) { RwLock::ExclusiveLockScope _(LogLevelsLock); LogLevels[Level] = Loggers; } void RefreshLogLevels(level::LogLevel* DefaultLevel) { spdlog::details::registry::log_levels Levels; { RwLock::SharedLockScope _(LogLevelsLock); for (int i = 0; i < level::LogLevelCount; ++i) { level::LogLevel CurrentLevel{i}; std::string_view Spec = LogLevels[i]; while (!Spec.empty()) { std::string LoggerName; if (auto CommaPos = Spec.find_first_of(','); CommaPos != std::string_view::npos) { LoggerName = Spec.substr(CommaPos + 1); Spec.remove_prefix(CommaPos + 1); } else { LoggerName = Spec; Spec = {}; } Levels[LoggerName] = to_spdlog_level(CurrentLevel); } } } if (DefaultLevel) { spdlog::level::level_enum SpdDefaultLevel = to_spdlog_level(*DefaultLevel); spdlog::details::registry::instance().set_levels(Levels, &SpdDefaultLevel); } else { spdlog::details::registry::instance().set_levels(Levels, nullptr); } } void RefreshLogLevels(level::LogLevel DefaultLevel) { RefreshLogLevels(&DefaultLevel); } void RefreshLogLevels() { RefreshLogLevels(nullptr); } void SetLogLevel(level::LogLevel NewLogLevel) { spdlog::set_level(to_spdlog_level(NewLogLevel)); } level::LogLevel GetLogLevel() { return level::to_logging_level(spdlog::get_level()); } LoggerRef Default() { ZEN_ASSERT(TheDefaultLogger); return TheDefaultLogger; } void SetDefault(std::string_view NewDefaultLoggerId) { auto NewDefaultLogger = spdlog::get(std::string(NewDefaultLoggerId)); ZEN_ASSERT(NewDefaultLogger); spdlog::set_default_logger(NewDefaultLogger); TheDefaultLogger = LoggerRef(*NewDefaultLogger); } LoggerRef TheErrorLogger; LoggerRef ErrorLog() { return TheErrorLogger; } void SetErrorLog(std::string_view NewErrorLoggerId) { if (NewErrorLoggerId.empty()) { TheErrorLogger = {}; } else { auto NewErrorLogger = spdlog::get(std::string(NewErrorLoggerId)); ZEN_ASSERT(NewErrorLogger); TheErrorLogger = LoggerRef(*NewErrorLogger.get()); } } LoggerRef Get(std::string_view Name) { std::shared_ptr Logger = spdlog::get(std::string(Name)); if (!Logger) { Logger = Default().SpdLogger->clone(std::string(Name)); spdlog::apply_logger_env_levels(Logger); spdlog::register_logger(Logger); } return *Logger; } std::once_flag ConsoleInitFlag; std::shared_ptr ConLogger; void SuppressConsoleLog() { if (ConLogger) { spdlog::drop("console"); ConLogger = {}; } ConLogger = spdlog::null_logger_mt("console"); } LoggerRef ConsoleLog() { std::call_once(ConsoleInitFlag, [&] { if (!ConLogger) { ConLogger = spdlog::stdout_color_mt("console"); spdlog::apply_logger_env_levels(ConLogger); ConLogger->set_pattern("%v"); } }); return *ConLogger; } void InitializeLogging() { TheDefaultLogger = *spdlog::default_logger_raw(); } void ShutdownLogging() { spdlog::shutdown(); TheDefaultLogger = {}; } bool EnableVTMode() { #if ZEN_PLATFORM_WINDOWS // Set output mode to handle virtual terminal sequences HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut == INVALID_HANDLE_VALUE) { return false; } DWORD dwMode = 0; if (!GetConsoleMode(hOut, &dwMode)) { return false; } dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; if (!SetConsoleMode(hOut, dwMode)) { return false; } #endif return true; } void FlushLogging() { spdlog::details::registry::instance().flush_all(); } } // namespace zen::logging namespace zen { bool LoggerRef::ShouldLog(int Level) const { return SpdLogger->should_log(static_cast(Level)); } void LoggerRef::SetLogLevel(logging::level::LogLevel NewLogLevel) { SpdLogger->set_level(to_spdlog_level(NewLogLevel)); } logging::level::LogLevel LoggerRef::GetLogLevel() { return logging::level::to_logging_level(SpdLogger->level()); } thread_local ScopedActivityBase* t_ScopeStack = nullptr; ScopedActivityBase* GetThreadActivity() { return t_ScopeStack; } ScopedActivityBase::ScopedActivityBase() : m_NextScope{t_ScopeStack} { t_ScopeStack = this; } ScopedActivityBase::~ScopedActivityBase() { if (t_ScopeStack != this) { ZEN_ERROR("invalid t_ScopeStack in ~ScopedActivityBase(). Expected {:#x}, found {:#x}", (uintptr_t)this, (uintptr_t)t_ScopeStack); return; } t_ScopeStack = m_NextScope; } std::string_view EmitActivitiesForLogging(StringBuilderBase& OutString) { OutString.Reset(); for (auto Bread = GetThreadActivity(); Bread; Bread = Bread->GetNext()) { OutString.Append("\n|>|>|> "); Bread->Emit(OutString); } return OutString.ToView(); } ////////////////////////////////////////////////////////////////////////// ScopedActivityString::~ScopedActivityString() { } void ScopedActivityString::Emit(StringBuilderBase& Target) { Target.Append(m_Text); } #if ZEN_WITH_TESTS void logging_forcelink() { } using namespace std::literals; TEST_CASE("simple.bread") { ExtendableStringBuilder<256> Crumbs; auto EmitBreadcrumbs = [&] { Crumbs.Reset(); for (auto Bread = GetThreadActivity(); Bread; Bread = Bread->GetNext()) { Bread->Emit(Crumbs); } return Crumbs.ToView(); }; SUBCASE("single") { ScopedActivityString _{"hello"}; EmitBreadcrumbs(); CHECK_EQ(Crumbs.ToView(), "hello"sv); } SUBCASE("multi") { ScopedActivityString $1{"hello"}; ScopedActivityString $2{"world"}; ScopedActivityString $3{"amaze"}; CHECK_EQ(EmitBreadcrumbs(), "amazeworldhello"sv); } SUBCASE("multi_defer") { int n = 42; ScopedActivityString $1{"hello"}; ScopedActivityString $2{"world"}; ScopedActivityString $3{"amaze"}; ScopedLazyActivity $4{[&](StringBuilderBase& Out) { Out << "plant"; }}; { ScopedLazyActivity $5{[&](StringBuilderBase& Out) { Out << n; }}; ZEN_LOG_SCOPE("{}:{}", "abc", "def"); CHECK_EQ(EmitBreadcrumbs(), "abc:def42plantamazeworldhello"sv); } CHECK_EQ(EmitBreadcrumbs(), "plantamazeworldhello"sv); } } #endif } // namespace zen