Algunas consideraciones para hacer buen Logging

Algunas consideraciones para hacer buen Logging

El monitoreo en proyectos de software es esencial, especialmente en sistemas grandes y distribuidos. Logging se refiere a la práctica de registrar eventos y datos relevantes en una aplicación. Una gestión adecuada de los registros puede ser la diferencia entre identificar y resolver un problema rápidamente o perder horas buscando un fallo. Como experiencia propia les cuento que tuve un bug de bajo impacto en produccion por varios meses y se resolvió una vez que se implementeron los logs en todas las diferentes aplicaciones, pero casi le celebraba su primer cumpleaños.

¿Por qué es importante el registro de logs?

  1. Monitoreo y alerta: Detectar y recibir notificaciones sobre problemas en tiempo real.
  2. Diagnóstico y solución de problemas: Analizar lo que sucedió durante un incidente.
  3. Auditoría y cumplimiento: Mantener un registro de eventos para revisiones futuras.
  4. Análisis de performance: Evaluar cómo se desempeña la aplicación.
  5. Seguridad: Detectar y registrar intentos de acceso no autorizado o comportamientos sospechosos.
  6. Centralización: Recopilar registros de múltiples fuentes en un solo lugar.
  7. Retención y recuperación: Almacenar registros y poder recuperarlos cuando sea necesario.
  8. Aprendizaje y mejora continua: Analizar eventos pasados para mejorar en el futuro.

Estrategias

1. ID de Rastreo (Trace ID)

El Trace ID es un identificador único que se genera al inicio de una transacción o petición y se propaga a través de todas las llamadas y servicios que toca esa transacción. Sirve como una etiqueta o hilo conductor para todas las operaciones relacionadas con una petición específica, es súmamente útil porque puedes:

  1. Agrupar logs: Todos los logs relacionados con una transacción en particular se pueden agrupar usando el Trace ID.
  2. Rastrear el flujo: Visualizar el camino exacto que tomó una petición a través de los diferentes servicios y operaciones.
  3. Diagnosticar problemas: Si algo sale mal, puedes aislar rápidamente los logs relevantes para esa transacción en particular.

Supongamos que tienes una aplicación web en Flask y esta aplicación hace llamadas a un servicio interno cuando se accede a una determinada ruta. Vamos a implementar un Trace ID en este contexto.

from flask import Flask, request, g
import uuid
import requests

app = Flask(__name__)

# Genera un Trace ID antes de cada petición y lo almacena en el contexto global 'g'.
@app.before_request
def generate_trace_id():
    g.trace_id = str(uuid.uuid4())

@app.route('/')
def index():
    # Usar el Trace ID en logs.
    app.logger.info('[TRACE_ID:%s] Petición recibida en el servicio principal', g.trace_id)

    # Supongamos que hay un servicio interno al que debemos llamar.
    headers = {
        "X-Trace-ID": g.trace_id
    }
    response = requests.get('http://internal-service/', headers=headers)

    app.logger.info('[TRACE_ID:%s] Respuesta recibida del servicio interno', g.trace_id)
    return "Petición procesada con TRACE ID: " + g.trace_id

Ahora, en el "internal-service", también deberías capturar ese Trace ID del encabezado y usarlo en sus logs, asegurando la continuidad del rastreo.

`@app.before_request
def extract_trace_id():
    g.trace_id = request.headers.get('X-Trace-ID', str(uuid.uuid4()))

@app.route('/')
def internal_service_endpoint():
    app.logger.info('[TRACE_ID:%s] Petición recibida en el servicio interno', g.trace_id)
    return "Procesado por el servicio interno."`

2. Logging Contextual

El Logging Contextual se refiere a incluir detalles adicionales en los registros, que proporcionen contexto sobre la operación que se está realizando, agregagando información relevante automáticamente a tus registros, por ej.:

import logging

def process_purchase(user_id, cart_items):
    total_value = sum(item['price'] for item in cart_items)

    # Logging contextual
    logging.info('Compra realizada por el usuario %s. Valor total: $%s. Artículos: %s', user_id, total_value, cart_items)

3. Logging Estructurado

Implica escribir registros en un formato estructurado y estandarizado, como JSON, en lugar de texto libre. Esto facilita el análisis de los logs con herramientas especializadas.

import logging
import json

class StructuredLogger:
    def __init__(self):
        logging.basicConfig(level=logging.INFO)

    def info(self, message, **kwargs):
        log_entry = {"message": message, **kwargs}
        logging.info(json.dumps(log_entry))

logger = StructuredLogger()

def process_purchase(user_id, cart_items):
    total_value = sum(item['price'] for item in cart_items)

    # Logging estructurado
    logger.info('Compra realizada', user_id=user_id, total_value=total_value, cart_items=cart_items)

Y eso produciría una entrada de logs como:

{"message": "Compra realizada", "user_id": "12345", "total_value": 100.5, "cart_items": [...] }

4. Distributed Tracing

Distributed Tracing, o rastreo distribuido, se refiere a la capacidad de rastrear y visualizar una petición a medida que pasa por múltiples servicios en un sistema distribuido. Jaeger es una herramienta que facilita el rastreo distribuido, trabajando conjuntamente con Trace IDs. En Jaeger, puedes visualizar cómo una petición se mueve a través de diversos microservicios, cuánto tiempo pasa en cada servicio y dónde podrían surgir cuellos de botella.

Hay una estrecha relación entre Trace ID y Distributed Tracing, tienen propósitos diferentes, ya mientras que un Trace ID te ofrece un medio para rastrear transacciones de forma básica, las herramientas de Distributed Tracing elevan esto a un nivel superior, ofreciendo visualización, análisis y capacidades de monitoreo avanzadas.

5. Log Aggregation

Herramientas como ELK Stack (Elasticsearch, Logstash, Kibana) son ideales para esto. Yo personalmente he elegido para mis proyectos Loki ques es otra solución de agregación de logs altamente eficiente y Grafana ofrece visualización. A diferencia de otras soluciones, Loki indexa solo los metadatos, lo que hace que el almacenamiento sea más rápido.

6. Niveles de Logging

Normalmente las diferentes librarías de logging ofrecen diferentes niveles como DEBUG, INFO, WARNING, ERROR y CRITICAL como base, pero se pueden externder en SILLY, TRACE, VERBOSE, HTTP, etc.

Es vital saber cuándo usar cada nivel para proporcionar el contexto adecuado.

7. Log Consistency

Mantener una estructura coherente para tus registros, como "[TRACE_ID:{trace_id}] Evento: {event_name}", hace que el análisis sea más manejable. Este punto se refiere a mantener un formato estándar para los mensajes de registro. lo que facilita la lectura, el análisis automático y la identificación de patrones. Un formato común puede incluir un ID de traza, el nivel de registro, la hora del evento, el servicio o módulo de origen, seguido del mensaje real.

Uando un formato consistente podría verse como:

[TRACE_ID:xxxx] [LEVEL] [TIMESTAMP] [SERVICE] Mensaje

Así, un mensaje real podría verse así:

[TRACE_ID:1234] [INFO] [2023-08-27 14:32:21] [AuthService] Usuario 'alice' autenticado con éxito.

Al mantener la coherencia en los registros, facilitas su análisis y seguimiento a lo largo del tiempo; básicamente, sin consistencia, el análisis de logs puede volverse una tarea tediosa y propensa a errores.

La gestión efectiva de registros en sistemas grandes es un arte y una ciencia. Al seguir mejores prácticas y utilizar las herramientas adecuadas, puedes asegurarte de estar bien equipado para monitorear, diagnosticar y mejorar tu aplicación de manera proactiva.

Y definitivamente, por razones de privacidad o seguridad, hay información que no deberías registrar. Asegúrate de no incluir datos sensibles como contraseñas o información personal de los usuarios.

Happy coding! :D


Photo by Cam Adams on Unsplash

Jack Fiallos

Jack Fiallos

Te gustó este artículo?