import os import subprocess import requests import smtplib from email.message import EmailMessage import time import schedule # --- CONFIGURATION (Loaded from environment variables) --- OLLAMA_API = os.getenv("OLLAMA_API_URL", "http://localhost:11434/api/generate") MODEL = os.getenv("OLLAMA_MODEL", "llama3.2:1b") AI_SYSTEM_PROMPT = os.getenv("AI_SYSTEM_PROMPT", "You are a Linux Sysadmin. Summarize these logs briefly.") MY_EMAIL = os.getenv("REPORT_TO_EMAIL") FROM_EMAIL = os.getenv("REPORT_FROM_EMAIL") SMTP_SERVER = os.getenv("SMTP_SERVER", "127.0.0.1") SMTP_PORT = int(os.getenv("SMTP_PORT", 25)) SMTP_USER = os.getenv("SMTP_USER", "") SMTP_PASS = os.getenv("SMTP_PASS", "") JOURNAL_UNIT = os.getenv("JOURNAL_UNIT", "mailserver") MAX_ERRORS = int(os.getenv("MAX_ERRORS_TO_ANALYZE", 50)) # Parse comma-separated keywords into a list keyword_string = os.getenv("ERROR_KEYWORDS", "error,failed,warning,fatal,timeout") KEYWORDS = [k.strip().lower() for k in keyword_string.split(',')] # --- EXECUTION TOGGLES --- DEBUG = os.getenv("DEBUG", "false").lower() in ("true", "1", "yes") RUN_NOW = os.getenv("RUN_NOW", "false").lower() in ("true", "1", "yes") REPORT_TIME = os.getenv("REPORT_TIME", "08:00") def debug_print(message): """Prints only if DEBUG=true in .env""" if DEBUG: print(f"[DEBUG] {message}") def get_yesterdays_logs(): print(f"Fetching logs for '{JOURNAL_UNIT}' from yesterday...") debug_print(f"Running command: journalctl -u {JOURNAL_UNIT} --since yesterday --until today") result = subprocess.run( ['journalctl', '-u', JOURNAL_UNIT, '--since', 'yesterday', '--until', 'today'], capture_output=True, text=True ) lines = result.stdout.splitlines() debug_print(f"Fetched {len(lines)} total lines from journalctl.") return lines def process_logs(logs): total_lines = len(logs) errors = [] debug_print(f"Filtering logs using keywords: {KEYWORDS}") for line in logs: line_lower = line.lower() if any(keyword in line_lower for keyword in KEYWORDS): errors.append(line) debug_print(f"Found {len(errors)} matching lines before truncation.") # Truncate to maximum allowed errors to save CPU time errors = errors[-MAX_ERRORS:] debug_print(f"Truncated to {len(errors)} lines for AI processing (MAX_ERRORS={MAX_ERRORS}).") return total_lines, errors def get_ai_summary(errors): if not errors: debug_print("Skipping AI API call because error list is empty.") return "No errors or warnings were found in the logs yesterday. Everything looks healthy!" print(f"Asking AI to summarize {len(errors)} errors... (This will take a few minutes)") error_text = "\n".join(errors) # Combine the system prompt from .env with the logs prompt = f"{AI_SYSTEM_PROMPT}\n\nLOGS:\n{error_text}" payload = { "model": MODEL, "prompt": prompt, "stream": False } debug_print(f"Sending prompt to Ollama at {OLLAMA_API} using model '{MODEL}'") debug_print(f"System Prompt used: {AI_SYSTEM_PROMPT}") debug_print(f"Total payload length: {len(prompt)} characters.") try: response = requests.post(OLLAMA_API, json=payload) debug_print(f"AI response status code: {response.status_code}") return response.json().get('response', 'Error: AI returned empty response.') except Exception as e: debug_print(f"AI Connection Exception: {str(e)}") return f"Failed to connect to local AI: {str(e)}" def send_email_report(total_traffic, ai_summary): if not MY_EMAIL or not FROM_EMAIL: print("Skipping email: REPORT_TO_EMAIL or REPORT_FROM_EMAIL not set.") return print(f"Sending email report to {MY_EMAIL}...") debug_print(f"SMTP Server: {SMTP_SERVER}:{SMTP_PORT}") debug_print(f"SMTP Auth User: {'' if SMTP_USER else ''}") msg = EmailMessage() msg['Subject'] = f"Daily AI Summary: {JOURNAL_UNIT}" msg['From'] = FROM_EMAIL msg['To'] = MY_EMAIL body = ( f"DAILY SERVER REPORT: {JOURNAL_UNIT}\n" f"----------------------------------------\n" f"Total Log Lines Processed: {total_traffic}\n\n" f"AI Analysis of Errors/Warnings:\n" f"{ai_summary}\n" ) msg.set_content(body) try: server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) if SMTP_USER: debug_print("Attempting STARTTLS and Login...") server.starttls() server.login(SMTP_USER, SMTP_PASS) debug_print("Transmitting email payload...") server.send_message(msg) server.quit() print("Email sent successfully!") except Exception as e: print(f"Failed to send email: {e}") debug_print(f"Email Exception: {str(e)}") def job(): print(f"\n--- Starting log analysis for {JOURNAL_UNIT} ---") logs = get_yesterdays_logs() total_traffic, errors = process_logs(logs) summary = get_ai_summary(errors) send_email_report(total_traffic, summary) print("--- Analysis complete. ---") if __name__ == "__main__": print("Log Analyzer Container starting up...") if DEBUG: print("[DEBUG] Debug mode is ENABLED.") # 1. Register the permanent daily schedule first schedule.every().day.at(REPORT_TIME).do(job) print(f"Job permanently scheduled to run daily at {REPORT_TIME}.") # 2. Handle the immediate run if requested if RUN_NOW: print("RUN_NOW is enabled. Executing immediate startup run...") job() print("--- Immediate run finished. Returning to standard schedule. ---") else: print("RUN_NOW is disabled. Skipping immediate run.") # 3. Enter the permanent background loop print(f"Entering background scheduler loop. Waiting for {REPORT_TIME}...") while True: schedule.run_pending() time.sleep(60)