// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include #include #include #include namespace zen::logging { struct FullFormatter::Impl { Impl(std::string_view LogId, std::chrono::time_point Epoch) : m_Epoch(Epoch) , m_LogId(LogId) , m_LinePrefix(128, ' ') , m_UseFullDate(false) { } explicit Impl(std::string_view LogId) : m_LogId(LogId), m_LinePrefix(128, ' '), m_UseFullDate(true) {} std::chrono::time_point m_Epoch; std::tm m_CachedLocalTm{}; std::chrono::seconds m_LastLogSecs{std::chrono::seconds(87654321)}; std::atomic m_CacheTimestamp{std::chrono::seconds(87654321)}; MemoryBuffer m_CachedDatetime; std::string m_LogId; std::string m_LinePrefix; bool m_UseFullDate = true; RwLock m_TimestampLock; }; FullFormatter::FullFormatter(std::string_view LogId, std::chrono::time_point Epoch) : m_Impl(std::make_unique(LogId, Epoch)) { } FullFormatter::FullFormatter(std::string_view LogId) : m_Impl(std::make_unique(LogId)) { } FullFormatter::~FullFormatter() = default; std::unique_ptr FullFormatter::Clone() const { ZEN_MEMSCOPE(ELLMTag::Logging); std::unique_ptr Copy; if (m_Impl->m_UseFullDate) { Copy = std::make_unique(m_Impl->m_LogId); } else { Copy = std::make_unique(m_Impl->m_LogId, m_Impl->m_Epoch); } Copy->SetColorEnabled(IsColorEnabled()); return Copy; } void FullFormatter::Format(const LogMessage& Msg, MemoryBuffer& OutBuffer) { ZEN_MEMSCOPE(ELLMTag::Logging); // Note that the sink is responsible for ensuring there is only ever a // single caller in here using namespace std::literals; std::chrono::seconds TimestampSeconds; std::chrono::milliseconds Millis; if (m_Impl->m_UseFullDate) { TimestampSeconds = std::chrono::duration_cast(Msg.GetTime().time_since_epoch()); if (TimestampSeconds != m_Impl->m_LastLogSecs) { RwLock::ExclusiveLockScope _(m_Impl->m_TimestampLock); m_Impl->m_LastLogSecs = TimestampSeconds; m_Impl->m_CachedLocalTm = helpers::SafeLocaltime(LogClock::to_time_t(Msg.GetTime())); m_Impl->m_CachedDatetime.clear(); m_Impl->m_CachedDatetime.push_back('['); helpers::Pad2(m_Impl->m_CachedLocalTm.tm_year % 100, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back('-'); helpers::Pad2(m_Impl->m_CachedLocalTm.tm_mon + 1, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back('-'); helpers::Pad2(m_Impl->m_CachedLocalTm.tm_mday, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back(' '); helpers::Pad2(m_Impl->m_CachedLocalTm.tm_hour, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back(':'); helpers::Pad2(m_Impl->m_CachedLocalTm.tm_min, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back(':'); helpers::Pad2(m_Impl->m_CachedLocalTm.tm_sec, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back('.'); } Millis = helpers::TimeFraction(Msg.GetTime()); } else { auto ElapsedTime = Msg.GetTime() - m_Impl->m_Epoch; TimestampSeconds = std::chrono::duration_cast(ElapsedTime); if (m_Impl->m_CacheTimestamp.load() != TimestampSeconds) { RwLock::ExclusiveLockScope _(m_Impl->m_TimestampLock); m_Impl->m_CacheTimestamp = TimestampSeconds; int Count = int(TimestampSeconds.count()); const int LogSecs = Count % 60; Count /= 60; const int LogMins = Count % 60; Count /= 60; const int LogHours = Count; m_Impl->m_CachedDatetime.clear(); m_Impl->m_CachedDatetime.push_back('['); helpers::Pad2(LogHours, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back(':'); helpers::Pad2(LogMins, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back(':'); helpers::Pad2(LogSecs, m_Impl->m_CachedDatetime); m_Impl->m_CachedDatetime.push_back('.'); } Millis = std::chrono::duration_cast(ElapsedTime - TimestampSeconds); } { RwLock::SharedLockScope _(m_Impl->m_TimestampLock); OutBuffer.append(m_Impl->m_CachedDatetime.begin(), m_Impl->m_CachedDatetime.end()); } helpers::Pad3(static_cast(Millis.count()), OutBuffer); OutBuffer.push_back(']'); OutBuffer.push_back(' '); if (!m_Impl->m_LogId.empty()) { OutBuffer.push_back('['); helpers::AppendStringView(m_Impl->m_LogId, OutBuffer); OutBuffer.push_back(']'); OutBuffer.push_back(' '); } // append logger name if exists if (Msg.GetLoggerName().size() > 0) { OutBuffer.push_back('['); helpers::AppendStringView(Msg.GetLoggerName(), OutBuffer); OutBuffer.push_back(']'); OutBuffer.push_back(' '); } OutBuffer.push_back('['); if (IsColorEnabled()) { helpers::AppendAnsiColor(Msg.GetLevel(), OutBuffer); } helpers::AppendStringView(helpers::LevelToShortString(Msg.GetLevel()), OutBuffer); if (IsColorEnabled()) { helpers::AppendAnsiReset(OutBuffer); } OutBuffer.push_back(']'); OutBuffer.push_back(' '); // add source location if present if (Msg.GetSource()) { OutBuffer.push_back('['); const char* Filename = helpers::ShortFilename(Msg.GetSource().Filename); helpers::AppendStringView(Filename, OutBuffer); OutBuffer.push_back(':'); helpers::AppendInt(Msg.GetSource().Line, OutBuffer); OutBuffer.push_back(']'); OutBuffer.push_back(' '); } // Handle newlines in single log call by prefixing each additional line to make // subsequent lines align with the first line in the message size_t AnsiBytes = IsColorEnabled() ? (helpers::AnsiColorForLevel(Msg.GetLevel()).size() + helpers::kAnsiReset.size()) : 0; const size_t LinePrefixCount = Min(OutBuffer.size() - AnsiBytes, m_Impl->m_LinePrefix.size()); auto MsgPayload = Msg.GetPayload(); auto ItLineBegin = MsgPayload.begin(); auto ItMessageEnd = MsgPayload.end(); bool IsFirstline = true; { auto ItLineEnd = ItLineBegin; auto EmitLine = [&] { if (IsFirstline) { IsFirstline = false; } else { helpers::AppendStringView(std::string_view(m_Impl->m_LinePrefix.data(), LinePrefixCount), OutBuffer); } helpers::AppendStringView(std::string_view(&*ItLineBegin, ItLineEnd - ItLineBegin), OutBuffer); }; while (ItLineEnd != ItMessageEnd) { if (*ItLineEnd++ == '\n') { EmitLine(); ItLineBegin = ItLineEnd; } } if (ItLineBegin != ItMessageEnd) { EmitLine(); helpers::AppendStringView("\n"sv, OutBuffer); } } } } // namespace zen::logging