feat: Implement daily log rotation

- Add logging to monitor_agent.py to replace print statements.
- Configure TimedRotatingFileHandler to keep logs for the past 24 hours.
- Update .gitignore to exclude the new log file.
- Update PROGRESS.md to reflect the completion of the task.
This commit is contained in:
2025-08-21 12:15:36 -05:00
parent e119bc7194
commit f65b2d468d
3 changed files with 38 additions and 29 deletions

2
.gitignore vendored
View File

@@ -3,4 +3,4 @@ __pycache__/
monitoring_data.json monitoring_data.json
log_position.txt log_position.txt
auth_log_position.txt auth_log_position.txt
monitor_agent.log monitoring_agent.log

View File

@@ -68,8 +68,8 @@
## TODO ## TODO
- [ ] Change baseline calculations to only use integers instead of floats. - [x] Change baseline calculations to only use integers instead of floats.
- [ ] Add a log file that only keeps records for the past 24 hours. - [x] Add a log file that only keeps records for the past 24 hours.
- [ ] Log all LLM responses to the console. - [ ] Log all LLM responses to the console.
- [ ] Reduce alerts to only happen between 9am and 12am. - [ ] Reduce alerts to only happen between 9am and 12am.
- [ ] Get hostnames of devices in Nmap scan. - [ ] Get hostnames of devices in Nmap scan.

View File

@@ -12,12 +12,26 @@ import os
from datetime import datetime, timezone from datetime import datetime, timezone
import pingparsing import pingparsing
import nmap import nmap
import logging
from logging.handlers import TimedRotatingFileHandler
# Load configuration # Load configuration
import config import config
from syslog_rfc5424_parser import parser from syslog_rfc5424_parser import parser
# --- Logging Configuration ---
LOG_FILE = "monitoring_agent.log"
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# Create a handler that rotates logs daily, keeping 1 backup
handler = TimedRotatingFileHandler(LOG_FILE, when="midnight", interval=1, backupCount=1)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
LOG_POSITION_FILE = 'log_position.txt' LOG_POSITION_FILE = 'log_position.txt'
AUTH_LOG_POSITION_FILE = 'auth_log_position.txt' AUTH_LOG_POSITION_FILE = 'auth_log_position.txt'
@@ -49,10 +63,10 @@ def get_system_logs():
return {"syslog": parsed_logs} return {"syslog": parsed_logs}
except FileNotFoundError: except FileNotFoundError:
print("Error: /var/log/syslog not found.") logger.error("/var/log/syslog not found.")
return {"syslog": []} return {"syslog": []}
except Exception as e: except Exception as e:
print(f"Error reading syslog: {e}") logger.error(f"Error reading syslog: {e}")
return {"syslog": []} return {"syslog": []}
@@ -66,7 +80,7 @@ def get_network_metrics():
result = transmitter.ping() result = transmitter.ping()
return ping_parser.parse(result).as_dict() return ping_parser.parse(result).as_dict()
except Exception as e: except Exception as e:
print(f"Error getting network metrics: {e}") logger.error(f"Error getting network metrics: {e}")
return {"error": "ping command failed"} return {"error": "ping command failed"}
def get_sensor_data(): def get_sensor_data():
@@ -74,7 +88,7 @@ def get_sensor_data():
try: try:
return subprocess.check_output(["sensors"], text=True) return subprocess.check_output(["sensors"], text=True)
except (subprocess.CalledProcessError, FileNotFoundError): except (subprocess.CalledProcessError, FileNotFoundError):
print("Error: 'sensors' command not found. Please install lm-sensors.") logger.error("'sensors' command not found. Please install lm-sensors.")
return None return None
def get_cpu_temperature(sensors_output): def get_cpu_temperature(sensors_output):
@@ -105,7 +119,6 @@ def get_gpu_temperature(sensors_output):
return {"gpu_temperature": "N/A"} return {"gpu_temperature": "N/A"}
def get_login_attempts(): def get_login_attempts():
"""Gets system login attempts from /var/log/auth.log since the last check.""" """Gets system login attempts from /var/log/auth.log since the last check."""
try: try:
@@ -129,10 +142,10 @@ def get_login_attempts():
return {"failed_login_attempts": failed_logins} return {"failed_login_attempts": failed_logins}
except FileNotFoundError: except FileNotFoundError:
print("Error: /var/log/auth.log not found.") logger.error("/var/log/auth.log not found.")
return {"failed_login_attempts": []} return {"failed_login_attempts": []}
except Exception as e: except Exception as e:
print(f"Error reading login attempts: {e}") logger.error(f"Error reading login attempts: {e}")
return {"failed_logins": []} return {"failed_logins": []}
def get_nmap_scan_results(): def get_nmap_scan_results():
@@ -141,7 +154,7 @@ def get_nmap_scan_results():
nm = nmap.PortScanner() nm = nmap.PortScanner()
scan_options = config.NMAP_SCAN_OPTIONS scan_options = config.NMAP_SCAN_OPTIONS
if os.geteuid() != 0 and "-sS" in scan_options: if os.geteuid() != 0 and "-sS" in scan_options:
print("Warning: Nmap -sS scan requires root privileges. Falling back to -sT.") logger.warning("Nmap -sS scan requires root privileges. Falling back to -sT.")
scan_options = scan_options.replace("-sS", "-sT") scan_options = scan_options.replace("-sS", "-sT")
scan_results = nm.scan(hosts=config.NMAP_TARGETS, arguments=scan_options) scan_results = nm.scan(hosts=config.NMAP_TARGETS, arguments=scan_options)
@@ -168,7 +181,7 @@ def get_nmap_scan_results():
return processed_results return processed_results
except Exception as e: except Exception as e:
print(f"Error performing Nmap scan: {e}") logger.error(f"Error performing Nmap scan: {e}")
return {"error": "Nmap scan failed"} return {"error": "Nmap scan failed"}
# --- LLM Interaction Function --- # --- LLM Interaction Function ---
@@ -247,15 +260,15 @@ def analyze_data_with_llm(data, baselines):
return json.loads(json_string) return json.loads(json_string)
else: else:
# Handle cases where the response is not valid JSON # Handle cases where the response is not valid JSON
print(f"LLM returned a non-JSON response: {sanitized_response}") logger.warning(f"LLM returned a non-JSON response: {sanitized_response}")
return {"severity": "low", "reason": sanitized_response} return {"severity": "low", "reason": sanitized_response}
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"Error decoding LLM response: {e}") logger.error(f"Error decoding LLM response: {e}")
# Fallback for invalid JSON # Fallback for invalid JSON
return {"severity": "low", "reason": sanitized_response} return {"severity": "low", "reason": sanitized_response}
except Exception as e: except Exception as e:
print(f"Error interacting with LLM: {e}") logger.error(f"Error interacting with LLM: {e}")
return None return None
@@ -267,11 +280,11 @@ def send_discord_alert(message):
try: try:
response = webhook.execute() response = webhook.execute()
if response.status_code == 200: if response.status_code == 200:
print("Discord alert sent successfully.") logger.info("Discord alert sent successfully.")
else: else:
print(f"Error sending Discord alert: {response.status_code} - {response.content}") logger.error(f"Error sending Discord alert: {response.status_code} - {response.content}")
except Exception as e: except Exception as e:
print(f"Error sending Discord alert: {e}") logger.error(f"Error sending Discord alert: {e}")
def send_google_home_alert(message): def send_google_home_alert(message):
"""Sends an alert to a Google Home speaker via Home Assistant.""" """Sends an alert to a Google Home speaker via Home Assistant."""
@@ -280,7 +293,7 @@ def send_google_home_alert(message):
response = ollama.generate(model="llama3.1:8b", prompt=f"Summarize the following message in a single sentence: {message}") response = ollama.generate(model="llama3.1:8b", prompt=f"Summarize the following message in a single sentence: {message}")
simplified_message = response['response'].strip() simplified_message = response['response'].strip()
except Exception as e: except Exception as e:
print(f"Error summarizing message: {e}") logger.error(f"Error summarizing message: {e}")
simplified_message = message.split('.')[0] # Take the first sentence as a fallback simplified_message = message.split('.')[0] # Take the first sentence as a fallback
url = f"{config.HOME_ASSISTANT_URL}/api/services/tts/speak" url = f"{config.HOME_ASSISTANT_URL}/api/services/tts/speak"
@@ -296,11 +309,11 @@ def send_google_home_alert(message):
try: try:
response = requests.post(url, headers=headers, json=data) response = requests.post(url, headers=headers, json=data)
if response.status_code == 200: if response.status_code == 200:
print("Google Home alert sent successfully.") logger.info("Google Home alert sent successfully.")
else: else:
print(f"Error sending Google Home alert: {response.status_code} - {response.text}") logger.error(f"Error sending Google Home alert: {response.status_code} - {response.text}")
except Exception as e: except Exception as e:
print(f"Error sending Google Home alert: {e}") logger.error(f"Error sending Google Home alert: {e}")
# --- Main Script Logic --- # --- Main Script Logic ---
@@ -308,7 +321,7 @@ daily_events = []
def run_monitoring_cycle(nmap_scan_counter): def run_monitoring_cycle(nmap_scan_counter):
"""Runs a single monitoring cycle.""" """Runs a single monitoring cycle."""
print("Running monitoring cycle...") logger.info("Running monitoring cycle...")
system_logs = get_system_logs() system_logs = get_system_logs()
network_metrics = get_network_metrics() network_metrics = get_network_metrics()
sensors_output = get_sensor_data() sensors_output = get_sensor_data()
@@ -349,7 +362,7 @@ def run_monitoring_cycle(nmap_scan_counter):
def main(): def main():
"""Main function to run the monitoring agent.""" """Main function to run the monitoring agent."""
if config.TEST_MODE: if config.TEST_MODE:
print("Running in test mode...") logger.info("Running in test mode...")
run_monitoring_cycle(0) run_monitoring_cycle(0)
else: else:
nmap_scan_counter = 0 nmap_scan_counter = 0
@@ -366,8 +379,4 @@ def main():
time.sleep(300) # Run every 5 minutes time.sleep(300) # Run every 5 minutes
if __name__ == "__main__": if __name__ == "__main__":
main() main()