#!/bin/bash # # LogWhisperer Agent - Script di monitoraggio log # Legge log di sistema, rileva errori critici e invia alert via webhook # set -euo pipefail # ============================================================================ # CONFIGURAZIONE DEFAULT # ============================================================================ SCRIPT_NAME="$(basename "$0")" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VERSION="1.0.0" # Default config paths CONFIG_FILE="/etc/logwhisperer/config.env" OFFSET_DIR="/var/lib/logwhisperer" DEBUG_LOG="/var/log/logwhisperer/debug.log" # Default values WEBHOOK_URL="" CLIENT_ID="" LOG_SOURCES="" POLL_INTERVAL=5 MAX_LINE_LENGTH=2000 # Error patterns (case-insensitive) PATTERNS=( "FATAL" "ERROR" "OOM" "Out of memory" "segfault" "disk full" "No space left on device" "Connection refused" "Permission denied" ) # Rate limiting (seconds) RATE_LIMIT=30 # ============================================================================ # FUNZIONI DI UTILITÀ # ============================================================================ log_debug() { if [[ "${DEBUG:-0}" == "1" ]]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: $*" >&2 fi } log_error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2 } log_info() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $*" } # ============================================================================ # USAGE E HELP # ============================================================================ usage() { cat </dev/null) || { log_error "Impossibile connettersi al webhook" return 1 } http_code=$(echo "$response" | tail -n1) if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then return 0 else log_error "Webhook ha restituito HTTP $http_code" return 1 fi } # ============================================================================ # TEST MODE # ============================================================================ test_line() { local line="$1" local source="${2:-/var/log/syslog}" local matched_pattern matched_pattern=$(match_pattern "$line") || { echo "No match" return 0 } # Stampa solo il JSON su stdout per permettere parsing build_payload "$source" "$line" "$matched_pattern" return 0 } # ============================================================================ # MAIN LOOP # ============================================================================ main_loop() { log_info "Avvio LogWhisperer Agent v$VERSION" log_info "Client ID: $CLIENT_ID" log_info "Log sources: $LOG_SOURCES" log_info "Webhook URL: $WEBHOOK_URL" # Array per tracciare ultimi alert (rate limiting) declare -A last_alert # Converte LOG_SOURCES in array IFS=',' read -ra SOURCES <<< "$LOG_SOURCES" # Inizializza offset directory mkdir -p "$OFFSET_DIR" while true; do for source in "${SOURCES[@]}"; do # Rimuovi spazi source=$(echo "$source" | xargs) if [[ ! -r "$source" ]]; then log_debug "File non leggibile: $source" continue fi # Offset file per questa sorgente local offset_file="$OFFSET_DIR/$(echo "$source" | tr '/' '_').offset" local last_pos=0 if [[ -f "$offset_file" ]]; then last_pos=$(cat "$offset_file") fi # Ottieni dimensione attuale local current_size current_size=$(stat -c%s "$source" 2>/dev/null || echo 0) # Se il file è stato troncato o ruotato if [[ $current_size -lt $last_pos ]]; then last_pos=0 fi # Leggi nuove righe tail -c +$((last_pos + 1)) "$source" 2>/dev/null | while IFS= read -r line; do # Trunca se troppo lunga if [[ ${#line} -gt $MAX_LINE_LENGTH ]]; then line="${line:0:$MAX_LINE_LENGTH}..." fi local matched_pattern if matched_pattern=$(match_pattern "$line"); then local now now=$(date +%s) local source_key="$source:$matched_pattern" # Rate limiting if [[ -n "${last_alert[$source_key]:-}" ]]; then local last_time=${last_alert[$source_key]} if [[ $((now - last_time)) -lt $RATE_LIMIT ]]; then log_debug "Rate limited: $source_key" continue fi fi log_info "Rilevato: $matched_pattern in $source" local payload payload=$(build_payload "$source" "$line" "$matched_pattern") if dispatch_webhook "$payload"; then last_alert[$source_key]=$now log_debug "Alert inviato con successo" else log_error "Fallimento invio alert" fi fi done # Salva nuova posizione echo "$current_size" > "$offset_file" done sleep "$POLL_INTERVAL" done } # ============================================================================ # PARSING ARGOMENTI # ============================================================================ DRY_RUN=0 VALIDATE=0 TEST_LINE="" TEST_SOURCE="/var/log/syslog" while [[ $# -gt 0 ]]; do case $1 in -c|--config) CONFIG_FILE="$2" shift 2 ;; -d|--dry-run) DRY_RUN=1 shift ;; -t|--test-line) TEST_LINE="$2" shift 2 ;; -s|--test-source) TEST_SOURCE="$2" shift 2 ;; -v|--validate) VALIDATE=1 shift ;; --debug) DEBUG=1 shift ;; -h|--help) usage exit 0 ;; --version) echo "$SCRIPT_NAME v$VERSION" exit 0 ;; *) log_error "Opzione sconosciuta: $1" usage exit 1 ;; esac done # ============================================================================ # MAIN # ============================================================================ main() { # Carica configurazione load_config # Modalità validazione if [[ $VALIDATE -eq 1 ]]; then if validate_config; then log_info "Configurazione valida" exit 0 else exit 1 fi fi # Modalità test linea if [[ -n "$TEST_LINE" ]]; then if [[ $DRY_RUN -eq 1 ]]; then test_line "$TEST_LINE" "$TEST_SOURCE" exit 0 fi fi # Verifica configurazione prima di avviare if ! validate_config; then log_error "Configurazione invalida. Usa --validate per dettagli." exit 1 fi # Modalità dry-run senza test line if [[ $DRY_RUN -eq 1 ]]; then log_info "Modalità dry-run: nessun webhook verrà inviato" fi # Avvia loop principale main_loop } main "$@"