#include #include #include #include #include namespace rack { namespace logger { static FILE* outputFile = NULL; static int64_t startTime = 0; static std::mutex logMutex; void init() { startTime = system::getNanoseconds(); if (settings::devMode) { outputFile = stderr; } else { outputFile = std::fopen(asset::logPath.c_str(), "w"); if (!outputFile) { std::fprintf(stderr, "Could not open log at %s\n", asset::logPath.c_str()); } } } void destroy() { if (outputFile && outputFile != stderr) { // Print end token so we know if the logger exited cleanly. std::fprintf(outputFile, "END"); std::fclose(outputFile); } } static const char* const levelLabels[] = { "debug", "info", "warn", "fatal" }; static const int levelColors[] = { 35, 34, 33, 31 }; static void logVa(Level level, const char* filename, int line, const char* format, va_list args) { std::lock_guard lock(logMutex); if (!outputFile) return; int64_t nowTime = system::getNanoseconds(); double duration = (nowTime - startTime) / 1e9; if (outputFile == stderr) std::fprintf(outputFile, "\x1B[%dm", levelColors[level]); std::fprintf(outputFile, "[%.03f %s %s:%d] ", duration, levelLabels[level], filename, line); if (outputFile == stderr) std::fprintf(outputFile, "\x1B[0m"); std::vfprintf(outputFile, format, args); std::fprintf(outputFile, "\n"); std::fflush(outputFile); } void log(Level level, const char* filename, int line, const char* format, ...) { va_list args; va_start(args, format); logVa(level, filename, line, format, args); va_end(args); } bool isTruncated() { if (settings::devMode) return false; // Open existing log file FILE* file = std::fopen(asset::logPath.c_str(), "r"); if (!file) return false; DEFER({ std::fclose(file); }); // Seek to last 3 characters std::fseek(file, -3, SEEK_END); char str[3]; if (std::fread(str, 1, 3, file) != 3) return true; if (std::memcmp(str, "END", 3) != 0) return true; return false; } } // namespace logger } // namespace rack