#include #include #include #include #include // #include // for dup2 namespace rack { namespace logger { std::string logPath; static FILE* outputFile = NULL; static std::mutex mutex; static bool truncated = false; const static long maxSize = 10 * 1000 * 1000; // 10 MB static bool fileEndsWith(FILE* file, std::string str) { // Seek to last `len` characters size_t len = str.size(); std::fseek(file, -long(len), SEEK_END); char actual[len]; if (std::fread(actual, 1, len, file) != len) return false; return std::string(actual, len) == str; } static bool isTruncated() { if (logPath.empty()) return false; // Open existing log file FILE* file = std::fopen(logPath.c_str(), "r"); if (!file) return false; DEFER({std::fclose(file);}); if (fileEndsWith(file, "END")) return false; // legacy <=v1 if (fileEndsWith(file, "Destroying logger\n")) return false; return true; } bool init() { if (outputFile) return true; std::lock_guard lock(mutex); truncated = false; // Don't open a file in development mode. if (logPath.empty()) { outputFile = stderr; } else { truncated = isTruncated(); outputFile = std::fopen(logPath.c_str(), "w"); if (!outputFile) { std::fprintf(stderr, "Could not open log at %s\n", logPath.c_str()); return false; } } return true; } void destroy() { std::lock_guard lock(mutex); if (outputFile && outputFile != stderr) { // Print end token so we know if the logger exited cleanly. std::fprintf(outputFile, "END"); std::fclose(outputFile); } outputFile = NULL; } 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* func, const char* format, va_list args) { if (!outputFile) return; // Record logging time before calling OS functions double nowTime = system::getTime(); // Check if log size is full if (outputFile != stderr) { long pos = std::ftell(outputFile); if (pos >= maxSize) return; } std::lock_guard lock(mutex); if (outputFile == stderr) std::fprintf(outputFile, "\x1B[%dm", levelColors[level]); std::fprintf(outputFile, "[%.03f %s %s:%d %s] ", nowTime, levelLabels[level], filename, line, func); if (outputFile == stderr) std::fprintf(outputFile, "\x1B[0m"); std::vfprintf(outputFile, format, args); std::fprintf(outputFile, "\n"); // Note: This adds around 10us, but it's important for logging to finish writing the file, and logging is not used in performance critical code. std::fflush(outputFile); } void log(Level level, const char* filename, int line, const char* func, const char* format, ...) { va_list args; va_start(args, format); logVa(level, filename, line, func, format, args); va_end(args); } bool wasTruncated() { return truncated; } } // namespace logger } // namespace rack