167 lines
5.9 KiB
Python
167 lines
5.9 KiB
Python
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: {'<configured>' if SMTP_USER else '<none>'}")
|
|
|
|
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)
|