// 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 = std::make_shared(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(); auto& LoggerSinks = DefaultLogger.m_Logger->Sinks(); LoggerSinks.clear(); // Collect sinks into a local vector first so we can optionally wrap them std::vector Sinks; if (LogOptions.NoConsoleOutput) { zen::logging::SuppressConsoleLog(); } else { auto ConsoleSink = std::make_shared(); 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) { auto DebugSink = std::make_shared(); DebugSink->SetLevel(logging::Debug); Sinks.push_back(DebugSink); } #endif bool IsAsync = LogOptions.AllowAsync && !LogOptions.IsDebug && !LogOptions.IsTest; if (IsAsync) { auto AsyncWrapper = std::make_shared(std::move(Sinks)); LoggerSinks.push_back(std::move(AsyncWrapper)); } else { LoggerSinks = std::move(Sinks); } logging::Registry::Instance().SetErrorHandler([](const std::string& ErrorMsg) { if (ErrorMsg == std::bad_alloc().what()) { return; } if (auto ErrLogger = zen::logging::ErrorLog()) { try { ErrLogger.m_Logger->Log(logging::Err, ErrorMsg); } catch (const std::exception&) { } } try { Log().m_Logger->Log(logging::Err, ErrorMsg); } catch (const std::exception&) { } }); 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(); logging::Registry::Instance().ApplyAll( [&](auto Logger) { Logger->Log(logging::Info, fmt::format("log starting at {}", 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.reset(); } } // namespace zen