Tema:
Docker Logs
Introducción
Docker es una herramienta poderosa para el despliegue de aplicaciones contenerizadas, y su sistema de logging es un componente esencial para monitorear, depurar y mantener aplicaciones en contenedores. Los logs en Docker permiten capturar la salida de las aplicaciones, proporcionando visibilidad sobre su comportamiento, errores y eventos importantes durante su ejecución.
Los logs en Docker no solo facilitan la identificación de problemas, sino que también ofrecen control sobre aspectos como la retención de logs, formato, drivers de almacenamiento y centralización de logs, haciéndolo una pieza clave en la observabilidad de sistemas modernos basados en contenedores.
Objetivo
Objetivo General:
- El objetivo de este tema es proporcionar una comprensión completa del sistema de logging en Docker, las mejores prácticas para implementarlo y cómo desarrollar aplicaciones que envíen logs correctamente a stdout y stderr.
Fundamentos de Docker Logs
¿Qué son los Docker Logs?
Docker captura automáticamente todo lo que una aplicación escribe a: - STDOUT (Standard Output): Salida estándar para mensajes informativos y de operación normal - STDERR (Standard Error): Salida de error estándar para mensajes de error y advertencias
Los logs se almacenan en el host de Docker y pueden ser consultados usando el comando docker logs.
Visualización de Logs
Ver los logs de un contenedor en ejecución:
docker logs <container_name_or_id>
Ver logs en tiempo real (seguir logs):
docker logs -f <container_name_or_id>
Ver las últimas N líneas de logs:
docker logs --tail 100 <container_name_or_id>
Ver logs con marcas de tiempo:
docker logs -t <container_name_or_id>
Ver logs desde una fecha específica:
docker logs --since 2024-12-09T10:00:00 <container_name_or_id>
Combinar opciones:
docker logs -f --tail 50 -t <container_name_or_id>
Mejores Prácticas para Docker Logs
1. Escribir Logs a STDOUT y STDERR
✅ Recomendado: Las aplicaciones deben enviar logs directamente a stdout y stderr, no a archivos.
Razones: - Docker captura automáticamente stdout/stderr - Facilita la agregación centralizada de logs - Simplifica la gestión de logs en entornos distribuidos - Evita problemas de espacio en disco dentro del contenedor - Sigue el principio de "Twelve-Factor App"
❌ Evitar: Escribir logs a archivos dentro del contenedor - Consume espacio en la capa de escritura del contenedor - Dificulta el acceso a los logs - Los logs se pierden cuando el contenedor se elimina
2. Utilizar Niveles de Log Apropiados
Implementar niveles de log estándar: - ERROR: Errores que requieren atención inmediata - WARN: Advertencias sobre situaciones anómalas pero no críticas - INFO: Información general sobre operaciones normales - DEBUG: Información detallada para depuración
3. Incluir Contexto en los Logs
Los logs deben incluir: - Marca de tiempo (timestamp) - Nivel de severidad - Identificador de solicitud o transacción - Información contextual relevante (usuario, recurso, acción)
Ejemplo de formato estructurado (JSON):
{"timestamp":"2024-12-09T10:30:45Z","level":"ERROR","service":"api","message":"Database connection failed","error":"connection timeout"}
4. Configurar Rotación de Logs
Evitar que los logs consuman todo el espacio en disco:
docker run -d \
--log-opt max-size=10m \
--log-opt max-file=3 \
nginx
5. Usar Logging Drivers Apropiados
Docker soporta múltiples drivers de logging según las necesidades:
| Driver | Descripción | Uso Recomendado |
|---|---|---|
json-file |
Driver por defecto, almacena logs en JSON | Desarrollo y testing |
syslog |
Envía logs a syslog | Integración con sistemas Unix/Linux |
journald |
Envía logs a journald | Sistemas con systemd |
gelf |
Envía logs a endpoints GELF (Graylog) | Centralización con Graylog |
fluentd |
Envía logs a Fluentd | Agregación avanzada de logs |
awslogs |
Envía logs a AWS CloudWatch | Aplicaciones en AWS |
splunk |
Envía logs a Splunk | Ambientes enterprise con Splunk |
Desarrollando Aplicaciones que Envían Logs a STDOUT/STDERR
Python
Ejemplo básico:
import sys
import logging
# Configurar logging para enviar a stdout
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
# Logs informativos a stdout
logger.info("Aplicación iniciada correctamente")
logger.debug("Procesando solicitud del usuario")
# Logs de error a stderr
try:
result = 10 / 0
except Exception as e:
logger.error(f"Error en operación: {e}", exc_info=True)
Ejemplo con JSON (estructurado):
import json
import sys
from datetime import datetime
def log_json(level, message, **kwargs):
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"level": level,
"message": message,
**kwargs
}
print(json.dumps(log_entry), file=sys.stdout)
log_json("INFO", "Usuario autenticado", user_id=12345, ip="192.168.1.1")
log_json("ERROR", "Conexión a BD fallida", error="timeout", retry_count=3)
Node.js
Ejemplo básico:
// Logs a stdout
console.log('INFO: Servidor iniciado en puerto 3000');
console.info('INFO: Conexión a base de datos exitosa');
// Logs a stderr
console.error('ERROR: No se pudo conectar a Redis');
console.warn('WARN: Límite de rate limiting alcanzado');
// Logs estructurados en JSON
const logInfo = (message, metadata = {}) => {
const logEntry = {
timestamp: new Date().toISOString(),
level: 'INFO',
message,
...metadata
};
console.log(JSON.stringify(logEntry));
};
const logError = (message, error, metadata = {}) => {
const logEntry = {
timestamp: new Date().toISOString(),
level: 'ERROR',
message,
error: error.message,
stack: error.stack,
...metadata
};
console.error(JSON.stringify(logEntry));
};
logInfo('Usuario creado', { userId: 123, username: 'john_doe' });
Ejemplo con Winston (librería popular):
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
)
})
]
});
logger.info('Servidor iniciado', { port: 3000, env: 'production' });
logger.error('Error en base de datos', { error: 'connection timeout', db: 'postgres' });
Java (Spring Boot)
Configuración en application.properties:
# Enviar logs a stdout
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
logging.level.root=INFO
logging.level.com.myapp=DEBUG
Ejemplo de código:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
logger.info("Iniciando aplicación Spring Boot");
try {
SpringApplication.run(Application.class, args);
logger.info("Aplicación iniciada exitosamente");
} catch (Exception e) {
logger.error("Error al iniciar aplicación", e);
System.exit(1);
}
}
}
Go
Ejemplo básico:
package main
import (
"encoding/json"
"log"
"os"
"time"
)
type LogEntry struct {
Timestamp string `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
}
func logJSON(level, message string) {
entry := LogEntry{
Timestamp: time.Now().UTC().Format(time.RFC3339),
Level: level,
Message: message,
}
jsonData, _ := json.Marshal(entry)
if level == "ERROR" {
os.Stderr.Write(jsonData)
os.Stderr.WriteString("\n")
} else {
os.Stdout.Write(jsonData)
os.Stdout.WriteString("\n")
}
}
func main() {
// Configurar logger estándar para stdout
log.SetOutput(os.Stdout)
log.Println("INFO: Aplicación iniciada")
// Logs estructurados
logJSON("INFO", "Servidor HTTP iniciado en :8080")
logJSON("ERROR", "No se pudo conectar a la base de datos")
}
Laboratorio 1: Explorando Docker Logs
Objetivo
Familiarizarse con los comandos de visualización de logs y entender cómo Docker captura stdout y stderr.
-
Crear un contenedor que genere logs continuamente:
docker run -d --name log-generator alpine sh -c \ "while true; do echo 'INFO: Operación normal'; sleep 2; echo 'ERROR: Algo salió mal' >&2; sleep 3; done" -
Ver todos los logs del contenedor:
docker logs log-generator -
Seguir los logs en tiempo real:
docker logs -f log-generator📝 Presione Ctrl+C para detener el seguimiento de logs.
-
Ver solo las últimas 10 líneas:
docker logs --tail 10 log-generator -
Ver logs con marcas de tiempo:
docker logs -t log-generator -
Ver logs desde hace 1 minuto:
docker logs --since 1m log-generator -
Inspeccionar la ubicación de los logs en el host:
docker inspect log-generator | grep LogPath -
Ver el contenido del archivo de logs directamente:
sudo cat $(docker inspect log-generator | grep LogPath | awk '{print $2}' | tr -d '",')
Limpieza de ambiente
docker stop log-generator
docker rm log-generator
Laboratorio 2: Aplicación Python con Logs Estructurados
Objetivo
Crear una aplicación Python que envíe logs estructurados en formato JSON a stdout y stderr.
-
Crear un directorio para el proyecto:
mkdir ~/docker-logs-lab && cd ~/docker-logs-lab -
Crear el archivo de la aplicación Python (
app.py):import json import sys import time from datetime import datetime import random def log_json(level, message, **kwargs): log_entry = { "timestamp": datetime.utcnow().isoformat() + "Z", "level": level, "service": "api-demo", "message": message, **kwargs } output = sys.stdout if level in ["INFO", "DEBUG"] else sys.stderr print(json.dumps(log_entry), file=output, flush=True) def main(): log_json("INFO", "Aplicación iniciada", version="1.0.0") counter = 0 while True: counter += 1 # Simular diferentes tipos de logs if counter % 5 == 0: log_json("ERROR", "Error simulado en operación", request_id=f"req-{counter}", error_code=500) elif counter % 3 == 0: log_json("WARN", "Latencia alta detectada", request_id=f"req-{counter}", latency_ms=random.randint(1000, 3000)) else: log_json("INFO", "Solicitud procesada exitosamente", request_id=f"req-{counter}", duration_ms=random.randint(10, 200)) time.sleep(2) if __name__ == "__main__": try: main() except KeyboardInterrupt: log_json("INFO", "Aplicación detenida por usuario") sys.exit(0) except Exception as e: log_json("ERROR", "Error fatal en aplicación", error=str(e)) sys.exit(1) -
Crear el Dockerfile:
FROM python:3.11-alpine WORKDIR /app COPY app.py . CMD ["python", "-u", "app.py"] -
Construir la imagen:
docker build -t python-logs-demo . -
Ejecutar el contenedor sin limitación de logs (⚠️ no recomendado en producción):
docker run -d --name logs-unlimited python-logs-demo -
Ver los logs estructurados:
docker logs -f --tail 20 logs-unlimited -
Filtrar solo logs de ERROR usando
jq:docker logs logs-unlimited 2>&1 | grep ERROR | jq . -
Detener el contenedor sin limitación:
docker stop logs-unlimited docker rm logs-unlimited -
Ejecutar el contenedor con rotación de logs (✅ recomendado):
docker run -d --name logs-limited \ --log-opt max-size=5m \ --log-opt max-file=3 \ python-logs-demo -
Verificar la configuración de logs:
docker inspect logs-limited | jq '.[0].HostConfig.LogConfig' -
Seguir los logs y observar el formato JSON:
docker logs -f logs-limited
📝 Los logs están estructurados en JSON, lo que facilita su procesamiento por sistemas de agregación como ELK Stack, Splunk, o Graylog.
Limpieza de ambiente
docker stop logs-limited
docker rm logs-limited
docker rmi python-logs-demo
Laboratorio 3: Configuración de Logging Drivers
Objetivo
Explorar diferentes drivers de logging y entender cómo redirigir logs a diferentes destinos.
-
Ejecutar contenedor con driver json-file (por defecto):
docker run -d --name nginx-json \ --log-driver json-file \ --log-opt max-size=1m \ --log-opt max-file=2 \ nginx -
Verificar el driver de logging:
docker inspect nginx-json | jq '.[0].HostConfig.LogConfig' -
Generar tráfico para producir logs:
docker exec nginx-json curl -s http://localhost > /dev/null -
Ver los logs:
docker logs nginx-json -
Ejecutar contenedor con driver syslog (si está disponible):
docker run -d --name nginx-syslog \ --log-driver syslog \ --log-opt syslog-address=udp://127.0.0.1:514 \ --log-opt tag="nginx-container" \ nginx
📝 Nota: Este contenedor enviará logs a syslog. Si no tienes un servidor syslog configurado, el contenedor podría no iniciar correctamente.
- Configurar logging a nivel de daemon de Docker (opcional, requiere permisos root).
Editar /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3",
"labels": "production_status",
"env": "os,customer"
}
}
Reiniciar Docker:
sudo systemctl restart docker
Limpieza de ambiente
docker stop nginx-json nginx-syslog 2>/dev/null
docker rm nginx-json nginx-syslog 2>/dev/null