Nombre del servicio directo en el logger

This commit is contained in:
2025-10-31 15:32:33 +01:00
parent 281ec46fc7
commit 64e1c0e970
+38 -44
View File
@@ -3,11 +3,13 @@ import json
import requests import requests
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
class LokiLogger: class LokiLogger:
""" """
Logger compatible con Grafana Loki / Alloy. Logger compatible con Grafana Loki / Alloy.
Envía logs en formato JSON a través del endpoint HTTP de Loki. Envía logs en formato JSON a través del endpoint HTTP de Loki.
""" """
ALLOWED_LEVELS = ( ALLOWED_LEVELS = (
"TRACE", "TRACE",
"DEBUG", "DEBUG",
@@ -24,25 +26,29 @@ class LokiLogger:
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, default_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,
): ):
""" """
:param endpoint: URL completa del endpoint de Loki / Alloy. :param endpoint: URL completa del endpoint de Loki / Alloy.
:param default_labels: etiquetas estáticas por defecto para todos los logs, :param default_labels: etiquetas estáticas comunes (ej: {"env": "dev"})
ej: {"job": "my-service", "env": "prod"}
: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. Ej: "INFO" solo enviará INFO o más graves. :param min_level: nivel mínimo para enviar logs
:param service_name: nombre del servicio (se usará también como 'job')
""" """
self.endpoint = endpoint self.endpoint = endpoint
self.default_labels = default_labels or {}
self.timeout = timeout self.timeout = timeout
self.service_name = service_name or "unknown-service"
min_level = min_level.upper() min_level = min_level.upper()
if min_level not in self.ALLOWED_LEVELS: if min_level not in self.ALLOWED_LEVELS:
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
# Asigna prioridad (menor valor = más detallado) # Base labels: incluye el job automáticamente
self.default_labels = dict(default_labels or {})
self.default_labels["job"] = self.service_name
self._level_order = { self._level_order = {
"TRACE": 0, "TRACE": 0,
"DEBUG": 1, "DEBUG": 1,
@@ -66,15 +72,11 @@ class LokiLogger:
self, self,
level: str, level: str,
message: str, message: str,
service: str,
labels: Optional[Dict[str, str]] = None, labels: Optional[Dict[str, str]] = None,
metadata: Optional[Dict[str, Any]] = None metadata: Optional[Dict[str, Any]] = None,
) -> None: ) -> None:
""" """Envía un log a Loki con los campos mínimos + metadata opcional."""
Envía un log a Loki con los campos mínimos + metadata opcional.
"""
level = level.upper() level = level.upper()
# Soporte retroactivo
if level == "WARNING": if level == "WARNING":
level = "WARN" level = "WARN"
@@ -82,70 +84,62 @@ class LokiLogger:
raise ValueError(f"Nivel no válido: {level}. Debe estar en {self.ALLOWED_LEVELS}") raise ValueError(f"Nivel no válido: {level}. Debe estar en {self.ALLOWED_LEVELS}")
if not self._should_log(level): if not self._should_log(level):
return # No enviar porque está por debajo del umbral return
# Combinar etiquetas # Combinar etiquetas
final_labels = dict(self.default_labels) final_labels = dict(self.default_labels)
if labels: if labels:
final_labels.update(labels) final_labels.update(labels)
# Payload base
payload_metadata = { payload_metadata = {
"service": service, "service": self.service_name,
"level": level "level": level,
} }
if metadata: if metadata:
payload_metadata.update(metadata) payload_metadata.update(metadata)
# Construir línea JSON
log_line = json.dumps({ log_line = json.dumps({
"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,
**payload_metadata **payload_metadata,
}) })
body = { body = {
"streams": [ "streams": [
{ {"stream": final_labels, "values": [[self._current_ns(), log_line]]}
"stream": final_labels,
"values": [
[self._current_ns(), log_line]
]
}
] ]
} }
# Enviar al endpoint Loki
try: try:
resp = requests.post(self.endpoint, json=body, timeout=self.timeout) resp = requests.post(self.endpoint, json=body, timeout=self.timeout)
resp.raise_for_status() resp.raise_for_status()
except Exception as e: except Exception as e:
print(f"Failed to send log to Loki: {e}", flush=True) print(f"Failed to send log to Loki: {e}", flush=True)
# Métodos por nivel estándar # Métodos estándar por nivel
def trace(self, message: str, service: str, labels: Optional[Dict[str, str]] = None, metadata: Optional[Dict[str, Any]] = None): def trace(self, message, labels=None, metadata=None):
self.log("TRACE", message, service, labels, metadata) self.log("TRACE", message, labels, metadata)
def debug(self, message: str, service: str, labels: Optional[Dict[str, str]] = None, metadata: Optional[Dict[str, Any]] = None): def debug(self, message, labels=None, metadata=None):
self.log("DEBUG", message, service, labels, metadata) self.log("DEBUG", message, labels, metadata)
def info(self, message: str, service: str, labels: Optional[Dict[str, str]] = None, metadata: Optional[Dict[str, Any]] = None): def info(self, message, labels=None, metadata=None):
self.log("INFO", message, service, labels, metadata) self.log("INFO", message, labels, metadata)
def warn(self, message: str, service: str, labels: Optional[Dict[str, str]] = None, metadata: Optional[Dict[str, Any]] = None): def warn(self, message, labels=None, metadata=None):
self.log("WARN", message, service, labels, metadata) self.log("WARN", message, labels, metadata)
def warning(self, message: str, service: str, labels: Optional[Dict[str, str]] = None, metadata: Optional[Dict[str, Any]] = None): def warning(self, message, labels=None, metadata=None):
self.log("WARN", message, service, labels, metadata) self.log("WARN", message, labels, metadata)
def error(self, message: str, service: str, labels: Optional[Dict[str, str]] = None, metadata: Optional[Dict[str, Any]] = None): def error(self, message, labels=None, metadata=None):
self.log("ERROR", message, service, labels, metadata) self.log("ERROR", message, labels, metadata)
def fatal(self, message: str, service: str, labels: Optional[Dict[str, str]] = None, metadata: Optional[Dict[str, Any]] = None): def fatal(self, message, labels=None, metadata=None):
self.log("FATAL", message, service, labels, metadata) self.log("FATAL", message, labels, metadata)
def critical(self, message: str, service: str, labels: Optional[Dict[str, str]] = None, metadata: Optional[Dict[str, Any]] = None): def critical(self, message, labels=None, metadata=None):
self.log("CRITICAL", message, service, labels, metadata) self.log("CRITICAL", message, labels, metadata)
def unknown(self, message: str, service: str, labels: Optional[Dict[str, str]] = None, metadata: Optional[Dict[str, Any]] = None): def unknown(self, message, labels=None, metadata=None):
self.log("UNKNOWN", message, service, labels, metadata) self.log("UNKNOWN", message, labels, metadata)