Refactor(ADN): Migración de tools/adn a adn/tools en busca de la Armonía Integral del DIIAA
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# adn/tools/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
|
||||
|
||||
require_relative 'colores'
|
||||
|
||||
# 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
|
||||
|
||||
# Muestra una sugerencia de comandos ADN a utilizar
|
||||
#
|
||||
# @param comandos [String, Array<String>] Comando o comandos sugeridos
|
||||
def sugerencia(comandos)
|
||||
comandos = [comandos] unless comandos.is_a?(Array)
|
||||
puts ""
|
||||
puts " 💡 #{Color::BOLD}Sugerencia:#{Color::RESET} Podés solucionar esto usando las herramientas ADN:"
|
||||
comandos.each do |cmd|
|
||||
puts " #{Color::CYAN}#{cmd}#{Color::RESET}"
|
||||
end
|
||||
puts ""
|
||||
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 adn/tools/
|
||||
caller_locations.each do |loc|
|
||||
ruta = loc.absolute_path || loc.path
|
||||
if ruta.include?('adn/tools')
|
||||
# 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
|
||||
Reference in New Issue
Block a user