/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: ConsoleFIO.cpp Date: 2021-6-22 Author: Reece ***/ #include #include "ConsoleFIO.hpp" namespace Aurora::Console::ConsoleFIO { static AuList gLogBuffer; static AuThreadPrimitives::RWLockUnique_t gLogMutex; static AuIOFS::OpenWriteUnique_t gFileHandle; static const auto &gLogConfig = gRuntimeConfig.console.fio; AuString GetLogDirectory() { AuString path; AuString procName; if ((gLogConfig.writeLogsToUserDir) || (!IO::FS::GetProfileDomain(path))) { path = "."; } if (!Process::GetProcName(procName)) { procName = "Unknown"; } path += "/Logs/"; path += procName; return path; } 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() { AuList files; AuBST fileMeta; AuUInt32 size {}; auto baseLogPath = GetLogDirectory(); IO::FS::FilesInDirectory(baseLogPath, files); for (const auto &file : files) { IO::FS::Stat stat; IO::FS::StatFile(baseLogPath + "/" + file, stat); fileMeta[file] = stat; size += stat.size; } EraseFilesByTimestamp(baseLogPath, files); // TODO: erase when size >= gLogConfig.maxSizeMb * 1024 } static void CompressLogs() { // TODO: write XZ's } void Flush() { /// It should be expected that the TLS teardown emergency flush to dispatch after deinit if (!gLogMutex) { return; } AU_LOCK_GUARD(gLogMutex->AsReadable()); if (gFileHandle) { gFileHandle->Write(Aurora::Memory::MemoryViewStreamRead(gLogBuffer)); } gLogBuffer.clear(); } static bool OpenLogFile() { AuString path; auto tm = Time::ToCivilTime(Time::CurrentClockMS()); path = fmt::format("{}/{:04}-{:02}-{:02}T{:02}-{:02}-{:02}Z.txt", GetLogDirectory(), 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 static_cast(gFileHandle); } void FIOCleanup() { CleanupOldLogs(); CompressLogs(); } void Init() { if (!gLogConfig.enableLogging) { return; } if (!OpenLogFile()) { return; } gLogMutex = AuThreadPrimitives::RWLockUnique(); if (!gLogMutex) { return; } Console::Hooks::AddFunctionalHook([&](const Console::ConsoleMessage &string) -> void { AU_LOCK_GUARD(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_IS_MODERNNT_DERIVED) gLogBuffer.insert(gLogBuffer.end(), AuUInt8('\r')); #endif gLogBuffer.insert(gLogBuffer.end(), AuUInt8('\n')); }); } void Pump() { } void Exit() { Flush(); gLogMutex.reset(); gFileHandle.reset(); } }