loki_mejorado para simplicidad

This commit is contained in:
2025-11-02 13:11:18 +01:00
parent cf4a77132e
commit a289816a2a
3 changed files with 68 additions and 49 deletions
+42 -36
View File
@@ -26,14 +26,14 @@ class LokiLogger:
def __init__( def __init__(
self, self,
endpoint: str = "http://127.0.0.1:3101/loki/api/v1/push", endpoint: str = "http://127.0.0.1:3101/loki/api/v1/push",
default_labels: Optional[Dict[str, str]] = None, add_labels: Optional[Dict[str, str]] = None,
timeout: float = 5.0, timeout: float = 5.0,
min_level: str = "DEBUG", min_level: str = "DEBUG",
service_name: Optional[str] = None, service_name: Optional[str] = None,
): ):
""" """
:param endpoint: URL completa del endpoint de Loki / Alloy. :param endpoint: URL completa del endpoint de Loki / Alloy.
:param default_labels: etiquetas estáticas comunes (ej: {"env": "dev"}) :param add_labels: etiquetas adicionales comunes (ej: {"env": "dev"})
:param timeout: timeout en segundos para la petición HTTP :param timeout: timeout en segundos para la petición HTTP
:param min_level: nivel mínimo para enviar logs :param min_level: nivel mínimo para enviar logs
:param service_name: nombre del servicio (usado como 'service_name' label) :param service_name: nombre del servicio (usado como 'service_name' label)
@@ -47,9 +47,8 @@ class LokiLogger:
raise ValueError(f"min_level debe estar en {self.ALLOWED_LEVELS}") raise ValueError(f"min_level debe estar en {self.ALLOWED_LEVELS}")
self.min_level = min_level self.min_level = min_level
# 🔹 Labels base (solo service_name + default_labels) # 🔹 Labels adicionales del constructor
self.default_labels = dict(default_labels or {}) self.add_labels = dict(add_labels or {})
self.default_labels["service_name"] = self.service_name
self._level_order = { self._level_order = {
"TRACE": 0, "TRACE": 0,
@@ -74,8 +73,8 @@ class LokiLogger:
self, self,
level: str, level: str,
message: str, message: str,
labels: Optional[Dict[str, str]] = None, add_labels: Optional[Dict[str, str]] = None,
metadata: Optional[Dict[str, Any]] = None, # mantenemos metadata por compatibilidad add_fields: Optional[Dict[str, Any]] = None,
) -> None: ) -> None:
"""Envía un log a Loki con los campos mínimos (timestamp + message).""" """Envía un log a Loki con los campos mínimos (timestamp + message)."""
level = level.upper() level = level.upper()
@@ -88,24 +87,31 @@ class LokiLogger:
if not self._should_log(level): if not self._should_log(level):
return return
# 🔸 Construimos labels: base + detected_level + custom # 🔸 Construimos labels: solo 2 labels por defecto
final_labels = { final_labels = {
"service_name": self.service_name, "service_name": self.service_name,
"detected_level": level, "detected_level": level,
} }
# Agrega default_labels (si existen) # Agrega labels adicionales del constructor
final_labels.update(self.default_labels) if self.add_labels:
final_labels.update(self.add_labels)
# Agrega labels personalizados # Agrega labels adicionales del método
if labels: if add_labels:
final_labels.update(labels) final_labels.update(add_labels)
# 🧾 El log line solo lleva timestamp y message # 🧾 El log line lleva timestamp + message + campos adicionales
log_line = json.dumps({ log_line_data = {
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"message": message, "message": message,
}) }
# Agrega campos adicionales si se pasan
if add_fields:
log_line_data.update(add_fields)
log_line = json.dumps(log_line_data)
body = { body = {
"streams": [ "streams": [
@@ -120,38 +126,38 @@ class LokiLogger:
print(f"Failed to send log to Loki: {e}", flush=True) print(f"Failed to send log to Loki: {e}", flush=True)
# Métodos estándar por nivel # Métodos estándar por nivel
def trace(self, message, labels=None, metadata=None): def trace(self, message, add_labels=None, add_fields=None):
self.log("TRACE", message, labels, metadata) self.log("TRACE", message, add_labels, add_fields)
def debug(self, message, labels=None, metadata=None): def debug(self, message, add_labels=None, add_fields=None):
self.log("DEBUG", message, labels, metadata) self.log("DEBUG", message, add_labels, add_fields)
def info(self, message, labels=None, metadata=None): def info(self, message, add_labels=None, add_fields=None):
self.log("INFO", message, labels, metadata) self.log("INFO", message, add_labels, add_fields)
def warn(self, message, labels=None, metadata=None): def warn(self, message, add_labels=None, add_fields=None):
self.log("WARN", message, labels, metadata) self.log("WARN", message, add_labels, add_fields)
def warning(self, message, labels=None, metadata=None): def warning(self, message, add_labels=None, add_fields=None):
self.log("WARN", message, labels, metadata) self.log("WARN", message, add_labels, add_fields)
def error(self, message, labels=None, metadata=None): def error(self, message, add_labels=None, add_fields=None):
self.log("ERROR", message, labels, metadata) self.log("ERROR", message, add_labels, add_fields)
def exception(self, exc: Exception, labels=None, metadata=None): def exception(self, exc: Exception, add_labels=None, add_fields=None):
"""Registra una excepción con traceback incluido.""" """Registra una excepción con traceback incluido."""
tb = traceback.format_exc() tb = traceback.format_exc()
message = str(exc) message = str(exc)
self.log("ERROR", f"{message}\n{tb}", labels=labels) self.log("ERROR", f"{message}\n{tb}", add_labels=add_labels, add_fields=add_fields)
def fatal(self, message, labels=None, metadata=None): def fatal(self, message, add_labels=None, add_fields=None):
self.log("FATAL", message, labels, metadata) self.log("FATAL", message, add_labels, add_fields)
def critical(self, message, labels=None, metadata=None): def critical(self, message, add_labels=None, add_fields=None):
self.log("CRITICAL", message, labels, metadata) self.log("CRITICAL", message, add_labels, add_fields)
def unknown(self, message, labels=None, metadata=None): def unknown(self, message, add_labels=None, add_fields=None):
self.log("UNKNOWN", message, labels, metadata) self.log("UNKNOWN", message, add_labels, add_fields)
# 🧩 Decorador para capturar excepciones # 🧩 Decorador para capturar excepciones
def catch_exceptions(self, reraise: bool = False): def catch_exceptions(self, reraise: bool = False):
+3
View File
@@ -36,6 +36,9 @@ schema_config:
ruler: ruler:
alertmanager_url: http://localhost:9093 alertmanager_url: http://localhost:9093
limits_config:
discover_log_levels: false
# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration # By default, Loki will send anonymous, but uniquely-identifiable usage and configuration
# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/ # analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/
# #
+21 -11
View File
@@ -4,21 +4,31 @@ from Logger.LokiLogger import LokiLogger
def prueba_log(): def prueba_log():
logger = LokiLogger( logger = LokiLogger(
default_labels={"job": "prueba_ejemplo", service_name="suite_logs_example_3",
# "env": "production" # add_labels={
}, # "job": "log_testing",
# "env": "development",
# "component": "main"
# },
min_level="DEBUG" min_level="DEBUG"
) )
try:
logger.info("Iniciando pruebas del sistema de logging")
logger.trace("Inicio del proceso de ETL", service="etl") logger.trace("Inicio del proceso de ETL")
logger.debug("Carga de datos completada", service="etl") logger.debug("Carga de datos completada")
logger.info("Pipeline ejecutado correctamente", service="etl") logger.info("Pipeline ejecutado correctamente")
logger.warn("Latencia superior a lo esperado", service="etl", metadata={"latency_ms": 850}) logger.warn("Latencia superior a lo esperado")
logger.error("Error al conectar con base de datos", service="etl", metadata={"db_host": "postgres"}) logger.error("Error al conectar con base de datos")
logger.fatal("Fallo crítico en nodo principal", service="etl") logger.fatal("Fallo crítico en nodo principal")
logger.critical("Memoria insuficiente para procesamiento", service="etl") logger.critical("Memoria insuficiente para procesamiento")
logger.unknown("Log sin nivel detectado", service="etl") logger.unknown("Log sin nivel detectado")
logger.info("Pruebas de logging completadas exitosamente")
except Exception as e:
logger.exception(e)
if __name__ == "__main__": if __name__ == "__main__":