Dicas para configurar cron com logging e notificação de falhas

1. Fundamentos do cron e redirecionamento de saída

O cron é um dos utilitários mais antigos e confiáveis do Unix, mas sua simplicidade pode enganar. Por padrão, a saída de um job cron é enviada por email ao proprietário do crontab — algo que raramente funciona em ambientes modernos sem um MTA configurado. A primeira lição é: nunca confie no comportamento padrão do cron para logging.

A sintaxe básica do crontab segue o formato minuto hora dia mês semana comando. Para redirecionar a saída corretamente, use:

# Exemplo básico com redirecionamento
0 2 * * * /opt/scripts/backup.sh >> /var/log/cron/backup.log 2>&1

O operador >> anexa a saída padrão ao arquivo, enquanto 2>&1 redireciona a saída de erro para o mesmo destino. Para integrar com syslog, utilize o comando logger:

0 3 * * * /opt/scripts/verifica_disco.sh 2>&1 | logger -t verifica_disco -p cron.info

Isso envia as mensagens para o syslog local com a tag "verifica_disco", permitindo consulta centralizada via journalctl ou arquivos em /var/log/.

2. Estrutura de logging robusta para jobs cron

Uma estratégia de logging bem planejada inclui três elementos: nomenclatura padronizada, rotação automática e timestamps. Crie diretórios específicos por job:

/var/log/cron/
├── backup/
│   ├── backup-2025-01-15.log
│   └── backup-2025-01-16.log
├── monitoria/
│   └── monitoria-2025-01-15.log
└── sincronizacao/
    └── sincronizacao-2025-01-15.log

Configure o logrotate para evitar que os logs cresçam indefinidamente. Exemplo em /etc/logrotate.d/cron-jobs:

/var/log/cron/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0640 root adm
    postrotate
        systemctl restart rsyslog > /dev/null 2>&1 || true
    endscript
}

Cada linha de log deve conter timestamp e identificação do job. No script, implemente:

#!/bin/bash
LOG_FILE="/var/log/cron/backup/backup-$(date +%Y-%m-%d).log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Iniciando backup..." >> "$LOG_FILE"

3. Captura e tratamento de erros dentro do script

Scripts robustos precisam de tratamento explícito de erros. Use set -e para abortar em qualquer falha e trap para capturar sinais e garantir limpeza:

#!/bin/bash
set -e
LOG_FILE="/var/log/cron/backup.log"
ERROR_FLAG=0

trap 'echo "[$(date)] [ERRO] Job finalizado com falha" >> "$LOG_FILE"; ERROR_FLAG=1' ERR

funcao_log() {
    local level="$1"
    local message="$2"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] ${message}" >> "$LOG_FILE"
}

funcao_log "INFO" "Iniciando processo de backup"
rsync -avz /dados /backup/ || funcao_log "ERRO" "Falha na sincronização"

Diferencie saída normal de erros críticos usando níveis de severidade (INFO, WARN, ERROR). Crie funções padronizadas para reuso em múltiplos scripts.

4. Notificação por email com mailx e mutt

Para ambientes que ainda dependem de email, configure o mailx (ou heirloom-mailx) com um relay SMTP. Edite /etc/mail.rc:

set smtp=smtp://smtp.exemplo.com:587
set smtp-auth=login
set smtp-auth-user=seuemail@exemplo.com
set smtp-auth-password=suasenha
set from="cron@exemplo.com"

No script, envie logs de erro automaticamente:

#!/bin/bash
LOG_FILE="/var/log/cron/backup.log"
SUBJECT="[CRON] Falha no backup - $(hostname) - $(date '+%Y-%m-%d %H:%M')"

if [ $? -ne 0 ]; then
    tail -50 "$LOG_FILE" | mailx -s "$SUBJECT" admin@exemplo.com
fi

Para formatação mais rica, use mutt:

mutt -s "$SUBJECT" -a "$LOG_FILE" -- admin@exemplo.com < /dev/null

5. Notificação via sistemas modernos (Slack, Telegram, Webhook)

Sistemas modernos preferem notificações instantâneas via API. Para Slack, crie um webhook e use curl:

#!/bin/bash
WEBHOOK_URL="https://hooks.slack.com/services/T0000/B0000/xxxxx"
MESSAGE="{\"text\": \"Falha no job de backup em $(hostname) - $(date)\"}"
curl -X POST -H 'Content-type: application/json' --data "$MESSAGE" "$WEBHOOK_URL"

Para Telegram, utilize a API de bots:

#!/bin/bash
BOT_TOKEN="seu_token"
CHAT_ID="seu_chat_id"
MESSAGE="Falha no job de backup em $(hostname)"
curl -s "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
    -d "chat_id=${CHAT_ID}" \
    -d "text=${MESSAGE}"

Para serviços de monitoramento como Healthchecks.io, envie um ping simples:

curl -fsS --retry 3 https://hc-ping.com/seu-uuid > /dev/null

6. Monitoramento proativo com scripts wrapper

Crie um wrapper que registre início, fim e status de cada execução. Isso permite detectar jobs que não executaram ou falharam silenciosamente:

#!/bin/bash
# wrapper_cron.sh
JOB_NAME="$1"
shift
LOG_DIR="/var/log/cron/${JOB_NAME}"
mkdir -p "$LOG_DIR"
LOG_FILE="${LOG_DIR}/${JOB_NAME}-$(date +%Y%m%d-%H%M%S).log"

echo "[$(date)] [INICIO] Job: ${JOB_NAME}" > "$LOG_FILE"
START_TIME=$(date +%s)

# Executa o comando real
"$@" >> "$LOG_FILE" 2>&1
EXIT_CODE=$?

END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))

echo "[$(date)] [FIM] Job: ${JOB_NAME} - Codigo: ${EXIT_CODE} - Duracao: ${DURATION}s" >> "$LOG_FILE"

if [ $EXIT_CODE -ne 0 ]; then
    # Envia alerta
    curl -s "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
        -d "chat_id=${CHAT_ID}" \
        -d "text=Job ${JOB_NAME} falhou com codigo ${EXIT_CODE} em $(hostname)"
fi

Use no crontab:

0 2 * * * /opt/scripts/wrapper_cron.sh backup /opt/scripts/backup.sh

Implemente heartbeat: se o wrapper não gerar log por mais de 24h, dispare alerta externo.

7. Centralização de logs e alertas com ferramentas externas

Para ambientes maiores, centralize logs. Envie para syslog remoto configurando o rsyslog:

# No cliente (script)
logger -n logserver.exemplo.com -P 514 -t backup "Iniciando backup"

# No servidor (/etc/rsyslog.conf)
:syslogtag, contains, "backup" /var/log/cron/backup.log

Use systemd-cat para integrar com journald:

#!/bin/bash
exec > >(systemd-cat -t backup -p info) 2>&1
echo "Iniciando backup"

Para métricas no Prometheus, crie um exporter simples que leia os logs e exponha contadores de falhas. Ou configure alertas no Zabbix com monitoramento de arquivos de log via log[] item.

8. Boas práticas e troubleshooting

Teste notificações manualmente antes de confiar nelas:

# Simula falha
/opt/scripts/backup.sh; echo "Exit code: $?"
# Testa envio de email
echo "Teste" | mailx -s "Teste notificacao cron" admin@exemplo.com
# Testa webhook
curl -X POST -H 'Content-type: application/json' \
    --data '{"text":"Teste webhook cron"}' "$WEBHOOK_URL"

Verifique permissões e caminhos absolutos em todos os scripts. O cron executa com ambiente mínimo — use PATH explícito:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * /opt/scripts/backup.sh

Audite jobs não notificados: crie um job diário que verifica se todos os wrappers registraram execução nas últimas 24h. Se faltar algum, dispare alerta.

#!/bin/bash
# verifica_heartbeat.sh
for job in backup monitoria sincronizacao; do
    LAST_LOG=$(ls -t /var/log/cron/${job}/*.log 2>/dev/null | head -1)
    if [ -z "$LAST_LOG" ] || [ $(stat -c %Y "$LAST_LOG") -lt $(date -d '24 hours ago' +%s) ]; then
        echo "Job ${job} sem execucao nas ultimas 24h" | mailx -s "Heartbeat perdido" admin@exemplo.com
    fi
done

Referências