Backup automatico script del 2026-01-18 07:00
This commit is contained in:
231
services/telegram-bot/log_monitor.py
Normal file
231
services/telegram-bot/log_monitor.py
Normal file
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict, deque
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
DEFAULT_PATTERNS = ["*.log", "*_log.txt"]
|
||||
TOKEN_FILE_HOME = os.path.expanduser("~/.telegram_dpc_bot_token")
|
||||
TOKEN_FILE_ETC = "/etc/telegram_dpc_bot_token"
|
||||
|
||||
TS_RE = re.compile(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
|
||||
|
||||
CATEGORIES = {
|
||||
"open_meteo_timeout": re.compile(
|
||||
r"timeout|timed out|Read timed out|Gateway Time-out|504", re.IGNORECASE
|
||||
),
|
||||
"ssl_handshake": re.compile(r"handshake", re.IGNORECASE),
|
||||
"permission_error": re.compile(r"PermissionError|permesso negato|Errno 13", re.IGNORECASE),
|
||||
"telegram_error": re.compile(r"Telegram error|Bad Request|chat not found|can't parse entities", re.IGNORECASE),
|
||||
"traceback": re.compile(r"Traceback", re.IGNORECASE),
|
||||
"exception": re.compile(r"\bERROR\b|Exception", re.IGNORECASE),
|
||||
"token_missing": re.compile(r"token missing|Token Telegram assente", re.IGNORECASE),
|
||||
}
|
||||
|
||||
|
||||
def load_text_file(path: str) -> str:
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return f.read().strip()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def load_bot_token() -> str:
|
||||
tok = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
|
||||
if tok:
|
||||
return tok
|
||||
tok = load_text_file(TOKEN_FILE_HOME)
|
||||
if tok:
|
||||
return tok
|
||||
tok = load_text_file(TOKEN_FILE_ETC)
|
||||
return tok.strip() if tok else ""
|
||||
|
||||
|
||||
def send_telegram(text: str, chat_ids: Optional[List[str]]) -> bool:
|
||||
token = load_bot_token()
|
||||
if not token or not chat_ids:
|
||||
return False
|
||||
url = f"https://api.telegram.org/bot{token}/sendMessage"
|
||||
base_payload = {
|
||||
"text": text,
|
||||
"disable_web_page_preview": True,
|
||||
}
|
||||
ok = False
|
||||
with requests.Session() as s:
|
||||
for chat_id in chat_ids:
|
||||
payload = dict(base_payload)
|
||||
payload["chat_id"] = chat_id
|
||||
try:
|
||||
resp = s.post(url, json=payload, timeout=15)
|
||||
if resp.status_code == 200:
|
||||
ok = True
|
||||
except Exception:
|
||||
continue
|
||||
return ok
|
||||
|
||||
|
||||
def tail_lines(path: str, max_lines: int) -> List[str]:
|
||||
dq: deque[str] = deque(maxlen=max_lines)
|
||||
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
for line in f:
|
||||
dq.append(line.rstrip("\n"))
|
||||
return list(dq)
|
||||
|
||||
|
||||
def parse_ts(line: str) -> Optional[datetime.datetime]:
|
||||
m = TS_RE.search(line)
|
||||
if not m:
|
||||
return None
|
||||
try:
|
||||
return datetime.datetime.strptime(m.group(1), "%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def analyze_logs(files: List[str], since: datetime.datetime, max_lines: int) -> Tuple[Dict, Dict, Dict, Dict]:
|
||||
category_hits = defaultdict(list)
|
||||
per_file_counts = defaultdict(lambda: defaultdict(int))
|
||||
timeout_minutes = defaultdict(int)
|
||||
stale_logs = {} # path -> (last_ts, hours_since_update)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
for path in files:
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
last_ts = None
|
||||
for line in tail_lines(path, max_lines):
|
||||
ts = parse_ts(line)
|
||||
if ts:
|
||||
last_ts = ts
|
||||
if not last_ts or last_ts < since:
|
||||
continue
|
||||
for cat, regex in CATEGORIES.items():
|
||||
if regex.search(line):
|
||||
category_hits[cat].append((last_ts, path, line))
|
||||
per_file_counts[path][cat] += 1
|
||||
if cat == "open_meteo_timeout":
|
||||
timeout_minutes[last_ts.strftime("%H:%M")] += 1
|
||||
break
|
||||
|
||||
# Verifica se il log è "stale" (non aggiornato da più di 24 ore)
|
||||
if last_ts:
|
||||
hours_since = (now - last_ts).total_seconds() / 3600.0
|
||||
if hours_since > 24:
|
||||
stale_logs[path] = (last_ts, hours_since)
|
||||
else:
|
||||
# Se non ha timestamp, verifica data di modifica del file
|
||||
try:
|
||||
mtime = os.path.getmtime(path)
|
||||
file_mtime = datetime.datetime.fromtimestamp(mtime)
|
||||
hours_since = (now - file_mtime).total_seconds() / 3600.0
|
||||
if hours_since > 24:
|
||||
stale_logs[path] = (file_mtime, hours_since)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return category_hits, per_file_counts, timeout_minutes, stale_logs
|
||||
|
||||
|
||||
def format_report(
|
||||
days: int,
|
||||
files: List[str],
|
||||
category_hits: Dict,
|
||||
per_file_counts: Dict,
|
||||
timeout_minutes: Dict,
|
||||
stale_logs: Dict,
|
||||
) -> str:
|
||||
now = datetime.datetime.now()
|
||||
since = now - datetime.timedelta(days=days)
|
||||
lines = []
|
||||
lines.append(f"🧾 Log Monitor - ultimi {days} giorni")
|
||||
lines.append(f"Intervallo: {since.strftime('%Y-%m-%d %H:%M')} → {now.strftime('%Y-%m-%d %H:%M')}")
|
||||
lines.append(f"File analizzati: {len(files)}")
|
||||
lines.append("")
|
||||
|
||||
# Sezione log non aggiornati
|
||||
if stale_logs:
|
||||
lines.append("⚠️ Log non aggiornati (>24h):")
|
||||
for path, (last_ts, hours_since) in sorted(stale_logs.items(), key=lambda x: x[1][1], reverse=True):
|
||||
short_path = os.path.basename(path)
|
||||
days_ago = hours_since / 24.0
|
||||
if days_ago >= 1:
|
||||
lines.append(f" • {short_path}: {days_ago:.1f} giorni fa ({last_ts.strftime('%Y-%m-%d %H:%M')})")
|
||||
else:
|
||||
lines.append(f" • {short_path}: {hours_since:.1f} ore fa ({last_ts.strftime('%Y-%m-%d %H:%M')})")
|
||||
lines.append("")
|
||||
|
||||
total_issues = sum(len(v) for v in category_hits.values())
|
||||
if total_issues == 0 and not stale_logs:
|
||||
lines.append("✅ Nessun problema rilevato nelle ultime 72 ore.")
|
||||
return "\n".join(lines)
|
||||
|
||||
lines.append(f"Problemi rilevati: {total_issues}")
|
||||
lines.append("")
|
||||
|
||||
for cat, items in sorted(category_hits.items(), key=lambda x: len(x[1]), reverse=True):
|
||||
lines.append(f"- {cat}: {len(items)}")
|
||||
# show up to 3 latest samples
|
||||
for ts, path, msg in sorted(items, key=lambda x: x[0], reverse=True)[:3]:
|
||||
short_path = os.path.basename(path)
|
||||
lines.append(f" • {ts.strftime('%Y-%m-%d %H:%M:%S')} | {short_path} | {msg[:180]}")
|
||||
lines.append("")
|
||||
|
||||
if timeout_minutes:
|
||||
lines.append("Timeout: minuti più frequenti")
|
||||
for minute, count in sorted(timeout_minutes.items(), key=lambda x: x[1], reverse=True)[:6]:
|
||||
lines.append(f" • {minute} -> {count}")
|
||||
lines.append("")
|
||||
|
||||
# Per-file summary (top 6 files)
|
||||
lines.append("File più problematici")
|
||||
file_totals = []
|
||||
for path, cats in per_file_counts.items():
|
||||
file_totals.append((sum(cats.values()), path))
|
||||
for total, path in sorted(file_totals, reverse=True)[:6]:
|
||||
short_path = os.path.basename(path)
|
||||
lines.append(f" • {short_path}: {total}")
|
||||
|
||||
return "\\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--days", type=int, default=3, help="Numero di giorni da analizzare")
|
||||
parser.add_argument("--max-lines", type=int, default=5000, help="Limite righe per file")
|
||||
parser.add_argument("--chat_id", help="Chat ID Telegram (separati da virgola)")
|
||||
parser.add_argument("--log", action="append", help="Aggiungi un file log specifico")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.log:
|
||||
files = [p for p in args.log if os.path.exists(p)]
|
||||
else:
|
||||
from pathlib import Path
|
||||
files = []
|
||||
for pat in DEFAULT_PATTERNS:
|
||||
files.extend(sorted([str(p) for p in Path(BASE_DIR).glob(pat)]))
|
||||
files = sorted(set(files))
|
||||
|
||||
since = datetime.datetime.now() - datetime.timedelta(days=args.days)
|
||||
category_hits, per_file_counts, timeout_minutes, stale_logs = analyze_logs(files, since, args.max_lines)
|
||||
report = format_report(args.days, files, category_hits, per_file_counts, timeout_minutes, stale_logs)
|
||||
|
||||
if args.chat_id:
|
||||
chat_ids = [c.strip() for c in args.chat_id.split(",") if c.strip()]
|
||||
send_telegram(report, chat_ids)
|
||||
else:
|
||||
print(report)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user