1# -*- coding: utf-8 -*-
2
3"""
4List the logged (executed) cron jobs and their commands (uses journald/logfile)
5NOTE: If using journald, the log level for cron.service should be at least 2
6(default is 1). This can be changed with `sudo systemctl edit cron --full`,
7and ammend `-L 2` to the ExecStart command.
8TODO: also output a list of scheduled (future) jobs
9"""
10
11from systemd import journal
12import datetime
13
14from logparse import config
15from logparse.formatting import *
16from logparse.load_parsers import Parser
17from logparse.parsers.cron import CronCommand
18
19
20class CronJournald(Parser):
21
22 def __init__(self):
23 super().__init__()
24 self.name = "cron_journald"
25 self.info = "List the logged (executed) cron jobs and their commands " \
26 "(uses journald module)"
27
28 def parse_log(self):
29
30 logger.debug("Starting cron section")
31 section = Section("cron")
32
33 # Initiate journald reader
34 j = journal.Reader()
35 j.this_machine()
36 j.log_level(journal.LOG_INFO)
37 j.add_match(_COMM="cron")
38 j.seek_realtime(section.period.startdate)
39
40 logger.info("Obtaining cron logs")
41
42 records = [entry for entry in j
43 if "MESSAGE" in entry and " CMD " in entry["MESSAGE"]]
44
45 if len(records) == 0:
46 logger.warning("Couldn't find any cron commands")
47 return 1
48
49 logger.info("Found {0} log records".format(len(records)))
50
51 logger.debug("Analysing cron commands for each user")
52 command_objects = []
53 users = {}
54 for record in records:
55 if record["_SOURCE_REALTIME_TIMESTAMP"] < section.period.startdate:
56 logger.warning("Discarding log record from {0} - was "
57 "seek_realtime set properly?".format(
58 record["_SOURCE_REALTIME_TIMESTAMP"]))
59 continue
60 try:
61 cmd_obj = CronCommand(record)
62 if config.prefs.getboolean("cron", "truncate-commands"):
63 cmd_obj.truncate()
64 if not (cmd_obj.match_user(config.prefs.get("cron", "users")
65 .split()) and cmd_obj.match_cmd(config.prefs.get(
66 "cron", "commands").split())):
67 logger.debug("Ignoring cron session by {0} with command "
68 "{1} due to config".format(cmd_obj.user, cmd_obj.cmd))
69 continue
70 command_objects.append(cmd_obj)
71 if not cmd_obj.user in users:
72 users[cmd_obj.user] = []
73 users[cmd_obj.user].append(cmd_obj.cmd)
74 except Exception as e:
75 logger.warning("Malformed cron log message: {0}. "
76 "Error message: {1}".format(record["MESSAGE"], str(e)))
77 continue
78
79 logger.info("Found {0} valid cron sessions".format(len(command_objects)))
80
81 if config.prefs.getboolean("cron", "summary"):
82 summary_data = Data()
83 summary_data.subtitle = "Total of " + plural("cron session",
84 len(command_objects)) + " for " + plural("user",
85 len(users))
86 summary_data.items = ["{}: `{}`".format(c.user, c.cmd)
87 for c in command_objects]
88 summary_data.orderbyfreq()
89 summary_data.truncl(config.prefs.getint("logparse", "maxcmd"))
90 section.append_data(summary_data)
91
92 if config.prefs.getboolean("cron", "list-users"):
93 for user, cmdlist in users.items():
94 user_data = Data()
95 user_data.subtitle = plural("session", len(cmdlist)) \
96 + " for " + user + (" (" + plural("unique command",
97 len(set(cmdlist))) + ")" if len(set(cmdlist)) > 1
98 else "")
99 user_data.items = ["`{0}`".format(cmd) for cmd in cmdlist]
100 user_data.orderbyfreq()
101 user_data.truncl(config.prefs.getint("logparse", "maxcmd"))
102 section.append_data(user_data)
103 logger.debug("Found {0} cron sessions for user {1} "
104 "({2} unique commands): {3}".format(
105 len(cmdlist), user,
106 len(set(cmdlist)), user_data.items))
107
108
109 logger.info("Finished cron section")
110
111 return section