rename parsers, better journald integration
[logparse.git] / logparse / parsers / cron_journald.py
index 13d9245560aefca7328ce633c093239b9522b928..cbd62d40e80e9ca134c20d28c34440ea29a87c8b 100644 (file)
@@ -1,23 +1,29 @@
-#
-#   cron_journald.py
-#
-#   List the logged (executed) cron jobs and their commands (uses journald module)
-#
-#   TODO: also output a list of scheduled (future) jobs
-#
+# -*- coding: utf-8 -*-
+
+"""
+List the logged (executed) cron jobs and their commands (uses journald/logfile)
+NOTE: If using journald, the log level for cron.service should be at least 2 
+(default is 1). This can be changed with `sudo systemctl edit cron --full`, 
+and ammend `-L 2` to the ExecStart command.
+TODO: also output a list of scheduled (future) jobs
+"""
 
 from systemd import journal
+import datetime
 
 from logparse import config
 from logparse.formatting import *
 from logparse.load_parsers import Parser
+from logparse.parsers.cron import CronCommand
+
 
 class CronJournald(Parser):
 
     def __init__(self):
         super().__init__()
         self.name = "cron_journald"
-        self.info = "List the logged (executed) cron jobs and their commands (uses journald module)"
+        self.info = "List the logged (executed) cron jobs and their commands " \
+                "(uses journald module)"
 
     def parse_log(self):
 
@@ -33,34 +39,72 @@ class CronJournald(Parser):
 
         logger.info("Obtaining cron logs")
 
-        messages = [entry["MESSAGE"] for entry in j if "MESSAGE" in entry and " CMD " in entry["MESSAGE"]]
-
-        total_jobs = len(messages)
+        records = [entry for entry in j
+                if "MESSAGE" in entry and " CMD " in entry["MESSAGE"]]
 
-        if total_jobs == 0:
+        if len(records) == 0:
             logger.warning("Couldn't find any cron commands")
             return 1
 
-        logger.info("Found " + str(total_jobs) + " cron jobs")
-        section.append_data(Data("Total of " + plural("cron session", total_jobs) + " executed across all users"))
+        logger.info("Found {0} log records".format(len(records)))
 
         logger.debug("Analysing cron commands for each user")
+        command_objects = []
         users = {}
+        for record in records:
+            if record["_SOURCE_REALTIME_TIMESTAMP"] < section.period.startdate:
+                logger.warning("Discarding log record from {0} - was "
+                        "seek_realtime set properly?".format(
+                            record["_SOURCE_REALTIME_TIMESTAMP"]))
+                continue
+            try:
+                cmd_obj = CronCommand(record)
+                if config.prefs.getboolean("cron", "truncate-commands"):
+                    cmd_obj.truncate() 
+                if not (cmd_obj.match_user(config.prefs.get("cron", "users")
+                    .split()) and cmd_obj.match_cmd(config.prefs.get(
+                        "cron", "commands").split())):
+                    logger.debug("Ignoring cron session by {0} with command "
+                        "{1} due to config".format(cmd_obj.user, cmd_obj.cmd))
+                    continue
+                command_objects.append(cmd_obj)
+                if not cmd_obj.user in users:
+                    users[cmd_obj.user] = []
+                users[cmd_obj.user].append(cmd_obj.cmd)
+            except Exception as e:
+                logger.warning("Malformed cron log message: {0}. "
+                    "Error message: {1}".format(record["MESSAGE"], str(e)))
+                continue
+
+        logger.info("Found {0} valid cron sessions".format(len(command_objects)))
+
+        if config.prefs.getboolean("cron", "summary"):
+            summary_data = Data()
+            summary_data.subtitle = "Total of " + plural("cron session",
+                    len(command_objects)) + " for " + plural("user",
+                            len(users))
+            summary_data.items = ["{}: `{}`".format(c.user, c.cmd) 
+                    for c in command_objects]
+            summary_data.orderbyfreq()
+            summary_data.truncl(config.prefs.getint("logparse", "maxcmd"))
+            section.append_data(summary_data)
+
+        if config.prefs.getboolean("cron", "list-users"):
+            for user, cmdlist in users.items():
+                user_data = Data()
+                user_data.subtitle = plural("session", len(cmdlist)) \
+                        + " for " + user + (" (" + plural("unique command",
+                            len(set(cmdlist))) + ")" if len(set(cmdlist)) > 1
+                            else "")
+                user_data.items = ["`{0}`".format(cmd) for cmd in cmdlist]
+                user_data.orderbyfreq()
+                user_data.truncl(config.prefs.getint("logparse", "maxcmd"))
+                section.append_data(user_data)
+                logger.debug("Found {0} cron sessions for user {1} "
+                        "({2} unique commands): {3}".format(
+                            len(cmdlist), user, 
+                            len(set(cmdlist)), user_data.items))
 
-        for msg in messages:
-            usr_cmd = re.search('\((\S+)\) CMD (.*)', msg)  # [('user', 'cmd')]
-            if usr_cmd:
-                if not usr_cmd.group(1) in users:
-                    users[usr_cmd.group(1)] = []
-                users[usr_cmd.group(1)].append(usr_cmd.group(2))
-
-        for usr, cmdlist in users.items():
-            user_data = Data()
-            user_data.subtitle = plural("cron session", len(cmdlist)) + " for " + usr
-            user_data.items = ("`{0}`".format(cmd) for cmd in cmdlist)
-            user_data.orderbyfreq()
-            user_data.truncl(config.prefs.getint("logparse", "maxcmd"))
-            section.append_data(user_data)
 
         logger.info("Finished cron section")