// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include #include namespace zen::logging { using namespace std::literals; static void WriteEscapedString(MemoryBuffer& Dest, std::string_view Text) { // Strip ANSI SGR sequences before escaping so they don't appear in JSON output static const auto IsEscapeStart = [](char C) { return C == '\033'; }; const char* RangeStart = Text.data(); const char* End = Text.data() + Text.size(); static const std::unordered_map SpecialCharacterMap{ {'\b', "\\b"sv}, {'\f', "\\f"sv}, {'\n', "\\n"sv}, {'\r', "\\r"sv}, {'\t', "\\t"sv}, {'"', "\\\""sv}, {'\\', "\\\\"sv}, }; for (const char* It = RangeStart; It != End; ++It) { // Skip ANSI SGR escape sequences (\033[...m) if (*It == '\033' && (It + 1) < End && *(It + 1) == '[') { if (RangeStart != It) { Dest.append(RangeStart, It); } const char* Seq = It + 2; while (Seq < End && *Seq != 'm') { ++Seq; } if (Seq < End) { ++Seq; // skip 'm' } It = Seq - 1; // -1 because the for loop will ++It RangeStart = Seq; continue; } if (auto SpecialIt = SpecialCharacterMap.find(*It); SpecialIt != SpecialCharacterMap.end()) { if (RangeStart != It) { Dest.append(RangeStart, It); } helpers::AppendStringView(SpecialIt->second, Dest); RangeStart = It + 1; } } if (RangeStart != End) { Dest.append(RangeStart, End); } } struct JsonFormatter::Impl { explicit Impl(std::string_view LogId) : m_LogId(LogId) {} std::tm m_CachedTm{0, 0, 0, 0, 0, 0, 0, 0, 0}; std::chrono::seconds m_LastLogSecs{0}; MemoryBuffer m_CachedDatetime; std::string m_LogId; RwLock m_TimestampLock; }; JsonFormatter::JsonFormatter(std::string_view LogId) : m_Impl(std::make_unique(LogId)) { } JsonFormatter::~JsonFormatter() = default; std::unique_ptr JsonFormatter::Clone() const { return std::make_unique(m_Impl->m_LogId); } void JsonFormatter::Format(const LogMessage& Msg, MemoryBuffer& Dest) { ZEN_MEMSCOPE(ELLMTag::Logging); using std::chrono::duration_cast; using std::chrono::milliseconds; using std::chrono::seconds; auto Secs = duration_cast(Msg.GetTime().time_since_epoch()); if (Secs != m_Impl->m_LastLogSecs) { RwLock::ExclusiveLockScope _(m_Impl->m_TimestampLock); m_Impl->m_CachedTm = helpers::SafeLocaltime(LogClock::to_time_t(Msg.GetTime())); m_Impl->m_LastLogSecs = Secs; // cache the date/time part for the next second. m_Impl->m_CachedDatetime.clear(); helpers::AppendInt(m_Impl->m_CachedTm.tm_year + 1900, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back('-'); helpers::Pad2(m_Impl->m_CachedTm.tm_mon + 1, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back('-'); helpers::Pad2(m_Impl->m_CachedTm.tm_mday, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back(' '); helpers::Pad2(m_Impl->m_CachedTm.tm_hour, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back(':'); helpers::Pad2(m_Impl->m_CachedTm.tm_min, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back(':'); helpers::Pad2(m_Impl->m_CachedTm.tm_sec, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back('.'); } helpers::AppendStringView("{"sv, Dest); helpers::AppendStringView("\"time\": \""sv, Dest); { RwLock::SharedLockScope _(m_Impl->m_TimestampLock); Dest.append(m_Impl->m_CachedDatetime.begin(), m_Impl->m_CachedDatetime.end()); } auto Millis = helpers::TimeFraction(Msg.GetTime()); helpers::Pad3(static_cast(Millis.count()), Dest); helpers::AppendStringView("\", "sv, Dest); helpers::AppendStringView("\"status\": \""sv, Dest); helpers::AppendStringView(helpers::LevelToShortString(Msg.GetLevel()), Dest); helpers::AppendStringView("\", "sv, Dest); helpers::AppendStringView("\"source\": \""sv, Dest); helpers::AppendStringView("zenserver"sv, Dest); helpers::AppendStringView("\", "sv, Dest); helpers::AppendStringView("\"service\": \""sv, Dest); helpers::AppendStringView("zencache"sv, Dest); helpers::AppendStringView("\", "sv, Dest); if (!m_Impl->m_LogId.empty()) { helpers::AppendStringView("\"id\": \""sv, Dest); helpers::AppendStringView(m_Impl->m_LogId, Dest); helpers::AppendStringView("\", "sv, Dest); } if (Msg.GetLoggerName().size() > 0) { helpers::AppendStringView("\"logger.name\": \""sv, Dest); helpers::AppendStringView(Msg.GetLoggerName(), Dest); helpers::AppendStringView("\", "sv, Dest); } if (Msg.GetThreadId() != 0) { helpers::AppendStringView("\"logger.thread_name\": \""sv, Dest); helpers::PadUint(Msg.GetThreadId(), 0, Dest); helpers::AppendStringView("\", "sv, Dest); } if (Msg.GetSource()) { helpers::AppendStringView("\"file\": \""sv, Dest); WriteEscapedString(Dest, helpers::ShortFilename(Msg.GetSource().Filename)); helpers::AppendStringView("\","sv, Dest); helpers::AppendStringView("\"line\": \""sv, Dest); helpers::AppendInt(Msg.GetSource().Line, Dest); helpers::AppendStringView("\","sv, Dest); } helpers::AppendStringView("\"message\": \""sv, Dest); WriteEscapedString(Dest, Msg.GetPayload()); helpers::AppendStringView("\""sv, Dest); helpers::AppendStringView("}\n"sv, Dest); } } // namespace zen::logging