Files
egutierrez f1a5e04d4f feat(cpp/core): logger + log_window + selectable_text widgets
Logger global thread-safe con ring buffer in-memory de 2000 entradas + escritura
opcional a archivo. log_window flotante consume el ring buffer con filtros por
nivel, busqueda y autoscroll; se abre desde Settings -> Logs en la menubar.
selectable_text cubre el patron drag-to-select + Ctrl+C en cualquier ventana.

app_menubar y framework run_app integran log_window_render() en el frame loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 11:50:57 +02:00

82 lines
2.8 KiB
C++

#pragma once
#include <cstddef>
// Logger global thread-safe para apps del registry. Escribe a archivo (cwd
// junto al ejecutable, igual que app_settings.ini) y mantiene un ring buffer
// in-memory que el visualizador (log_window) consume.
//
// Lifecycle:
// - run_app llama logger_init(cfg.log_file, cfg.log_level) si log_file != nullptr
// - app llama log_info / log_warn / ... durante su ciclo de vida
// - run_app llama logger_close() al exit
//
// Apps que NO usan fn::run_app deben llamar logger_init/close manualmente.
// Si nunca se llama logger_init, los log_* siguen funcionando contra el ring
// buffer in-memory pero no escriben a disco.
//
// Formato de cada linea:
// [YYYY-MM-DD HH:MM:SS.mmm] [LEVEL] mensaje
namespace fn_log {
enum class Level : int {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
};
// Inicializa el logger global. file_path se interpreta relativo al cwd
// (donde la app ya escribe app_settings.ini). Crea/trunca el archivo si no
// existe; si existe, abre en modo append.
//
// Idempotente: si ya hay un archivo abierto, lo cierra y reabre el nuevo.
// Returns true si pudo abrir el archivo (false → solo buffer in-memory).
bool logger_init(const char* file_path, Level min_level = Level::Info);
// Cierra el archivo. log_* siguen funcionando contra el buffer in-memory.
void logger_close();
// Nivel minimo. Mensajes por debajo se descartan silenciosamente (no van ni
// al archivo ni al buffer).
void logger_set_level(Level level);
Level logger_level();
// Path del archivo activo. Vacio si no inicializado o cerrado.
const char* logger_path();
// Emisores. Formato printf-style. Cada llamada escribe una linea completa.
// Thread-safe (mutex interno).
void log_debug(const char* fmt, ...);
void log_info (const char* fmt, ...);
void log_warn (const char* fmt, ...);
void log_error(const char* fmt, ...);
// === Ring buffer in-memory (para log_window) ===
constexpr std::size_t kBufferCapacity = 2000;
constexpr std::size_t kEntryTextMax = 480; // deja sitio para timestamp + level
struct Entry {
Level level;
long long ts_ms; // unix epoch en milisegundos
char text[kEntryTextMax];
};
// Numero de entradas vivas en el buffer (≤ kBufferCapacity).
std::size_t buffer_size();
// Acceso por indice [0, buffer_size()). i==0 es la entrada mas antigua viva.
// Nullptr si i fuera de rango. Snapshot — el caller debe asumir que la
// entrada puede ser sobrescrita en la siguiente llamada thread-unsafe; para
// el viewer esto no es problema porque ImGui es single-threaded.
const Entry* buffer_at(std::size_t i);
// Limpia el ring buffer. No toca el archivo en disco.
void buffer_clear();
// Helper para el viewer: nombre corto del nivel ("DEBUG"/"INFO"/...).
const char* level_label(Level level);
} // namespace fn_log