Get rid of syslog (or a journald log filter in ~100 lines of Python)
We’ve recently switched the host behind the siosm.fr domain and thus decided it was time we dropped syslog logging entirely and use journald only. We used to get weekly log reports sent by mail by logrotate, but we never read them as they were way too big, thus this was useless.
Note: The ‘we’ here refers to PO and I.
Instead, we’ve written a replacement using the Python 3 module that comes with journald.
This simple script is based on the sound principles described by Marcus J. Ranum in: artificial ignorance: how-to guide and The Six Dumbest Ideas in Computer Security (especially #2: Enumerating Badness).
The output is so small, we’ve made it a daily run script, allowing us to catch real issues as soon as they appear.
Here is the full script, with included comments as there is nothing complex here:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/usr/bin/env python3
from systemd import journal
from datetime import datetime,timedelta
import re
import smtplib
from email.mime.text import MIMEText
# First, let's define patterns to ignore.
# Those are for matching dynamically named units:
session = re.compile("session-[a-z]?\d+.scope")
sshUnit = re.compile("sshd@[0-9a-f.:]*")
# Those will match the logged message itself:
sshSocketStart = re.compile("(Starting|Stopping) OpenSSH Per-Connection Daemon.*")
sshAcceptPublicKey = re.compile("Accepted publickey for (bob|alice).*")
sshReceivedDisconnect = re.compile("Received disconnect from.*")
logindNewSession = re.compile("New session [a-z]?\d+ of user (bob|alice).*")
sshdSessionClosed = re.compile(".*session closed for user (bob|alice).*")
sessionOpenedRoot = re.compile(".*session opened for user root.*")
suSessionClosedGit = re.compile(".*session opened for user git.*")
anacronNormalExit = re.compile("Normal exit (\d+ jobs run).*")
postfixStatistics = re.compile("statistics:.*")
postfixHostnameDoesNotResolve = re.compile("warning: hostname .* does not resolve to address .*: Name or service not known")
# Open the journal for reading, set log level and go back one day and 10 minutes
j = journal.Reader()
j.log_level(journal.LOG_INFO)
yesterday = datetime.now() - timedelta(days=1, minutes=10)
j.seek_realtime(yesterday)
# We'll store messages in this variable
mailContent = []
# Filter and store output
for entry in j:
# Special cases for logs without a message
if 'MESSAGE' not in entry:
mailContent.append( 'U %s %s[%s]: EMPTY!' % (
datetime.ctime(entry['__REALTIME_TIMESTAMP']),
entry['PRIORITY'],
entry['SYSLOG_IDENTIFIER'],
))
# With systemd unit name
elif '_SYSTEMD_UNIT' in entry:
if entry['PRIORITY'] > 4:
if entry['_SYSTEMD_UNIT'] == "wtcomments.service":
pass
elif entry['_SYSTEMD_UNIT'] == "ffsync.service":
pass
elif session.match(entry['_SYSTEMD_UNIT']):
pass
elif sshUnit.match(entry['_SYSTEMD_UNIT']):
if sshAcceptPublicKey.match(entry['MESSAGE']):
pass
elif sshReceivedDisconnect.match(entry['MESSAGE']):
pass
elif entry['_SYSTEMD_UNIT'] == "systemd-logind.service":
if logindNewSession.match(entry['MESSAGE']):
pass
elif entry['_SYSTEMD_UNIT'] == "postfix.service":
if postfixHostnameDoesNotResolve.match(entry['MESSAGE']):
pass
else:
mailContent.append( 'U %s %s %s %s[%s]: %s' % (
datetime.ctime(entry['__REALTIME_TIMESTAMP']),
entry['PRIORITY'],
entry['_SYSTEMD_UNIT'],
entry['SYSLOG_IDENTIFIER'],
entry['_PID'],
entry['MESSAGE']
))
# With syslog identifier only
elif entry['SYSLOG_IDENTIFIER'] == "systemd":
if sshSocketStart.match(entry['MESSAGE']):
pass
elif firewalldStart.match(entry['MESSAGE']):
pass
elif entry['SYSLOG_IDENTIFIER'] == "sshd":
if sshdSessionClosed.match(entry['MESSAGE']):
pass
elif entry['SYSLOG_IDENTIFIER'] == "sudo":
if sessionOpenedRoot.match(entry['MESSAGE']):
pass
elif entry['SYSLOG_IDENTIFIER'] == "CROND":
if sessionOpenedRoot.match(entry['MESSAGE']):
pass
elif entry['SYSLOG_IDENTIFIER'] == "anacron":
if anacronNormalExit.match(entry['MESSAGE']):
pass
elif entry['SYSLOG_IDENTIFIER'] == "postfix/anvil":
if postfixStatistics.match(entry['MESSAGE']):
pass
elif entry['SYSLOG_IDENTIFIER'] == "su":
if suSessionClosedGit.match(entry['MESSAGE']):
pass
else:
mailContent.append( 'S %s %s %s: %s' % (
datetime.ctime(entry['__REALTIME_TIMESTAMP']),
entry['PRIORITY'],
entry['SYSLOG_IDENTIFIER'],
entry['MESSAGE']
))
# Send the content in a mail to root
mail = MIMEText('\n'.join(mailContent))
mail['Subject'] = '[example.com] Logs from ' + datetime.ctime(yesterday) + ' to ' + datetime.ctime(datetime.now())
mail['From'] = 'journald@example.com'
mail['To'] = 'root@example.com'
server = smtplib.SMTP('localhost')
server.send_message(mail)
server.quit()
Comments
You can also contact me directly if you have feedback.