#include "quill/Backend.h" #include "quill/Frontend.h" #include "quill/Logger.h" #include "quill/LogMacros.h" #include "quill/sinks/ConsoleSink.h" #include "quill/sinks/FileSink.h" #include #include "log.h" #include "util.h" #include #include #include namespace fs = ::std::filesystem; namespace logging { ::quill::Logger* s_logger = nullptr; ::quill::Logger* s_loggerOpenVr = nullptr; ::quill::Logger* s_loggerIpc = nullptr; ::quill::Logger* s_loggerHooking = nullptr; ::quill::Logger* s_loggerCalibration = nullptr; class UiHaltOnErrorSink final : public quill::Sink { public: UiHaltOnErrorSink() = default; void write_log(quill::MacroMetadata const* /** log_metadata **/, uint64_t /** log_timestamp **/, std::string_view /** thread_id **/, std::string_view /** thread_name **/, std::string const& /** process_id **/, std::string_view /** logger_name **/, quill::LogLevel log_level, std::string_view /** log_level_description **/, std::string_view /** log_level_short_code **/, std::vector> const* /** named_args - only populated when named args in the format placeholder are used **/, std::string_view /** log_message **/, std::string_view log_statement) override { // Propagate error logs to the UI if (log_level < quill::LogLevel::Backtrace && log_level >= quill::LogLevel::Error) { // @TODO: propagate to ui } } void flush_sink() noexcept override { } void run_periodic_tasks() noexcept override { } }; class DriverLogSink final : public quill::Sink { public: DriverLogSink() = default; void write_log(quill::MacroMetadata const* /** log_metadata **/, uint64_t /** log_timestamp **/, std::string_view /** thread_id **/, std::string_view /** thread_name **/, std::string const& /** process_id **/, std::string_view logger_name, quill::LogLevel log_level, std::string_view /** log_level_description **/, std::string_view /** log_level_short_code **/, std::vector> const* /** named_args - only populated when named args in the format placeholder are used **/, std::string_view log_message, std::string_view log_statement) override { // Propagate error logs to the steamVR std::string szLogLevel; switch (log_level) { case quill::LogLevel::TraceL3: case quill::LogLevel::TraceL2: case quill::LogLevel::TraceL1: szLogLevel = "Trace"; break; case quill::LogLevel::Debug: szLogLevel = "Debug"; break; case quill::LogLevel::Info: szLogLevel = "Info"; break; case quill::LogLevel::Notice: szLogLevel = "Notice"; break; case quill::LogLevel::Warning: szLogLevel = "Warning"; break; case quill::LogLevel::Error: szLogLevel = "Error"; break; case quill::LogLevel::Critical: szLogLevel = "Critical"; break; case quill::LogLevel::Backtrace: szLogLevel = "Backtrace"; break; case quill::LogLevel::None: szLogLevel = "None"; break; default: szLogLevel = fmt::format("LogLevel_{}", (uint32_t)log_level); break; } std::string vrLogMsg = fmt::format("[{} : {}] {}", logger_name, szLogLevel, log_message); vr::VRDriverLog()->Log(vrLogMsg.c_str()); } void flush_sink() noexcept override { } void run_periodic_tasks() noexcept override { } }; bool IsFileOlderThan30Days(int year, int month, int day, int hour, int minute, int second) { std::tm time = std::tm{ second, minute, hour, day, month - 1, year - 1900 }; auto fileTime = std::chrono::system_clock::from_time_t(std::mktime(&time)); auto currentTime = std::chrono::system_clock::now(); auto fileAge = currentTime - fileTime; return fileAge > std::chrono::days(CLEAR_LOG_FILES_AFTER_DAYS); } bool ParseLogFilename(const std::string& filename, int& year, int& month, int& day, int& hour, int& minute, int& second) { if (filename.size() != 27) { return false; } std::stringstream ss(filename.substr(4, 23)); ss >> year; ss.ignore(1); ss >> month; ss.ignore(1); ss >> day; ss.ignore(1); ss >> hour; ss.ignore(1); ss >> minute; ss.ignore(1); ss >> second; return !ss.fail(); } void DeleteOldLogFiles(const std::filesystem::path& directoryPath) { for (const auto& entry : std::filesystem::directory_iterator(directoryPath)) { if (entry.is_regular_file() && entry.path().filename() != "log_latest.log") { std::string fileName = entry.path().filename().string(); int year, month, day, hour, minute, second; if (fileName.substr(0, 4) == "log_" && ParseLogFilename(fileName, year, month, day, hour, minute, second) && IsFileOlderThan30Days(year, month, day, hour, minute, second)) { std::filesystem::remove(entry.path()); } } } } // Helper for declaring a logger #ifdef _DEBUG #define CONSOLE_SINK quill::Frontend::create_or_get_sink("console"), #else #define CONSOLE_SINK #endif #define MAKE_LOGGER_OVERLAY(title) \ quill::Frontend::create_or_get_logger(title, \ { \ CONSOLE_SINK \ quill::Frontend::create_or_get_sink(logFilePath), \ quill::Frontend::create_or_get_sink(logFilePath2), \ quill::Frontend::create_or_get_sink("halt_on_error") \ }); #define MAKE_LOGGER_DRIVER(title) \ quill::Frontend::create_or_get_logger(title, \ { \ quill::Frontend::create_or_get_sink(logFilePath), \ quill::Frontend::create_or_get_sink(logFilePath2), \ quill::Frontend::create_or_get_sink("driverlog") \ }); void Init(bool isOverlay) { assert(s_logger == nullptr); assert(s_loggerOpenVr == nullptr); quill::Backend::start(); auto now = std::chrono::system_clock::now(); auto time = std::chrono::system_clock::to_time_t(now); auto tm = *std::localtime(&time); const char* logType = isOverlay ? "overlay" : "driver"; std::string logFilePath = (util::getSpaceCalibratorLogsDir() / fmt::format("log_{}_{:%Y_%m_%d_%H_%M_%S}.log", logType, tm)).string(); std::string logFilePath2 = (util::getSpaceCalibratorLogsDir() / fmt::format("log_{}_latest.log", logType)).string(); DeleteOldLogFiles(util::getSpaceCalibratorLogsDir()); if (isOverlay) { s_logger = MAKE_LOGGER_OVERLAY("overlay"); s_loggerOpenVr = MAKE_LOGGER_OVERLAY("openvr"); s_loggerIpc = MAKE_LOGGER_OVERLAY("ipc"); s_loggerHooking = MAKE_LOGGER_OVERLAY("hooking"); s_loggerCalibration = MAKE_LOGGER_OVERLAY("calibration"); } else { s_logger = MAKE_LOGGER_DRIVER("driver"); s_loggerOpenVr = MAKE_LOGGER_DRIVER("openvr"); s_loggerIpc = MAKE_LOGGER_DRIVER("ipc"); s_loggerHooking = MAKE_LOGGER_DRIVER("hooking"); s_loggerCalibration = MAKE_LOGGER_DRIVER("calibration"); } } }