- Reestructuración del CLI en módulos discretos (core/, comando/, config/, clientes/) - Migración del punto de entrada tools/adn.rb hacia el launcher ejecutable tools/adn/run - Simplificación de nombres de comandos (remoción prefijo subcomando_) - Corrección de rutas base transversales (PROJECT_ROOT, BITACORAS_DIR) - Reescritura integral de sentencias require_relative - Restauración de sintaxis en archivo sync.rb truncado
226 lines
7.0 KiB
Ruby
226 lines
7.0 KiB
Ruby
# frozen_string_literal: true
|
||
|
||
# tools/adn/logger.rb — Logger estructurado JSON para el sistema ADN
|
||
# ====================================================================
|
||
# Este módulo proporciona logging estructurado en JSON para todas las
|
||
# herramientas del sistema ADN. Los logs se almacenan en `logs/adn/`
|
||
# con rotación diaria automática.
|
||
|
||
require 'json'
|
||
require 'fileutils'
|
||
require 'date'
|
||
require 'time'
|
||
|
||
module ADN
|
||
class Logger
|
||
# Niveles de log soportados
|
||
NIVELES = %w[DEBUG INFO EXITO ADVERTENCIA ERROR].freeze
|
||
|
||
# Colores ANSI para terminal (opcional)
|
||
module Color
|
||
RESET = "\e[0m"
|
||
BOLD = "\e[1m"
|
||
GREEN = "\e[32m"
|
||
CYAN = "\e[36m"
|
||
YELLOW = "\e[33m"
|
||
RED = "\e[31m"
|
||
MAGENTA = "\e[35m"
|
||
DIM = "\e[2m"
|
||
end
|
||
|
||
# Inicializa el logger
|
||
#
|
||
# @param opts [Hash] Opciones de configuración
|
||
# @option opts [String] :directorio Directorio para logs (default: logs/adn/)
|
||
# @option opts [Boolean] :terminal Mostrar logs en terminal (default: true)
|
||
# @option opts [Boolean] :colores Usar colores en terminal (default: true)
|
||
# @option opts [String] :archivo Nombre base del archivo de log (default: fecha actual)
|
||
def initialize(opts = {})
|
||
@directorio = opts[:directorio] || File.join(File.expand_path('../../..', __dir__), 'logs', 'adn')
|
||
@mostrar_terminal = opts.fetch(:terminal, true)
|
||
@usar_colores = opts.fetch(:colores, true)
|
||
@archivo_base = opts[:archivo] || Date.today.iso8601
|
||
|
||
# Crear directorio si no existe
|
||
FileUtils.mkdir_p(@directorio) unless Dir.exist?(@directorio)
|
||
|
||
# Ruta completa del archivo de log
|
||
@ruta_log = File.join(@directorio, "#{@archivo_base}.log")
|
||
|
||
# Buffer para logs (útil para pruebas o modo batch)
|
||
@buffer = []
|
||
end
|
||
|
||
# Registra un mensaje de nivel DEBUG
|
||
#
|
||
# @param mensaje [String] Mensaje descriptivo
|
||
# @param datos [Hash] Datos adicionales estructurados
|
||
# @param opts [Hash] Opciones adicionales
|
||
# @option opts [Boolean] :silencioso No mostrar en terminal
|
||
def debug(mensaje, datos = {}, opts = {})
|
||
escribir('DEBUG', mensaje, datos, opts)
|
||
mostrar_terminal('🐛', mensaje, Color::DIM, opts) if debug_activado?
|
||
end
|
||
|
||
# Registra un mensaje de nivel INFO
|
||
#
|
||
# @param mensaje [String] Mensaje descriptivo
|
||
# @param datos [Hash] Datos adicionales estructurados
|
||
# @param opts [Hash] Opciones adicionales
|
||
# @option opts [Boolean] :silencioso No mostrar en terminal
|
||
def info(mensaje, datos = {}, opts = {})
|
||
escribir('INFO', mensaje, datos, opts)
|
||
mostrar_terminal('ℹ', mensaje, Color::CYAN, opts)
|
||
end
|
||
|
||
# Registra un mensaje de nivel ÉXITO
|
||
#
|
||
# @param mensaje [String] Mensaje descriptivo
|
||
# @param datos [Hash] Datos adicionales estructurados
|
||
# @param opts [Hash] Opciones adicionales
|
||
# @option opts [Boolean] :silencioso No mostrar en terminal
|
||
def exito(mensaje, datos = {}, opts = {})
|
||
escribir('EXITO', mensaje, datos, opts)
|
||
mostrar_terminal('✓', mensaje, Color::GREEN, opts)
|
||
end
|
||
|
||
# Registra un mensaje de nivel ADVERTENCIA
|
||
#
|
||
# @param mensaje [String] Mensaje descriptivo
|
||
# @param datos [Hash] Datos adicionales estructurados
|
||
# @param opts [Hash] Opciones adicionales
|
||
# @option opts [Boolean] :silencioso No mostrar en terminal
|
||
def advertencia(mensaje, datos = {}, opts = {})
|
||
escribir('ADVERTENCIA', mensaje, datos, opts)
|
||
mostrar_terminal('⚠', mensaje, Color::YELLOW, opts)
|
||
end
|
||
|
||
# Alias inglés para advertencia
|
||
#
|
||
# @param mensaje [String] Mensaje descriptivo
|
||
# @param datos [Hash] Datos adicionales estructurados
|
||
# @param opts [Hash] Opciones adicionales
|
||
# @option opts [Boolean] :silencioso No mostrar en terminal
|
||
def warn(mensaje, datos = {}, opts = {})
|
||
advertencia(mensaje, datos, opts)
|
||
end
|
||
|
||
# Registra un mensaje de nivel ERROR
|
||
#
|
||
# @param mensaje [String] Mensaje descriptivo
|
||
# @param datos [Hash] Datos adicionales estructurados
|
||
# @param opts [Hash] Opciones adicionales
|
||
# @option opts [Boolean] :silencioso No mostrar en terminal
|
||
def error(mensaje, datos = {}, opts = {})
|
||
escribir('ERROR', mensaje, datos, opts)
|
||
mostrar_terminal('✗', mensaje, Color::RED, opts)
|
||
end
|
||
|
||
# Obtiene los logs almacenados en buffer
|
||
#
|
||
# @return [Array<Hash>] Logs en buffer
|
||
def buffer
|
||
@buffer.dup
|
||
end
|
||
|
||
# Limpia el buffer interno
|
||
def limpiar_buffer
|
||
@buffer.clear
|
||
end
|
||
|
||
# Cierra cualquier recurso del logger (archivos abiertos, etc.)
|
||
def cerrar
|
||
# Por ahora no hay recursos persistentes abiertos
|
||
true
|
||
end
|
||
|
||
# Verifica si el debug está activado (variable de entorno ADN_DEBUG)
|
||
#
|
||
# @return [Boolean]
|
||
def debug_activado?
|
||
ENV['ADN_DEBUG'] == '1' || ENV['ADN_DEBUG'] == 'true'
|
||
end
|
||
|
||
# Ruta actual del archivo de log
|
||
#
|
||
# @return [String]
|
||
def ruta_log
|
||
@ruta_log
|
||
end
|
||
|
||
private
|
||
|
||
# Escribe una entrada de log en formato JSON
|
||
#
|
||
# @param nivel [String] Nivel del log
|
||
# @param mensaje [String] Mensaje descriptivo
|
||
# @param datos [Hash] Datos adicionales estructurados
|
||
# @param opts [Hash] Opciones adicionales
|
||
def escribir(nivel, mensaje, datos, opts)
|
||
entrada = {
|
||
timestamp: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S%z'),
|
||
nivel: nivel,
|
||
mensaje: mensaje,
|
||
datos: datos.reject { |k, _| k == :silencioso }, # Excluir opciones internas
|
||
herramienta: opts[:herramienta] || determinar_herramienta_llamante
|
||
}
|
||
|
||
# Agregar al buffer
|
||
@buffer << entrada.dup
|
||
|
||
# Escribir al archivo
|
||
File.open(@ruta_log, 'a') do |archivo|
|
||
archivo.puts(entrada.to_json)
|
||
end
|
||
end
|
||
|
||
# Muestra el mensaje en terminal (si está habilitado)
|
||
#
|
||
# @param icono [String] Icono para representar el nivel
|
||
# @param mensaje [String] Mensaje a mostrar
|
||
# @param color [String] Código ANSI de color
|
||
# @param opts [Hash] Opciones adicionales
|
||
def mostrar_terminal(icono, mensaje, color, opts)
|
||
return unless @mostrar_terminal && !opts[:silencioso]
|
||
|
||
if @usar_colores
|
||
puts "#{color}#{icono} #{mensaje}#{Color::RESET}"
|
||
else
|
||
puts "#{icono} #{mensaje}"
|
||
end
|
||
end
|
||
|
||
# Intenta determinar qué herramienta llamó al logger
|
||
#
|
||
# @return [String] Nombre de la herramienta
|
||
def determinar_herramienta_llamante
|
||
# Obtener el backtrace y buscar llamadas desde tools/adn/
|
||
caller_locations.each do |loc|
|
||
ruta = loc.absolute_path || loc.path
|
||
if ruta.include?('tools/adn')
|
||
# Extraer nombre del archivo sin extensión
|
||
nombre = File.basename(ruta, '.*')
|
||
return "adn.#{nombre}" unless nombre.empty?
|
||
end
|
||
end
|
||
|
||
'adn.desconocido'
|
||
end
|
||
end
|
||
|
||
# Logger global preconfigurado para uso rápido
|
||
#
|
||
# @return [ADN::Logger]
|
||
def self.logger
|
||
@logger ||= Logger.new
|
||
end
|
||
|
||
# Configura el logger global
|
||
#
|
||
# @param opts [Hash] Opciones de configuración
|
||
# @return [ADN::Logger]
|
||
def self.configurar_logger(opts = {})
|
||
@logger = Logger.new(opts)
|
||
end
|
||
end
|