#include "core/logger.h" #include #include #include #include #include #include #include namespace fn_log { namespace { std::mutex g_mu; FILE* g_file = nullptr; std::string g_path; Level g_min_level = Level::Info; // Ring buffer in-memory. g_count es el numero de entradas vivas (clamp a // capacity); g_head es el indice donde se escribira la proxima entrada. Entry g_buf[kBufferCapacity]; std::size_t g_count = 0; std::size_t g_head = 0; long long now_ms() { using namespace std::chrono; return duration_cast(system_clock::now().time_since_epoch()).count(); } // Formato: "YYYY-MM-DD HH:MM:SS.mmm". out debe tener al menos 24 bytes. void format_ts(long long ts_ms, char* out, std::size_t out_size) { std::time_t secs = static_cast(ts_ms / 1000); int millis = static_cast(ts_ms % 1000); if (millis < 0) millis = 0; if (millis > 999) millis = 999; std::tm tm_buf{}; #ifdef _WIN32 localtime_s(&tm_buf, &secs); #else localtime_r(&secs, &tm_buf); #endif std::snprintf(out, out_size, "%04d-%02d-%02d %02d:%02d:%02d.%03d", (tm_buf.tm_year + 1900) % 10000, (tm_buf.tm_mon + 1) % 100, tm_buf.tm_mday % 100, tm_buf.tm_hour % 100, tm_buf.tm_min % 100, tm_buf.tm_sec % 100, millis); } void push_entry(Level level, long long ts_ms, const char* text) { Entry& e = g_buf[g_head]; e.level = level; e.ts_ms = ts_ms; std::snprintf(e.text, kEntryTextMax, "%s", text); g_head = (g_head + 1) % kBufferCapacity; if (g_count < kBufferCapacity) ++g_count; } // Convierte el indice "logico" (0 = mas antigua) al indice fisico del array. std::size_t logical_to_physical(std::size_t i) { if (g_count < kBufferCapacity) return i; // buffer aun no lleno return (g_head + i) % kBufferCapacity; } void emit(Level level, const char* fmt, std::va_list ap) { if (static_cast(level) < static_cast(g_min_level)) return; // msg deja 64 bytes para que el prefijo "[ts] [LEVEL] " quepa siempre en // el buffer destino sin que -Wformat-truncation se queje. char msg[kEntryTextMax - 64]; std::vsnprintf(msg, sizeof(msg), fmt, ap); long long ts = now_ms(); char ts_buf[32]; format_ts(ts, ts_buf, sizeof(ts_buf)); char line[kEntryTextMax]; std::snprintf(line, sizeof(line), "[%s] [%s] %s", ts_buf, level_label(level), msg); std::lock_guard lk(g_mu); push_entry(level, ts, line); if (g_file) { std::fputs(line, g_file); std::fputc('\n', g_file); std::fflush(g_file); } } } // namespace bool logger_init(const char* file_path, Level min_level) { std::lock_guard lk(g_mu); if (g_file) { std::fclose(g_file); g_file = nullptr; } g_min_level = min_level; g_path.clear(); if (!file_path || !*file_path) return false; g_file = std::fopen(file_path, "a"); if (!g_file) { std::fprintf(stderr, "[fn_log] no pude abrir %s para append\n", file_path); return false; } g_path = file_path; return true; } void logger_close() { std::lock_guard lk(g_mu); if (g_file) { std::fclose(g_file); g_file = nullptr; } g_path.clear(); } void logger_set_level(Level level) { std::lock_guard lk(g_mu); g_min_level = level; } Level logger_level() { std::lock_guard lk(g_mu); return g_min_level; } const char* logger_path() { return g_path.c_str(); } void log_debug(const char* fmt, ...) { std::va_list ap; va_start(ap, fmt); emit(Level::Debug, fmt, ap); va_end(ap); } void log_info (const char* fmt, ...) { std::va_list ap; va_start(ap, fmt); emit(Level::Info, fmt, ap); va_end(ap); } void log_warn (const char* fmt, ...) { std::va_list ap; va_start(ap, fmt); emit(Level::Warn, fmt, ap); va_end(ap); } void log_error(const char* fmt, ...) { std::va_list ap; va_start(ap, fmt); emit(Level::Error, fmt, ap); va_end(ap); } std::size_t buffer_size() { std::lock_guard lk(g_mu); return g_count; } const Entry* buffer_at(std::size_t i) { std::lock_guard lk(g_mu); if (i >= g_count) return nullptr; return &g_buf[logical_to_physical(i)]; } void buffer_clear() { std::lock_guard lk(g_mu); g_count = 0; g_head = 0; } const char* level_label(Level level) { switch (level) { case Level::Debug: return "DEBUG"; case Level::Info: return "INFO"; case Level::Warn: return "WARN"; case Level::Error: return "ERROR"; } return "?"; } } // namespace fn_log