// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include #include namespace zen::logging { struct RotatingFileSink::Impl { Impl(const std::filesystem::path& BaseFilename, std::size_t MaxSize, std::size_t MaxFiles, bool RotateOnOpen) : m_BaseFilename(BaseFilename) , m_MaxSize(MaxSize) , m_MaxFiles(MaxFiles) , m_Formatter(std::make_unique()) { ZEN_MEMSCOPE(ELLMTag::Logging); std::error_code Ec; if (RotateOnOpen) { RwLock::ExclusiveLockScope RotateLock(m_Lock); Rotate(RotateLock, Ec); } else { m_CurrentFile.Open(m_BaseFilename, BasicFile::Mode::kWrite, Ec); if (!Ec) { m_CurrentSize = m_CurrentFile.FileSize(Ec); } if (!Ec) { if (m_CurrentSize > m_MaxSize) { RwLock::ExclusiveLockScope RotateLock(m_Lock); Rotate(RotateLock, Ec); } } } if (Ec) { throw std::system_error(Ec, fmt::format("Failed to open log file '{}'", m_BaseFilename.string())); } } ~Impl() { try { RwLock::ExclusiveLockScope RotateLock(m_Lock); m_CurrentFile.Close(); } catch (const std::exception&) { } } void Rotate(RwLock::ExclusiveLockScope&, std::error_code& OutEc) { ZEN_MEMSCOPE(ELLMTag::Logging); m_CurrentFile.Close(); OutEc = RotateFiles(m_BaseFilename, m_MaxFiles); if (OutEc) { return; } m_CurrentFile.Open(m_BaseFilename, BasicFile::Mode::kWrite, OutEc); if (OutEc) { return; } m_CurrentSize = m_CurrentFile.FileSize(OutEc); if (OutEc) { // FileSize failed but we have an open file — reset to 0 // so we can at least attempt writes from the start m_CurrentSize = 0; OutEc.clear(); } } bool TrySinkIt(const LogMessage& Msg, MemoryBuffer& OutFormatted) { ZEN_MEMSCOPE(ELLMTag::Logging); RwLock::SharedLockScope Lock(m_Lock); if (!m_CurrentFile.IsOpen()) { return false; } m_Formatter->Format(Msg, OutFormatted); helpers::StripAnsiSgrSequences(OutFormatted); size_t AddSize = OutFormatted.size(); size_t WritePos = m_CurrentSize.fetch_add(AddSize); if (WritePos + AddSize > m_MaxSize) { return false; } std::error_code Ec; m_CurrentFile.Write(OutFormatted.data(), OutFormatted.size(), WritePos, Ec); if (Ec) { return false; } m_NeedFlush = true; return true; } bool TrySinkIt(const MemoryBuffer& Formatted) { ZEN_MEMSCOPE(ELLMTag::Logging); RwLock::SharedLockScope Lock(m_Lock); if (!m_CurrentFile.IsOpen()) { return false; } size_t AddSize = Formatted.size(); size_t WritePos = m_CurrentSize.fetch_add(AddSize); if (WritePos + AddSize > m_MaxSize) { return false; } std::error_code Ec; m_CurrentFile.Write(Formatted.data(), Formatted.size(), WritePos, Ec); if (Ec) { return false; } m_NeedFlush = true; return true; } RwLock m_Lock; const std::filesystem::path m_BaseFilename; const std::size_t m_MaxSize; const std::size_t m_MaxFiles; std::unique_ptr m_Formatter; std::atomic_size_t m_CurrentSize; BasicFile m_CurrentFile; std::atomic m_NeedFlush = false; }; RotatingFileSink::RotatingFileSink(const std::filesystem::path& BaseFilename, std::size_t MaxSize, std::size_t MaxFiles, bool RotateOnOpen) : m_Impl(std::make_unique(BaseFilename, MaxSize, MaxFiles, RotateOnOpen)) { } RotatingFileSink::~RotatingFileSink() = default; void RotatingFileSink::Log(const LogMessage& Msg) { ZEN_MEMSCOPE(ELLMTag::Logging); try { MemoryBuffer Formatted; if (m_Impl->TrySinkIt(Msg, Formatted)) { return; } // This intentionally has no limit on the number of retries, see // comment above. for (;;) { { RwLock::ExclusiveLockScope RotateLock(m_Impl->m_Lock); // Only rotate if no-one else has rotated before us if (m_Impl->m_CurrentSize > m_Impl->m_MaxSize || !m_Impl->m_CurrentFile.IsOpen()) { std::error_code Ec; m_Impl->Rotate(RotateLock, Ec); if (Ec) { return; } } } if (m_Impl->TrySinkIt(Formatted)) { return; } } } catch (const std::exception&) { // Silently eat errors } } void RotatingFileSink::Flush() { if (!m_Impl->m_NeedFlush) { return; } ZEN_MEMSCOPE(ELLMTag::Logging); try { RwLock::SharedLockScope Lock(m_Impl->m_Lock); if (m_Impl->m_CurrentFile.IsOpen()) { m_Impl->m_CurrentFile.Flush(); } } catch (const std::exception&) { // Silently eat errors } m_Impl->m_NeedFlush = false; } void RotatingFileSink::SetFormatter(std::unique_ptr InFormatter) { ZEN_MEMSCOPE(ELLMTag::Logging); try { RwLock::ExclusiveLockScope _(m_Impl->m_Lock); m_Impl->m_Formatter = std::move(InFormatter); } catch (const std::exception&) { // Silently eat errors } } } // namespace zen::logging