Rotación y Compresión de Logs
Rota y comprime logs de aplicaciones con Bash para evitar que el disco se llene y simplificar la retención.
Visión General
Los logs de aplicaciones crecen continuamente. Sin rotación, un solo servicio verboso puede llenar el disco, colapsar el host y hacer imposible el análisis de logs. Un script de rotación de logs en Bash renombra logs activos, comprime los antiguos y elimina archivos más allá de una edad de retención. Esto mantiene los logs accesibles, buscables y delimitados en tamaño sin depender de un agente de gestión de logs pesado.
Cuándo Usar
Usa este recurso cuando:
- Necesites rotar logs en un servidor sin logrotate instalado.
- Quieras reglas personalizadas de nomenclatura, compresión o retención.
- Rotes logs para un contenedor o entorno embebido.
- Necesites enviar logs comprimidos a almacenamiento frío.
Solución
Script de rotación de logs en Bash
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="${1:-/var/log/app}"
RETENTION_DAYS="${2:-30}"
MAX_SIZE_MB="${3:-100}"
mkdir -p "$LOG_DIR/archive"
for log in "$LOG_DIR"/*.log; do
[[ -f "$log" ]] || continue
size_mb=$(du -m "$log" | cut -f1)
if (( size_mb > MAX_SIZE_MB )); then
base=$(basename "$log" .log)
timestamp=$(date +%Y%m%d-%H%M%S)
rotated="$LOG_DIR/archive/${base}-${timestamp}.log"
mv "$log" "$rotated"
gzip "$rotated"
touch "$log"
fi
done
# Eliminar logs comprimidos antiguos
find "$LOG_DIR/archive" -name '*.gz' -type f -mtime +$RETENTION_DAYS -delete
# Opcional: señalar a la app para reabrir logs
# kill -HUP "$APP_PID"
echo "Rotación de logs completada"
Explicación
El script itera sobre archivos *.log en el directorio objetivo. Si un log excede MAX_SIZE_MB, renombra el archivo con un timestamp, lo comprime con gzip y crea un nuevo archivo de log vacío. Los archivos comprimidos antiguos se eliminan después de RETENTION_DAYS. El kill -HUP opcional indica a los demonios que esperan reapertura por señal que cierren el descriptor de archivo antiguo y comiencen a escribir en el archivo nuevo. Esto es común para servicios de larga ejecución como nginx o aplicaciones personalizadas.
Variantes
| Variante | Compresión | Caso de uso |
|---|---|---|
| gzip | Por defecto | Buen equilibrio, ampliamente soportado |
| bzip2 | Más lento, más pequeño | Archivos a largo plazo |
| zstd | Rápido, moderno | Grandes volúmenes de logs |
| xz | Más pequeño, más lento | Archivos de cumplimiento |
Lo que funciona
- Rota antes de que el disco se llene. Monitorea el espacio libre y rota al 70-80% de uso, no al 99%.
- Usa copytruncate o señales cuando sea posible. Mover un archivo de log abierto puede hacer que la app siga escribiendo en el antiguo inode.
- Mantén permisos restrictivos en archivos. Los logs pueden contener datos sensibles; usa
chmod 640en archivos. - Prueba la rotación en una copia primero. Un script malo puede borrar logs activos; valida contra un directorio no productivo.
- Centraliza logs después de la rotación. Envía logs comprimidos a S3, Loki o Elasticsearch para retención y búsqueda.
Errores Comunes
- Borrar logs antes de comprimirlos. El paso de archivado puede fallar; conserva el original hasta que gzip tenga éxito.
- Rotar el mismo archivo dos veces. Los nombres con timestamp evitan sobrescribir el mismo archivo.
- No manejar logs en subdirectorios. Usa
findcon-maxdepthsi los logs están anidados. - Ejecutar como root innecesariamente. Usa la cuenta de servicio que posee los archivos de log.
- Olvidar reabrir descriptores de archivo. La app puede seguir escribiendo en el archivo movido; envía HUP o usa copytruncate.
Preguntas frecuentes
Configuración de logrotate (enfoque estándar de Linux)
# /etc/logrotate.d/myapp
/var/log/app/*.log {
daily
rotate 30
compress
compresscmd /usr/bin/zstd
compressext .zst
delaycompress
missingok
notifempty
create 640 appuser appgroup
dateext
dateformat -%Y%m%d-%H%M%S
sharedscripts
postrotate
# Señalar a la app para reabrir archivos de log
if [ -f /var/run/app.pid ]; then
kill -HUP "$(cat /var/run/app.pid)"
fi
endscript
}
# Probar configuración de logrotate sin hacer cambios
logrotate -d /etc/logrotate.d/myapp
# Forzar rotación ahora
logrotate -f /etc/logrotate.d/myapp
# Verificar que la rotación ocurrió
ls -la /var/log/app/
Rotación con copytruncate (sin señal necesaria)
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="${1:-/var/log/app}"
RETENTION_DAYS="${2:-30}"
MAX_SIZE_MB="${3:-100}"
mkdir -p "$LOG_DIR/archive"
for log in "$LOG_DIR"/*.log; do
[[ -f "$log" ]] || continue
size_mb=$(du -m "$log" | cut -f1)
if (( size_mb > MAX_SIZE_MB )); then
base=$(basename "$log" .log)
timestamp=$(date +%Y%m%d-%H%M%S)
rotated="$LOG_DIR/archive/${base}-${timestamp}.log"
# Copiar y truncar — la app sigue escribiendo al mismo descriptor de archivo
cp "$log" "$rotated"
: > "$log" # Truncar in place
gzip "$rotated"
chmod 640 "${rotated}.gz"
fi
done
# Eliminar logs comprimidos antiguos
find "$LOG_DIR/archive" -name '*.gz' -type f -mtime +$RETENTION_DAYS -delete
echo "[$(date -Iseconds)] Rotación copytruncate completada para $LOG_DIR"
Compresión zstd para grandes volúmenes de logs
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="${1:-/var/log/app}"
COMPRESSION_LEVEL="${2:-3}" # zstd: 1=rápido, 19=máximo, 3=por defecto
# zstd ofrece compresión 3-5x más rápida que gzip con ratios similares
# Instalar: apt install zstd O yum install zstd
for log in "$LOG_DIR"/*.log; do
[[ -f "$log" ]] || continue
size_mb=$(du -m "$log" | cut -f1)
if (( size_mb > 100 )); then
base=$(basename "$log" .log)
timestamp=$(date +%Y%m%d-%H%M%S)
rotated="$LOG_DIR/archive/${base}-${timestamp}.log"
mv "$log" "$rotated"
zstd -"$COMPRESSION_LEVEL" --rm "$rotated"
touch "$log"
echo "Rotado y comprimido: ${rotated}.zst"
fi
done
# Benchmark: zstd -3 vs gzip -6 en un archivo de log de 500MB
# gzip -6: 12.3s, ratio 4.2x
# zstd -3: 2.1s, ratio 4.0x (6x más rápido, ratio similar)
Envío de logs a S3 después de la rotación
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="${1:-/var/log/app}"
S3_BUCKET="${2:-my-app-logs}"
S3_PREFIX="${3:-logs/$(hostname)}"
RETENTION_DAYS="${4:-90}"
# Subir logs rotados y comprimidos a S3
find "$LOG_DIR/archive" -name '*.gz' -type f -mtime +1 | while read -r archive; do
rel_path="${archive#$LOG_DIR/archive/}"
s3_key="$S3_PREFIX/$rel_path"
aws s3 cp "$archive" "s3://$S3_BUCKET/$s3_key" \
--storage-class STANDARD_IA \
--metadata "host=$(hostname),date=$(date -I)" \
--no-progress
if [ $? -eq 0 ]; then
echo "[$(date -Iseconds)] Subido $archive a s3://$S3_BUCKET/$s3_key"
rm "$archive"
else
echo "[$(date -Iseconds)] FALLO al subir $archive"
fi
done
# Eliminar archivos locales anteriores a la retención (ya subidos a S3)
find "$LOG_DIR/archive" -name '*.gz' -type f -mtime +$RETENTION_DAYS -delete
# Política de ciclo de vida S3 mueve logs a Glacier después de 90 días, elimina después de 365
# Configurada vía: aws s3api put-bucket-lifecycle-configuration
Rotación de logs en Python con RotatingFileHandler
import logging
from logging.handlers import RotatingFileHandler
import gzip
import os
from pathlib import Path
class CompressedRotatingFileHandler(RotatingFileHandler):
"""RotatingFileHandler que comprime logs rotados con gzip."""
def __init__(self, filename, max_bytes=100*1024*1024, backup_count=30, encoding='utf-8'):
super().__init__(filename, maxBytes=max_bytes, backupCount=backup_count, encoding=encoding)
def rotate(self, source, dest):
super().rotate(source, dest)
if os.path.exists(dest):
with open(dest, 'rb') as f_in:
with gzip.open(f'{dest}.gz', 'wb') as f_out:
f_out.writelines(f_in)
os.remove(dest)
def getFilesToDelete(self):
dir_name, base_name = os.path.split(self.baseFilename)
files = super().getFilesToDelete()
# También limpiar archivos comprimidos
for i in range(self.backupCount + 1, self.backupCount + 100):
compressed = os.path.join(dir_name, f'{base_name}.{i}.gz')
if os.path.exists(compressed):
files.append(compressed)
return files
# Uso
logger = logging.getLogger('app')
handler = CompressedRotatingFileHandler(
'/var/log/app/application.log',
max_bytes=100*1024*1024, # 100MB
backup_count=30,
)
handler.setFormatter(logging.Formatter(
'%(asctime)s [%(levelname)s] %(name)s: %(message)s'
))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# Log messages — la rotación ocurre automáticamente
for i in range(10000):
logger.info(f"Procesando registro {i}")
Recursos Relacionados
Comprimir y Descomprimir Archivos
Cómo manejar archivos ZIP, GZIP y TAR programáticamente.
RecipeScripting en Bash para Automatizacion DevOps y Tareas de
Como escribir scripts Bash robustos para automatizar despliegues, monitoreo de sistemas, rotacion de logs y tareas de mantenimiento rutinarias
RecipeScript de Rotación de Backups
Automatiza backups con políticas de retención usando un script de Bash que rota snapshots diarios, semanales y mensuales.
RecipeLoop Sobre Archivos en Bash
Cómo iterar de forma segura sobre archivos y directorios en Bash, manejando espacios, globs y listas grandes de archivos con patrones correctos.
RecipeEjecución Paralela en Bash
Cómo ejecutar comandos de shell en paralelo con xargs, GNU parallel y trabajos en segundo plano de Bash controlando la concurrencia y recolectando resultados.
RecipeAutomatización de AWS CLI con Bash
Automatiza el aprovisionamiento, etiquetado y limpieza de recursos de AWS usando scripts de Bash envueltos alrededor de AWS CLI.