Rotar Archivos de Log
Cómo implementar rotación de logs por tamaño, fecha y cantidad para prevenir la exaustión de disco en Python, Node.js, Java y Linux.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Descripción General
La rotación de logs previene que un solo archivo de log crezca sin límite y agote el espacio en disco. Una estrategia adecuada de rotación comprime logs antiguos, mantiene un número configurable de backups y opcionalmente elimina archivos que exceden una edad de retención. Esta receta muestra rotación basada en tamaño y tiempo en Python, Node.js, Java y Linux.
Cuándo Usar
- Los logs de aplicación crecen continuamente y arriesgan llenar el disco
- Necesitas retener logs históricos para cumplimiento o debugging
- Las herramientas de análisis de logs prefieren archivos más pequeños acotados en tiempo
- Quieres comprimir logs antiguos para reducir costos de almacenamiento
- Múltiples procesos escriben al mismo archivo de log
Cuándo NO Usar
- Estás usando un servicio de logging centralizado (Datadog, Splunk, ELK) que ingiere desde stdout/stderr — deja que la plataforma maneje la retención
- Necesitas búsqueda de logs a nivel de milisegundo a través de todo el historial — usa una base de datos de logs en su lugar
- Tu aplicación corre como contenedores efímeros con sistemas de archivos de solo lectura — envía a stdout
Implementación Paso a Paso
Python
import logging
import logging.handlers
# Rotación por tamaño: máximo 10MB, mantener 5 backups
handler = logging.handlers.RotatingFileHandler(
'app.log',
maxBytes=10 * 1024 * 1024, # 10 MB
backupCount=5,
encoding='utf-8'
)
handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s %(name)s: %(message)s'
))
logger = logging.getLogger('myapp')
logger.setLevel(logging.INFO)
logger.addHandler(handler)
# Rotación por tiempo: diaria a medianoche, mantener 30 días
from logging.handlers import TimedRotatingFileHandler
timed_handler = TimedRotatingFileHandler(
'app_daily.log',
when='midnight',
interval=1,
backupCount=30,
encoding='utf-8',
utc=True
)
timed_handler.suffix = '%Y-%m-%d'
timed_handler.extMatch = r'^\d{4}-\d{2}-\d{2}$'
logger.addHandler(timed_handler)
# WatchedFileHandler para rotación externa (compatibilidad con logrotate)
from logging.handlers import WatchedFileHandler
watched = WatchedFileHandler('app.log')
logger.addHandler(watched)
Node.js
import winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
// Rotación por tamaño con Winston
const sizeTransport = new winston.transports.File({
filename: 'app.log',
maxsize: 10 * 1024 * 1024, // 10 MB
maxFiles: 5,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
)
});
// Rotación diaria
const dailyTransport = new DailyRotateFile({
filename: 'app-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '30d',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
)
});
const logger = winston.createLogger({
level: 'info',
transports: [sizeTransport, dailyTransport]
});
// Limpieza automática de archivos antiguos
dailyTransport.on('rotate', (oldFilename, newFilename) => {
console.log(`Rotado log: ${oldFilename} -> ${newFilename}`);
});
Java
import java.util.logging.*;
// Usando java.util.logging con rotación personalizada
public class LogRotationExample {
public static void setupLogging() throws Exception {
Logger logger = Logger.getLogger("myapp");
logger.setLevel(Level.INFO);
// Rotación por tamaño: 10MB, 5 backups
FileHandler fileHandler = new FileHandler(
"app.log", // patrón
10 * 1024 * 1024, // límite en bytes
5, // cantidad
true // append
);
fileHandler.setFormatter(new SimpleFormatter());
logger.addHandler(fileHandler);
}
}
// Logback (más común en producción)
// logback.xml:
/*
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
*/
Linux (logrotate)
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily # Rotar diariamente
missingok # OK si el archivo de log no existe
rotate 30 # Mantener 30 backups
compress # Comprimir logs antiguos con gzip
delaycompress # Comprimir la rotación después de la siguiente
notifempty # No rotar archivos vacíos
create 0644 appuser appuser
sharedscripts # Ejecutar postrotate una vez para todos los archivos
dateext # Usar fecha en lugar de número como sufijo
dateformat -%Y%m%d
postrotate
# Señalizar a la aplicación para reabrir el archivo de log
kill -HUP $(cat /var/run/myapp.pid) > /dev/null 2>&1 || true
endscript
}
# Rotación por tamaño con logrotate
/var/log/myapp/app.log {
size 100M # Rotar cuando el archivo exceda 100MB
rotate 10
compress
copytruncate # Copiar luego truncar (sin señal necesaria)
delaycompress
}
Mejores Prácticas
- Usa
copytruncateocreatecon una señal postrotate para evitar perder entradas de log entre la copia y la reapertura. Las aplicaciones deben manejarSIGHUPpara reabrir descriptores de archivo. - Configura
totalSizeCapo equivalente para limitar el almacenamiento total entre todos los logs rotados, no solo la cantidad de archivos. - Comprime logs rotados para reducir almacenamiento en un 80-95%. Usa
delaycompresspara mantener el backup más reciente sin comprimir para acceso inmediato con grep. - Ejecuta logrotate con
-d(modo debug) antes de desplegar en producción para verificar rutas y permisos sin hacer cambios. - Monitorea el uso de disco de forma independiente. La rotación es una red de seguridad, no un sustituto de la planificación de capacidad.
Errores Comunes
- No manejar la señal de reapertura en la aplicación. La aplicación continúa escribiendo al inodo viejo después de la rotación, haciendo que el archivo eliminado siga consumiendo espacio hasta que el proceso se reinicia.
- Usar
copytruncatecon writers con buffer. Los datos en buffer de la aplicación pueden perderse cuando el archivo es truncado. - Configurar
maxFilesobackupCountdemasiado bajo para cumplimiento. 5 backups de 10MB cada uno son solo 50MB de historial — insuficiente para la mayoría del debugging en producción. - Ignorar zonas horarias en
TimedRotatingFileHandler. Usautc=Truepara evitar comportamiento ambiguo durante transiciones de horario de verano. - Ejecutar múltiples instancias de la aplicación con el mismo archivo de log. Writers concurrentes sin un mecanismo de bloqueo entrelazan líneas de log o corrompen el archivo.