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>
This commit is contained in:
2026-05-04 11:50:57 +02:00
parent d55b8fea5d
commit f1a5e04d4f
17 changed files with 1280 additions and 64 deletions
+84
View File
@@ -0,0 +1,84 @@
---
name: logger
kind: function
lang: cpp
domain: core
version: "1.0.0"
purity: impure
signature: "bool fn_log::logger_init(const char* file_path, fn_log::Level min_level = Level::Info); void fn_log::logger_close(); void fn_log::logger_set_level(Level); fn_log::Level fn_log::logger_level(); const char* fn_log::logger_path(); void fn_log::log_debug(const char* fmt, ...); void fn_log::log_info(const char* fmt, ...); void fn_log::log_warn(const char* fmt, ...); void fn_log::log_error(const char* fmt, ...); std::size_t fn_log::buffer_size(); const fn_log::Entry* fn_log::buffer_at(std::size_t); void fn_log::buffer_clear(); const char* fn_log::level_label(fn_log::Level)"
description: "Logger global thread-safe para apps C++ del registry. Escribe a archivo (cwd, junto al ejecutable) en modo append y mantiene un ring buffer in-memory de 2000 entradas que el visualizador log_window consume. Formato: [YYYY-MM-DD HH:MM:SS.mmm] [LEVEL] mensaje."
tags: [logger, logging, file, infra, thread-safe]
uses_functions: []
uses_types: [log_level_cpp_core, log_entry_cpp_core]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [chrono, cstdarg, cstdio, ctime, mutex, string]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/logger.cpp"
framework: ""
params:
- name: file_path
desc: "Ruta del archivo de log relativa al cwd (junto al ejecutable). Modo append. Si vacio o nullptr, no se escribe a disco — solo buffer in-memory"
- name: min_level
desc: "Nivel minimo a aceptar. Mensajes por debajo se descartan antes de tocar archivo o buffer"
- name: fmt
desc: "Formato printf-style de log_debug/info/warn/error. Cada llamada produce una linea independiente"
- name: i
desc: "Indice [0, buffer_size()) en el ring buffer. 0 = entrada mas antigua viva"
output: "logger_init retorna true si pudo abrir el archivo (false → solo buffer). logger_close cierra archivo (idempotente). log_* mutan estado global thread-safe. buffer_at retorna puntero valido o nullptr si i fuera de rango"
notes: "consumido por cpp/framework/app_base.cpp (init/close automatico via AppConfig.log) y cpp/functions/core/log_window.cpp (lectura del buffer)"
---
# logger
Logger global, thread-safe, integrado en `fn::run_app`: las apps solo declaran un `AppLogConfig` y emiten con `log_info(...)` etc.
## Uso desde una app
```cpp
#include "app_base.h"
#include "core/logger.h"
int main() {
return fn::run_app({
.title = "Mi App",
.log = {.file_path = "mi_app.log",
.level = static_cast<int>(fn_log::Level::Info)}
}, render);
}
```
Tras esto, `fn::run_app` llama `logger_init` antes del primer frame y `logger_close` al exit. La app solo necesita usar los emisores:
```cpp
fn_log::log_info ("usuario abrio archivo %s", path);
fn_log::log_warn ("retry %d/%d", attempt, max);
fn_log::log_error("connection failed: %s", reason);
fn_log::log_debug("estado interno: %d items", n);
```
## Uso sin fn::run_app
Apps que arman su propio main loop deben llamar manualmente:
```cpp
fn_log::logger_init("app.log", fn_log::Level::Info);
// ... vida de la app ...
fn_log::logger_close();
```
## Reglas
- Ruta relativa al cwd (igual convencion que `app_settings.ini`).
- Modo append: relanzar la app conserva el historico previo en disco.
- Thread-safe: un mutex interno protege archivo + buffer + nivel.
- Truncacion: cada mensaje cabe en `kEntryTextMax - 64` caracteres formateados; el resto se trunca silenciosamente.
- Si `logger_init` no se llama o falla, los `log_*` siguen siendo seguros: solo escriben al ring buffer in-memory (que la ventana `Logs` puede mostrar igualmente).
## Integracion
- `fn::AppConfig::log` activa el logger desde el framework.
- `fn_ui::log_window` lee el ring buffer y pinta la ventana "Logs..." del menubar.