stats_manager.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import os
  2. import time
  3. import datetime
  4. import re
  5. import json
  6. import threading
  7. import config
  8. from core.file_manager import load_panel_settings, write_file
  9. stats_lock = threading.Lock()
  10. last_file_size = 0
  11. def stats_collector():
  12. global last_file_size
  13. while True:
  14. time.sleep(60)
  15. try:
  16. s = load_panel_settings()
  17. if s.get('query_log_enabled') != 'yes' or not os.path.exists(config.QUERY_LOG_PATH):
  18. continue
  19. current_size = os.path.getsize(config.QUERY_LOG_PATH)
  20. if current_size < last_file_size:
  21. last_file_size = 0
  22. lines = []
  23. with open(config.QUERY_LOG_PATH, 'r', errors='ignore') as f:
  24. f.seek(last_file_size)
  25. lines = f.readlines()
  26. last_file_size = f.tell()
  27. if not lines: continue
  28. now = datetime.datetime.now()
  29. d_key, t_key = now.strftime("%Y-%m-%d"), now.strftime("%H:%M")
  30. with stats_lock:
  31. st = {}
  32. if os.path.exists(config.STATS_FILE):
  33. try:
  34. with open(config.STATS_FILE, "r") as f: st = json.load(f)
  35. except: pass
  36. if d_key not in st:
  37. st[d_key] = {
  38. "queries": 0, "ips": {}, "tlds": {}, "timeline": {},
  39. "blocked_queries": 0, "blocked_domains": {},
  40. "blocked_clients": {}, "block_timeline": {}
  41. }
  42. day = st[d_key]
  43. q_count_min = 0
  44. b_count_min = 0
  45. for line in lines:
  46. line_lower = line.lower()
  47. # 1. Κανονικά Queries
  48. if 'query:' in line_lower:
  49. q_count_min += 1; day["queries"] = day.get("queries", 0) + 1
  50. ip_match = re.search(r'([0-9a-fA-F\.\:]+)#\d+', line)
  51. if ip_match:
  52. ip = ip_match.group(1); day["ips"][ip] = day["ips"].get(ip, 0) + 1
  53. q_match = re.search(r'query:\s+([^\s/]+)', line, re.I)
  54. if q_match:
  55. q_val = q_match.group(1).lower().strip('.')
  56. tld = q_val.split('.')[-1] if '.' in q_val else q_val
  57. day["tlds"][tld] = day.get("tlds", {}).get(tld, 0) + 1
  58. # 2. Blocked / RPZ Queries (Πιάνει όλα τα formats του BIND)
  59. if 'rpz' in line_lower and ('rewrite' in line_lower or 'qname' in line_lower or 'passthru' in line_lower):
  60. b_count_min += 1
  61. day["blocked_queries"] = day.get("blocked_queries", 0) + 1
  62. # Ψάχνει IP και Domain αγνοώντας τυχόν "@0x..."
  63. m_rpz = re.search(r'client\s+(?:@[^\s]+\s+)?([0-9a-fA-F\.\:]+)#\d+\s+\((.*?)\)', line)
  64. if m_rpz:
  65. b_ip = m_rpz.group(1)
  66. b_dom = m_rpz.group(2).strip('.')
  67. day.setdefault("blocked_clients", {})[b_ip] = day.get("blocked_clients", {}).get(b_ip, 0) + 1
  68. day.setdefault("blocked_domains", {})[b_dom] = day.get("blocked_domains", {}).get(b_dom, 0) + 1
  69. day.setdefault("timeline", {})[t_key] = {"rps": round(q_count_min / 60.0, 3), "unique_ips": len(day["ips"])}
  70. day.setdefault("block_timeline", {})[t_key] = round(b_count_min / 60.0, 3)
  71. cutoff = (now - datetime.timedelta(days=int(s.get('stats_retention', 30)))).strftime("%Y-%m-%d")
  72. st = {k: v for k, v in st.items() if k >= cutoff}
  73. write_file(config.STATS_FILE, json.dumps(st))
  74. except Exception as e:
  75. print(f"Stats Collector Error: {e}")
  76. def start_stats_daemon():
  77. threading.Thread(target=stats_collector, daemon=True).start()