// Copyright Epic Games, Inc. All Rights Reserved. #include "zenutil/logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zen { static bool g_IsLoggingInitialized; logging::SinkPtr g_FileSink; logging::SinkPtr GetFileSink() { return g_FileSink; } void InitializeLogging(const LoggingOptions& LogOptions) { BeginInitializeLogging(LogOptions); FinishInitializeLogging(LogOptions); } static std::terminate_handler OldTerminateHandler = nullptr; void BeginInitializeLogging(const LoggingOptions& LogOptions) { ZEN_MEMSCOPE(ELLMTag::Logging); zen::logging::InitializeLogging(); zen::logging::EnableVTMode(); // Sinks logging::SinkPtr FileSink; if (!LogOptions.AbsLogFile.empty()) { if (LogOptions.AbsLogFile.has_parent_path()) { zen::CreateDirectories(LogOptions.AbsLogFile.parent_path()); } FileSink = logging::SinkPtr(new zen::logging::RotatingFileSink(LogOptions.AbsLogFile, /* max size */ 128 * 1024 * 1024, /* max files */ 16, /* rotate on open */ true)); if (LogOptions.AbsLogFile.extension() == ".json") { FileSink->SetFormatter(std::make_unique(LogOptions.LogId)); } else { FileSink->SetFormatter(std::make_unique(LogOptions.LogId)); // this will have a date prefix } } OldTerminateHandler = std::set_terminate([]() { try { constexpr int SkipFrameCount = 4; constexpr int FrameCount = 8; uint8_t CallstackBuffer[CallstackRawMemorySize(SkipFrameCount, FrameCount)]; CallstackFrames* Callstack = GetCallstackRaw(&CallstackBuffer[0], SkipFrameCount, FrameCount); fmt::basic_memory_buffer Message; auto Appender = fmt::appender(Message); fmt::format_to(Appender, "Program exited abnormally via std::terminate()"); if (Callstack->FrameCount > 0) { fmt::format_to(Appender, "\n"); CallstackToStringRaw(Callstack, &Message, [](void* UserData, uint32_t FrameIndex, const char* FrameText) { ZEN_UNUSED(FrameIndex); fmt::basic_memory_buffer* Message = (fmt::basic_memory_buffer*)UserData; auto Appender = fmt::appender(*Message); fmt::format_to(Appender, " {}\n", FrameText); }); } Message.push_back('\0'); // We use direct ZEN_LOG here instead of ZEN_ERROR as we don't care about *this* code location in the log ZEN_LOG(Log(), zen::logging::Critical, "{}", Message.data()); zen::logging::FlushLogging(); } catch (const std::exception&) { // Ignore any exceptions in terminate callback } if (OldTerminateHandler) { OldTerminateHandler(); } }); // Default LoggerRef DefaultLogger = zen::logging::Default(); // Collect sinks into a local vector first so we can optionally wrap them std::vector Sinks; if (LogOptions.NoConsoleOutput) { zen::logging::SuppressConsoleLog(); } else { logging::SinkPtr ConsoleSink(new logging::AnsiColorStdoutSink()); if (LogOptions.QuietConsole) { ConsoleSink->SetLevel(logging::Warn); } Sinks.push_back(ConsoleSink); } if (FileSink) { Sinks.push_back(FileSink); } #if ZEN_PLATFORM_WINDOWS if (zen::IsDebuggerPresent() && LogOptions.IsDebug) { logging::SinkPtr DebugSink(new logging::MsvcSink()); DebugSink->SetLevel(logging::Debug); Sinks.push_back(DebugSink); } #endif bool IsAsync = LogOptions.AllowAsync && !LogOptions.IsDebug && !LogOptions.IsTest; if (IsAsync) { std::vector AsyncSinks; AsyncSinks.emplace_back(new logging::AsyncSink(std::move(Sinks))); DefaultLogger->SetSinks(std::move(AsyncSinks)); } else { DefaultLogger->SetSinks(std::move(Sinks)); } static struct : logging::ErrorHandler { void HandleError(const std::string_view& ErrorMsg) override { if (ErrorMsg == std::bad_alloc().what()) { return; } static constinit logging::LogPoint ErrorPoint{{}, logging::Err, "{}"}; if (auto ErrLogger = zen::logging::ErrorLog()) { try { ErrLogger->Log(ErrorPoint, fmt::make_format_args(ErrorMsg)); } catch (const std::exception&) { } } try { Log()->Log(ErrorPoint, fmt::make_format_args(ErrorMsg)); } catch (const std::exception&) { } } } s_ErrorHandler; logging::Registry::Instance().SetErrorHandler(&s_ErrorHandler); g_FileSink = std::move(FileSink); } void FinishInitializeLogging(const LoggingOptions& LogOptions) { ZEN_MEMSCOPE(ELLMTag::Logging); logging::LogLevel LogLevel = logging::Info; if (LogOptions.IsDebug) { LogLevel = logging::Debug; } if (LogOptions.IsTest || LogOptions.IsVerbose) { LogLevel = logging::Trace; } // Configure all registered loggers according to settings logging::RefreshLogLevels(LogLevel); logging::Registry::Instance().FlushOn(logging::Err); logging::Registry::Instance().FlushEvery(std::chrono::seconds{2}); logging::Registry::Instance().SetFormatter(std::make_unique( LogOptions.LogId, std::chrono::system_clock::now() - std::chrono::milliseconds(GetTimeSinceProcessStart()))); // default to duration prefix // If the console logger was initialized before, the above will change the output format // so we need to reset it logging::ResetConsoleLog(); if (g_FileSink) { if (LogOptions.AbsLogFile.extension() == ".json") { g_FileSink->SetFormatter(std::make_unique(LogOptions.LogId)); } else { g_FileSink->SetFormatter(std::make_unique(LogOptions.LogId)); // this will have a date prefix } const std::string StartLogTime = zen::DateTime::Now().ToIso8601(); static constinit logging::LogPoint LogStartPoint{{}, logging::Info, "log starting at {}"}; logging::Registry::Instance().ApplyAll([&](auto Logger) { Logger->Log(LogStartPoint, fmt::make_format_args(StartLogTime)); }); } g_IsLoggingInitialized = true; } void ShutdownLogging() { if (g_IsLoggingInitialized && g_FileSink) { auto DefaultLogger = zen::logging::Default(); ZEN_LOG_INFO(DefaultLogger, "log ending at {}", zen::DateTime::Now().ToIso8601()); } zen::logging::ShutdownLogging(); g_FileSink = nullptr; } } // namespace zen