/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: ConsoleLogger.cpp Date: 2021-6-22 Author: Reece ***/ #include #include "ConsoleLogger.hpp" namespace Aurora::Console::ConsoleLogger { static Threading::Threads::ThreadUnique_t gFileWriterThread; static AuList gLogBuffer; static Threading::Primitives::RWLockUnique_t gLogMutex; static IO::FS::OpenWriteUnique_t gFileHandle; static const auto & gLogConfig = gRuntimeConfig.console.logging; static void EraseFilesByTimestamp(const AuString &path, /*const its not worth reallocation*/ AuList &files) { if (files.size() <= gLogConfig.maxLogs) { return; } // our filenames are usually prefixed by an ISO 8601 timestamp where the most significant bits are last (YYYY-MM-DD?HH-MM-SS) // a quick ghetto sort should be all we need. no need to waste time parsing timestamps std::sort(files.begin(), files.end()); auto amount = files.size() - gLogConfig.maxLogs; for (auto x = 0, i = 0; ((x < amount) && (i > files.size())); i++) { try { if (IO::FS::Remove(path + "/" + files[i])) { x++; } } catch (...) { } } } static void CleanupOldLogs() { AuString path; AuString procName; if (!Process::GetProcName(procName)) { return; } if (!IO::FS::GetProfileDomain(path)) { return; } path += "/Logs/"; path += procName; AuList files; AuBST fileMeta; AuUInt32 size {}; IO::FS::FilesInDirectory(path, files); for (const auto &file : files) { IO::FS::Stat stat; IO::FS::StatFile(path + "/" + file, stat); fileMeta[file] = stat; size += stat.size; } EraseFilesByTimestamp(path, files); // TODO: erase when size >= gLogConfig.maxSizeMb * 1024 } static void CompressLogs() { } void Flush() { Threading::WaitableLockGuard a(gLogMutex->AsReadable()); if (gFileHandle) { gFileHandle->Write(gLogBuffer.data(), gLogBuffer.size()); } gLogBuffer.clear(); } static bool OpenLogFile() { AuString path; AuString procName; if (!Process::GetProcName(procName)) { return false; } if (!IO::FS::GetProfileDomain(path)) { return false; } auto tm = Time::ToCivilTime(Time::CurrentClockMS()); path += fmt::format("/Logs/{}/{:04}-{:02}-{:02}T{:02}-{:02}-{:02}Z.txt", procName, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); gFileHandle = IO::FS::OpenWriteUnique(path); return gFileHandle ? true : false; } static void LogThreadInit() { CleanupOldLogs(); auto thread = Threading::Threads::GetThread(); while (!thread->Exiting()) { Sleep(500); Flush(); } } void Init() { if (!gLogConfig.enableLogging) { return; } if (!OpenLogFile()) { return; } gLogMutex = Threading::Primitives::RWLockUnique(); if (!gLogMutex) { return; } Threading::Threads::AbstractThreadVectors handler; handler.DoRun = [](Threading::Threads::IAuroraThread *) { LogThreadInit(); }; gFileWriterThread = Threading::Threads::ThreadUnique(handler); if (!gFileWriterThread) { return; } gFileWriterThread->SetName("ConsoleFIO"); gFileWriterThread->Run(); Console::Hooks::AddHook([&](const Console::ConsoleMessage &string) -> void { Threading::WaitableLockGuard a(gLogMutex->AsWritable()); auto str = string.ToSimplified(); gLogBuffer.reserve(gLogBuffer.size() + str.size() + 2); gLogBuffer.insert(gLogBuffer.end(), reinterpret_cast(str.data()), reinterpret_cast(str.data()) + str.size()); #if defined(AURORA_PLATFORM_WIN32) gLogBuffer.insert(gLogBuffer.end(), AuUInt8('\r')); #endif gLogBuffer.insert(gLogBuffer.end(), AuUInt8('\n')); }); } void Pump() { } void Exit() { Flush(); gFileWriterThread.reset(); gLogMutex.reset(); gFileHandle.reset(); CompressLogs(); } }