|
|
@@ -85,8 +85,8 @@ import argparse
|
|
|
|
import tempfile
|
|
|
|
import tempfile
|
|
|
|
#import mysql.connector
|
|
|
|
#import mysql.connector
|
|
|
|
import numpy as np
|
|
|
|
import numpy as np
|
|
|
|
import plotly.graph_objects as go
|
|
|
|
#import plotly.graph_objects as go
|
|
|
|
import plotly.express as px
|
|
|
|
#import plotly.express as px
|
|
|
|
import colorsys
|
|
|
|
import colorsys
|
|
|
|
import pymysql
|
|
|
|
import pymysql
|
|
|
|
import json
|
|
|
|
import json
|
|
|
@@ -98,7 +98,7 @@ log_dir_path = "/var/log/mailstats"
|
|
|
|
# Check if the directory exists, and create it if it doesn't
|
|
|
|
# Check if the directory exists, and create it if it doesn't
|
|
|
|
if not os.path.exists(log_dir_path):
|
|
|
|
if not os.path.exists(log_dir_path):
|
|
|
|
os.makedirs(log_dir_path)
|
|
|
|
os.makedirs(log_dir_path)
|
|
|
|
logging.basicConfig(level=logging.INFO, # Default level of messages to log
|
|
|
|
logging.basicConfig(level=logging.DEBUG, # Default level of messages to log
|
|
|
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
|
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
|
|
handlers=[
|
|
|
|
handlers=[
|
|
|
|
logging.StreamHandler(), # Log to console
|
|
|
|
logging.StreamHandler(), # Log to console
|
|
|
@@ -108,7 +108,7 @@ enable_graphs = True; #This could be a DB entry if required.
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
except ImportError:
|
|
|
|
except ImportError:
|
|
|
|
logging.debug("Matplotlib is not installed - no graphs")
|
|
|
|
logging.warning("Matplotlib is not installed - no graphs")
|
|
|
|
enable_graphs = False;
|
|
|
|
enable_graphs = False;
|
|
|
|
|
|
|
|
|
|
|
|
Mailstats_version = '1.2'
|
|
|
|
Mailstats_version = '1.2'
|
|
|
@@ -150,6 +150,10 @@ PERCENT = TOTALS + 1
|
|
|
|
ColTotals = 24
|
|
|
|
ColTotals = 24
|
|
|
|
ColPercent = 25
|
|
|
|
ColPercent = 25
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def strip_ansi_codes(text):
|
|
|
|
|
|
|
|
ansi_escape = re.compile(r'\x1b\[[0-9;]*m')
|
|
|
|
|
|
|
|
return ansi_escape.sub('', text)
|
|
|
|
|
|
|
|
|
|
|
|
def replace_bracket_content(input_filename, output_filename):
|
|
|
|
def replace_bracket_content(input_filename, output_filename):
|
|
|
|
import re
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
|
@@ -242,6 +246,8 @@ def get_logs_from_Journalctl(date='yesterday'):
|
|
|
|
entry_timestamp = entry.get('__REALTIME_TIMESTAMP', None)
|
|
|
|
entry_timestamp = entry.get('__REALTIME_TIMESTAMP', None)
|
|
|
|
entry_microseconds = int(entry_timestamp.timestamp() * 1_000_000)
|
|
|
|
entry_microseconds = int(entry_timestamp.timestamp() * 1_000_000)
|
|
|
|
if entry_timestamp and since_microseconds <= entry_microseconds <= until_microseconds:
|
|
|
|
if entry_timestamp and since_microseconds <= entry_microseconds <= until_microseconds:
|
|
|
|
|
|
|
|
# takeout ASCII Escape sequences from the message
|
|
|
|
|
|
|
|
entry['MESSAGE'] = strip_ansi_codes(entry['MESSAGE'])
|
|
|
|
logs.append(entry)
|
|
|
|
logs.append(entry)
|
|
|
|
|
|
|
|
|
|
|
|
# Sort logs by __REALTIME_TIMESTAMP in ascending order
|
|
|
|
# Sort logs by __REALTIME_TIMESTAMP in ascending order
|
|
|
@@ -588,32 +594,49 @@ def parse_data(data):
|
|
|
|
# and mapping:
|
|
|
|
# and mapping:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
return_dict = {
|
|
|
|
return_dict = {
|
|
|
|
'sme': fields[0].strip() if len(fields) > 0 else None,
|
|
|
|
'sme': fields[0].strip() if len(fields) > 0 else "",
|
|
|
|
'qpsmtpd': fields[1].strip() if len(fields) > 1 else None,
|
|
|
|
'qpsmtpd': fields[1].strip() if len(fields) > 1 else "",
|
|
|
|
'id': fields[2].strip() if len(fields) > 2 else None,
|
|
|
|
'id': fields[2].strip() if len(fields) > 2 else "",
|
|
|
|
'action': fields[3].strip() if len(fields) > 3 else None, #5
|
|
|
|
'action': fields[3].strip() if len(fields) > 3 else "", #5
|
|
|
|
'logterse': fields[4].strip() if len(fields) > 4 else None,
|
|
|
|
'logterse': fields[4].strip() if len(fields) > 4 else "",
|
|
|
|
'ip': fields[5].strip() if len(fields) > 5 else None,
|
|
|
|
'ip': fields[5].strip() if len(fields) > 5 else "",
|
|
|
|
'sendurl': fields[6].strip() if len(fields) > 6 else None, #1
|
|
|
|
'sendurl': fields[6].strip() if len(fields) > 6 else "", #1
|
|
|
|
'sendurl1': fields[7].strip() if len(fields) > 7 else None, #2
|
|
|
|
'sendurl1': fields[7].strip() if len(fields) > 7 else "", #2
|
|
|
|
'from-email': fields[8].strip() if len(fields) > 8 else None, #3
|
|
|
|
'from-email': fields[8].strip() if len(fields) > 8 else "", #3
|
|
|
|
'error-reason': fields[8].strip() if len(fields) > 9 else None, #3
|
|
|
|
'error-reason': fields[8].strip() if len(fields) > 9 else "", #3
|
|
|
|
'to-email': fields[9].strip() if len(fields) > 9 else None, #4
|
|
|
|
'to-email': fields[9].strip() if len(fields) > 9 else "", #4
|
|
|
|
'error-plugin': fields[10].strip() if len(fields) > 10 else None, #5
|
|
|
|
'error-plugin': fields[10].strip() if len(fields) > 10 else "", #5
|
|
|
|
'action1': fields[10].strip() if len(fields) > 10 else None, #5
|
|
|
|
'action1': fields[10].strip() if len(fields) > 10 else "", #5
|
|
|
|
'error-number' : fields[11].strip() if len(fields) > 11 else None, #6
|
|
|
|
'error-number' : fields[11].strip() if len(fields) > 11 else "", #6
|
|
|
|
'sender': fields[12].strip() if len(fields) > 12 else None, #7
|
|
|
|
'sender': fields[12].strip() if len(fields) > 12 else "", #7
|
|
|
|
'virus': fields[12].strip() if len(fields) > 12 else None, #7
|
|
|
|
'virus': fields[12].strip() if len(fields) > 12 else "", #7
|
|
|
|
'error-msg' :fields[13].strip() if len(fields) > 13 else None, #7
|
|
|
|
'error-msg' :fields[13].strip() if len(fields) > 13 else "", #7
|
|
|
|
'spam-status': fields[13].strip() if len(fields) > 13 else None, #8
|
|
|
|
'spam-status': fields[13].strip() if len(fields) > 13 else "", #8
|
|
|
|
'error-result': fields[14].strip() if len(fields) > 14 else None,#8
|
|
|
|
'error-result': fields[14].strip() if len(fields) > 14 else "",#8
|
|
|
|
# Add more fields as necessary
|
|
|
|
# Add more fields as necessary
|
|
|
|
}
|
|
|
|
}
|
|
|
|
except:
|
|
|
|
except:
|
|
|
|
logging.error(f"error:len:{len(fields)}")
|
|
|
|
logging.error(f"error:len:{len(fields)}")
|
|
|
|
return_dict = {}
|
|
|
|
return_dict = create_empty_return()
|
|
|
|
return return_dict
|
|
|
|
return return_dict
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def safe_strip(lst, index):
|
|
|
|
|
|
|
|
if 0 <= index < len(lst):
|
|
|
|
|
|
|
|
value = lst[index]
|
|
|
|
|
|
|
|
if value is not None:
|
|
|
|
|
|
|
|
return value.strip()
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_empty_return():
|
|
|
|
|
|
|
|
# Return dictionary with all keys, values None
|
|
|
|
|
|
|
|
keys = [
|
|
|
|
|
|
|
|
'sme', 'qpsmtpd', 'id', 'action', 'logterse', 'ip', 'sendurl', 'sendurl1',
|
|
|
|
|
|
|
|
'from-email', 'error-reason', 'to-email', 'error-plugin', 'action1', 'error-number',
|
|
|
|
|
|
|
|
'sender', 'virus', 'error-msg', 'spam-status', 'error-result'
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
return {key: "" for key in keys}
|
|
|
|
|
|
|
|
|
|
|
|
# def count_entries_by_hour(log_entries):
|
|
|
|
# def count_entries_by_hour(log_entries):
|
|
|
|
# hourly_counts = defaultdict(int)
|
|
|
|
# hourly_counts = defaultdict(int)
|
|
|
|
# for entry in log_entries:
|
|
|
|
# for entry in log_entries:
|
|
|
|