Log rotation e cleanup automatizado

1. Fundamentos do Log Rotation

Log rotation é o processo de gerenciar arquivos de log que crescem continuamente, dividindo-os em arquivos menores e removendo versões antigas. Sem rotação, um único arquivo de log pode consumir gigabytes de espaço em disco e degradar a performance de I/O do sistema.

A estrutura de nomes típica inclui timestamp (meuapp.log.2025-03-28), sequência numérica (meuapp.log.1, meuapp.log.2) ou compressão automática (meuapp.log.2025-03-28.gz). A compressão com gzip ou bzip2 reduz o tamanho dos logs antigos em 80-90%.

É crucial distinguir rotation de truncation. Rotation move o arquivo atual para um novo nome e cria um arquivo vazio para continuar logging. Truncation simplesmente esvazia o arquivo existente, o que pode causar perda de dados se o processo gravador não for notificado. Sempre prefira rotation com sinalização adequada aos processos.

2. Implementação com logrotate via Shell

O logrotate é a ferramenta padrão no Linux. Sua configuração global fica em /etc/logrotate.conf, e configurações específicas em /etc/logrotate.d/.

Exemplo de configuração para logs do nginx em /etc/logrotate.d/nginx:

/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 640 nginx adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
    endscript
}

Para forçar rotação manual e testar configurações:

# Forçar rotação imediata
sudo logrotate -f /etc/logrotate.d/nginx

# Modo debug: mostra o que seria feito sem executar
sudo logrotate -d /etc/logrotate.d/nginx

# Verificar última execução
cat /var/lib/logrotate/status

3. Script Customizado de Rotation com Bash

Quando o logrotate não atende necessidades específicas, um script Bash oferece controle total. Exemplo funcional:

#!/bin/bash

LOG_DIR="/var/log/meuapp"
LOG_FILE="meuapp.log"
MAX_SIZE=10485760  # 10MB em bytes
RETENTION_DAYS=30
LOCK_FILE="/tmp/rotate_meuapp.lock"

# Evitar execução concorrente
exec 200>"$LOCK_FILE"
flock -n 200 || { echo "Rotação já em execução"; exit 1; }

# Detectar se o log está ativo (evitar conflito de escrita)
if lsof "$LOG_DIR/$LOG_FILE" > /dev/null 2>&1; then
    echo "AVISO: Log está sendo escrito no momento"
fi

# Verificar tamanho atual
CURRENT_SIZE=$(stat -c%s "$LOG_DIR/$LOG_FILE" 2>/dev/null || echo 0)

if [ "$CURRENT_SIZE" -ge "$MAX_SIZE" ]; then
    TIMESTAMP=$(date +%Y-%m-%d_%H%M%S)

    # Mover e comprimir
    mv "$LOG_DIR/$LOG_FILE" "$LOG_DIR/${LOG_FILE}.${TIMESTAMP}"
    gzip "$LOG_DIR/${LOG_FILE}.${TIMESTAMP}"

    # Criar novo arquivo vazio com permissões corretas
    touch "$LOG_DIR/$LOG_FILE"
    chmod 640 "$LOG_DIR/$LOG_FILE"

    echo "Log rotacionado: ${LOG_FILE}.${TIMESTAMP}.gz"
fi

# Cleanup de logs antigos
find "$LOG_DIR" -name "${LOG_FILE}.*.gz" -mtime +$RETENTION_DAYS -delete

flock -u 200

4. Políticas de Cleanup e Retenção

Definir políticas claras evita surpresas. Duas abordagens comuns:

Retenção por tempo (mais comum):

# Remover logs com mais de 30 dias
find /var/log/meuapp -name "*.gz" -mtime +30 -delete

# Ou mover para archive antes de deletar
find /var/log/meuapp -name "*.gz" -mtime +30 -exec mv {} /archive/ \;

Retenção por quantidade (útil quando o volume diário varia):

#!/bin/bash
LOG_DIR="/var/log/meuapp"
MAX_FILES=14

# Contar arquivos de log compactados
FILE_COUNT=$(ls -1 "$LOG_DIR"/*.gz 2>/dev/null | wc -l)

if [ "$FILE_COUNT" -gt "$MAX_FILES" ]; then
    # Remover os mais antigos até atingir o limite
    TO_REMOVE=$((FILE_COUNT - MAX_FILES))
    ls -1t "$LOG_DIR"/*.gz | tail -n "$TO_REMOVE" | xargs rm -f
    echo "Removidos $TO_REMOVE arquivos antigos"
fi

Cleanup condicional preserva os últimos N arquivos mesmo se antigos:

# Preservar sempre os 7 arquivos mais recentes, depois limpar por idade
ls -1t /var/log/meuapp/*.gz | tail -n +8 | xargs rm -f 2>/dev/null
find /var/log/meuapp -name "*.gz" -mtime +60 -delete

5. Tratamento de Logs de Aplicações Específicas

Nginx/Apache: usar kill -USR1 ou kill -HUP para recarregar sem perder conexões.

postrotate
    [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript

Docker: logs JSON podem crescer rapidamente. Limpeza segura:

# Limitar logs do container a 10MB e 3 arquivos
docker run --log-opt max-size=10m --log-opt max-file=3 meuapp

# Limpeza manual de logs parados
docker container prune --force
docker system prune --volumes --force

# Verificar tamanho dos logs
du -sh /var/lib/docker/containers/*/*-json.log

Logs do sistema (/var/log/syslog, auth.log): usar logrotate padrão do sistema, nunca manipular manualmente sem entender as permissões e processos envolvidos.

# Configuração segura para syslog
/var/log/syslog
/var/log/auth.log {
    rotate 7
    daily
    missingok
    notifempty
    delaycompress
    compress
    postrotate
        systemctl restart rsyslog > /dev/null 2>&1 || true
    endscript
}

6. Automação com Cron e Systemd Timers

Cron job para execução diária às 3h da manhã:

# Editar crontab: crontab -e
0 3 * * * /usr/local/bin/rotate_logs.sh >> /var/log/rotation.log 2>&1

Systemd timer (mais moderno e confiável):

# /etc/systemd/system/rotate-logs.service
[Unit]
Description=Rotation de logs do sistema

[Service]
Type=oneshot
ExecStart=/usr/local/bin/rotate_logs.sh
StandardOutput=journal
StandardError=journal

# /etc/systemd/system/rotate-logs.timer
[Unit]
Description=Timer para rotação diária de logs

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Ativar o timer:

sudo systemctl daemon-reload
sudo systemctl enable rotate-logs.timer
sudo systemctl start rotate-logs.timer

Importante: o script de rotation deve logar suas próprias ações em um arquivo separado para evitar loops infinitos:

ROTATION_LOG="/var/log/rotation_audit.log"
echo "$(date): Rotação executada, $(ls /var/log/meuapp/*.gz | wc -l) arquivos" >> "$ROTATION_LOG"

7. Monitoramento e Alertas

Verificação de espaço e notificação em caso de falha:

#!/bin/bash

LOG_DIR="/var/log/meuapp"
THRESHOLD=80  # Percentual de uso do disco
ALERT_EMAIL="admin@exemplo.com"

# Verificar espaço antes da rotação
DISK_USAGE=$(df /var/log | awk 'NR==2 {print $5}' | sed 's/%//')

if [ "$DISK_USAGE" -gt "$THRESHOLD" ]; then
    echo "ALERTA: Disco em /var/log está com ${DISK_USAGE}% de uso"
fi

# Executar rotação
/usr/local/bin/rotate_logs.sh
ROTATION_EXIT=$?

# Verificar espaço depois
NEW_DISK_USAGE=$(df /var/log | awk 'NR==2 {print $5}' | sed 's/%//')
SPACE_FREED=$((DISK_USAGE - NEW_DISK_USAGE))

# Relatório
REPORT="Rotação executada em $(date)
Exit code: $ROTATION_EXIT
Espaço antes: ${DISK_USAGE}%
Espaço depois: ${NEW_DISK_USAGE}%
Espaço liberado: ${SPACE_FREED}%
Logs rotacionados: $(ls -1 $LOG_DIR/*.gz 2>/dev/null | wc -l)"

echo "$REPORT"

# Notificar em caso de erro
if [ "$ROTATION_EXIT" -ne 0 ]; then
    echo "$REPORT" | mail -s "Falha na rotação de logs" "$ALERT_EMAIL"
    curl -X POST -H "Content-Type: application/json" \
         -d "{\"text\":\"Falha na rotação de logs em $(hostname)\"}" \
         https://hooks.slack.com/services/SEU/WEBHOOK
fi

8. Boas Práticas e Armadilhas Comuns

Sempre usar flock para evitar execução concorrente:

LOCK_FILE="/tmp/rotate_logs.lock"
exec 200>"$LOCK_FILE"
flock -n 200 || { echo "Já está rodando"; exit 1; }
# ... rotação ...
flock -u 200

Cuidado com links simbólicos: nunca rotacionar o link em si, mas o arquivo alvo:

# Verificar se é link simbólico antes de rotacionar
if [ -L "$LOG_DIR/$LOG_FILE" ]; then
    echo "ERRO: $LOG_DIR/$LOG_FILE é um link simbólico"
    exit 1
fi

Testar em staging antes de produção:

# Criar ambiente de teste
mkdir -p /tmp/test_logs
cp /var/log/meuapp.log /tmp/test_logs/
./rotate_logs.sh --dry-run --log-dir /tmp/test_logs

Outras armadilhas comuns:
- Não esquecer de recarregar o processo após rotação
- Verificar permissões do novo arquivo (muitas vezes o processo não consegue escrever)
- Não usar rm -f sem antes verificar se o arquivo não está sendo usado
- Configurar rotação antes do disco encher completamente (monitore com df -h)

Referências