View | Details | Raw Unified | Return to bug 44928
Collapse All | Expand All

(-)a/management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/logcheck.py (-2 / +258 lines)
Line 0    Link Here 
0
- 
1
#!/usr/bin/python2.7
1
--
2
# coding: utf-8
3
#
4
# Univention Management Console module:
5
#  System Diagnosis UMC module
6
#
7
# Copyright 2017 Univention GmbH
8
#
9
# http://www.univention.de/
10
#
11
# All rights reserved.
12
#
13
# The source code of this program is made available
14
# under the terms of the GNU Affero General Public License version 3
15
# (GNU AGPL V3) as published by the Free Software Foundation.
16
#
17
# Binary versions of this program provided by Univention to you as
18
# well as other copyrighted, protected or trademarked materials like
19
# Logos, graphics, fonts, specific documentations and configurations,
20
# cryptographic keys etc. are subject to a license agreement between
21
# you and Univention and not subject to the GNU AGPL V3.
22
#
23
# In the case you use this program under the terms of the GNU AGPL V3,
24
# the program is provided in the hope that it will be useful,
25
# but WITHOUT ANY WARRANTY; without even the implied warranty of
26
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
# GNU Affero General Public License for more details.
28
#
29
# You should have received a copy of the GNU Affero General Public
30
# License with the Debian GNU/Linux or Univention distribution in file
31
# /usr/share/common-licenses/AGPL-3; if not, see
32
# <http://www.gnu.org/licenses/>.
33
34
import re
35
import mmap
36
import errno
37
import socket
38
import itertools as it
39
40
from univention.management.console.modules.diagnostic import Warning
41
42
from univention.lib.i18n import Translation
43
_ = Translation('univention-management-console-module-diagnostic').translate
44
45
title = _('Check logfiles for errors')
46
description = _('No errors found in logfiles.')
47
48
49
class Logcheck(object):
50
	# The `error_regex` should be a `re.compile()`ed regex matching error
51
	# markers in log messages. Log messages are either single log lines, or
52
	# multi line log entries, if `split_messages()` is overridden (as in
53
	# `MultiLineLogcheck`). This is used as `error_regex.search()` to find log
54
	# messages that contain any error marker.
55
	# example: `ERROR_REGEX = re.compile('(ERROR)|(CRITICAL)')`
56
	ERROR_REGEX = None
57
58
	# The `start_regex` should be a `re.compile()`ed regex matching the start
59
	# of a service. This should include the start of the line and not only the
60
	# start marker. This is used as `start_regex.finditer()` to find the last
61
	# service start in a logfile and seek to it.
62
	# example: `START_REGEX = re.compile('^.*Starting service xxx')`
63
	START_REGEX = None
64
65
	def __init__(self, filename, error_regex=None, start_regex=None):
66
		self.filename = filename
67
		self.error_regex = error_regex or self.ERROR_REGEX
68
		self.start_regex = start_regex or self.START_REGEX
69
70
		self.fob = open(self.filename)
71
		# We use a `mmap`, as we can't use `re` on files and reading all the
72
		# content of a file might by too slow.
73
		self.mmap = mmap.mmap(self.fob.fileno(), 0, access=mmap.ACCESS_READ)
74
75
		if self.start_regex is not None:
76
			self._seek_to_last_start()
77
78
	@classmethod
79
	def with_file(cls, filename, **kwargs):
80
		def wrapper():
81
			return cls(filename, **kwargs)
82
		return wrapper
83
84
	@classmethod
85
	def with_error_phrases(cls, filename, *phrases, **kwargs):
86
		regex = re.compile('|'.join('({})'.format(phrase) for phrase in phrases))
87
		return cls.with_file(filename, error_regex=regex, **kwargs)
88
89
	def __enter__(self):
90
		return self
91
92
	def __exit__(self):
93
		self.close()
94
95
	def _seek_to_last_start(self):
96
		# find the last `match` object by exhausting the iterator
97
		match = None
98
		for match in self.start_regex.finditer(self.mmap):
99
			pass
100
		start = match.start() if match is not None else 0
101
		self.mmap.seek(start)
102
103
	def close(self):
104
		if self.mmap is not None:
105
			self.mmap.close()
106
		if self.fob is not None:
107
			self.fob.close()
108
109
	def split_messages(self):
110
		'''
111
		Yield all lines like `file.splitlines()`.
112
		Override this for multiline log entries.
113
		'''
114
		while True:
115
			line = self.mmap.readline()
116
			if line == '':
117
				break
118
			yield line.rstrip('\n')
119
120
	def errors(self):
121
		for message in self.split_messages():
122
			search = self.error_regex.search(message)
123
			if search is not None and search.span() != (0, 0):
124
				yield message
125
126
127
class MultiLineLogcheck(Logcheck):
128
	# The `message_start_regex` should be a `re.compile()`ed regex matching the
129
	# start of a multiline log message. This is used to group together multiple
130
	# log lines and used as `message_start_regex.search()` to find if a line is
131
	# a message start.
132
	MESSAGE_START_REGEX = None
133
134
	def __init__(self, filename, message_start_regex=None, **kwargs):
135
		super(MultiLineLogcheck, self).__init__(filename, **kwargs)
136
		self.message_start_regex = message_start_regex or self.MESSAGE_START_REGEX
137
138
	def split_messages(self):
139
		chunk = list()
140
		for line in super(MultiLineLogcheck, self).split_messages():
141
			if self.message_start_regex.search(line) is not None:
142
				if chunk:
143
					yield '\n'.join(chunk)
144
					chunk = list()
145
			chunk.append(line)
146
		if chunk:
147
			yield '\n'.join(chunk)
148
149
150
class SyslogLogCheck(Logcheck):
151
	SERVICE_NAME = None
152
153
	def __init__(self, filename, service_name=None, **kwargs):
154
		super(SyslogLogCheck, self).__init__(filename, **kwargs)
155
		self.service_name = service_name or self.SERVICE_NAME
156
157
	def split_messages(self):
158
		if self.service_name is not None:
159
			regex_tmpl = '^.*({})|(unassigned-hostname) {}\[\d+\]:'
160
			service_regex = re.compile(regex_tmpl.format(socket.gethostname(), self.service_name))
161
			for line in super(SyslogLogCheck, self).split_messages():
162
				if service_regex.search(line) is not None:
163
					yield line
164
		else:
165
			for line in super(SyslogLogCheck, self).split_messages():
166
				yield line
167
168
169
class SambaLogcheck(MultiLineLogcheck):
170
	# comma separated logging header (level is not reliable):
171
	# <date>, <level> [, pid=<pid>] [, <effective user>, <real user>]
172
	# see samba source: lib/util/debug.c `dbghdrclass()`
173
	MESSAGE_START_REGEX = re.compile((
174
		'^\['
175
		'\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}.\d{6}'
176
		', ( \d)|(\d{2}).'
177
		'(, pid=\d+)?'
178
		'(, effective\(\d+, \d+\)), real\(\d+, \d+\)?'
179
		'\]'
180
	))
181
182
183
class UniventionDebugLogcheck(Logcheck):
184
	ERROR_REGEX = re.compile('\(\s*ERROR\s*\)')
185
	START_REGEX = re.compile('^.*DEBUG_INIT.*$', re.MULTILINE)
186
187
	@classmethod
188
	def with_levels(cls, filename, *levels, **kwargs):
189
		phrases = ('\(\s*{}\s*\)'.format(l) for l in levels)
190
		return cls.with_error_phrases(filename, *phrases, **kwargs)
191
192
193
class MultiLineUniventionDebugLogcheck(MultiLineLogcheck, UniventionDebugLogcheck):
194
	MESSAGE_START_REGEX = re.compile('^\d{2}.\d{2}.\d{4} \d{2}:\d{2}:\d{2},\d{3}')
195
196
197
class UniventionUpdaterLogcheck(UniventionDebugLogcheck):
198
	ERROR_REGEX = re.compile('(CRITICAL)|(error code)|(Hash Sum mismatch)')
199
	START_REGEX = re.compile('^Starting univention-upgrade. Current UCS version is', re.MULTILINE)
200
201
202
LOGFILES = {
203
	'univention/listener':
204
		UniventionDebugLogcheck.with_file('/var/log/univention/listener.log'),
205
	'univention/notifier':
206
		UniventionDebugLogcheck.with_file('/var/log/univention/notifier.log'),
207
	'univention/updater':
208
		UniventionUpdaterLogcheck.with_file('/var/log/univention/updater.log'),
209
	'univention/s4connector':
210
		MultiLineUniventionDebugLogcheck.with_levels('/var/log/univention/connector-s4.log',
211
			'ERROR', 'WARNING'),
212
	'syslog/named':
213
		SyslogLogCheck.with_file('/var/log/syslog', service_name='named',
214
			error_regex=re.compile("update '.*' denied")),
215
	'samba/samba':
216
		SambaLogcheck.with_error_phrases('/var/log/samba/log.samba', 'BACKTRACE'),
217
	'samba/smbd':
218
		SambaLogcheck.with_error_phrases('/var/log/samba/log.smbd', 'BACKTRACE'),
219
}
220
221
222
class ErrorFound(Exception):
223
	def __init__(self, name, filename, error_message):
224
		super(ErrorFound, self).__init__(name, filename, error_message)
225
		self.name = name
226
		self.filename = filename
227
		self.error_message = error_message
228
229
	def __str__(self):
230
		return self.error_message
231
232
233
def find_log_errors():
234
	for (name, check_wrapper) in LOGFILES.iteritems():
235
		try:
236
			check = check_wrapper()
237
		except IOError as error:
238
			if error.errno != errno.ENOENT:
239
				raise
240
		else:
241
			for error in check.errors():
242
				yield ErrorFound(name, check.filename, error)
243
244
245
def run():
246
	errors = list(find_log_errors())
247
	if errors:
248
		ed = [_('Some errors in logfiles found.')]
249
		grouped = it.groupby(errors, lambda e: (e.name, e.filename))
250
		for ((name, filename), group) in grouped:
251
			ed.append(_('\nIn logfile {log!r}:').format(log=filename))
252
			ed.extend(str(e) for e in group)
253
		raise Warning(description='\n'.join(ed))
254
255
256
if __name__ == '__main__':
257
	from univention.management.console.modules.diagnostic import main
258
	main()
2
.../umc/python/diagnostic/de.po                    | 24 ++++++++++++++++++++--
259
.../umc/python/diagnostic/de.po                    | 24 ++++++++++++++++++++--
3
1 file changed, 22 insertions(+), 2 deletions(-)
260
1 file changed, 22 insertions(+), 2 deletions(-)
(-)a/management/univention-management-console-module-diagnostic/umc/python/diagnostic/de.po (-3 / +22 lines)
 Lines 2-9    Link Here 
2
msgid ""
2
msgid ""
3
msgstr ""
3
msgstr ""
4
"Project-Id-Version: univention-management-console-module-diagnostic\n"
4
"Project-Id-Version: univention-management-console-module-diagnostic\n"
5
"Report-Msgid-Bugs-To: packages@univention.de\n"
5
"Report-Msgid-Bugs-To: \n"
6
"POT-Creation-Date: 2016-01-14 12:19+0100\n"
6
"POT-Creation-Date: 2017-07-04 12:14+0200\n"
7
"PO-Revision-Date: \n"
7
"PO-Revision-Date: \n"
8
"Last-Translator: Univention GmbH <packages@univention.de>\n"
8
"Last-Translator: Univention GmbH <packages@univention.de>\n"
9
"Language-Team: Univention GmbH <packages@univention.de>\n"
9
"Language-Team: Univention GmbH <packages@univention.de>\n"
 Lines 12-17   msgstr "" Link Here 
12
"Content-Type: text/plain; charset=UTF-8\n"
12
"Content-Type: text/plain; charset=UTF-8\n"
13
"Content-Transfer-Encoding: 8bit\n"
13
"Content-Transfer-Encoding: 8bit\n"
14
14
15
#: umc/python/diagnostic/plugins/logcheck.py:251
16
msgid ""
17
"\n"
18
"In logfile {log!r}:"
19
msgstr ""
20
"\n"
21
"In der Logdatei {log!r}:"
22
15
#: umc/python/diagnostic/plugins/nameserver.py:15
23
#: umc/python/diagnostic/plugins/nameserver.py:15
16
#, python-format
24
#, python-format
17
msgid "%d of the configured nameservers are not responding to DNS queries."
25
msgid "%d of the configured nameservers are not responding to DNS queries."
 Lines 27-32   msgstr "" Link Here 
27
msgid "Adjust to suggested limits"
35
msgid "Adjust to suggested limits"
28
msgstr "An vorgeschlagene Limits anpassen"
36
msgstr "An vorgeschlagene Limits anpassen"
29
37
38
#: umc/python/diagnostic/plugins/logcheck.py:45
39
msgid "Check logfiles for errors"
40
msgstr "Überprüfe Logdateien auf Fehler"
41
30
#: umc/python/diagnostic/plugins/gateway.py:11
42
#: umc/python/diagnostic/plugins/gateway.py:11
31
msgid "Gateway is not reachable"
43
msgid "Gateway is not reachable"
32
msgstr "Gateway ist nicht erreichbar"
44
msgstr "Gateway ist nicht erreichbar"
 Lines 97-102   msgstr "" Link Here 
97
msgid "Nameserver(s) are not responsive"
109
msgid "Nameserver(s) are not responsive"
98
msgstr "Nameserver sind nicht ansprechbar"
110
msgstr "Nameserver sind nicht ansprechbar"
99
111
112
#: umc/python/diagnostic/plugins/logcheck.py:46
113
msgid "No errors found in logfiles."
114
msgstr "Keine Fehler in Logdateien gefunden."
115
100
#: umc/python/diagnostic/plugins/package_status.py:11
116
#: umc/python/diagnostic/plugins/package_status.py:11
101
msgid "Package status corrupt"
117
msgid "Package status corrupt"
102
msgstr "Paketstatus korrupt"
118
msgstr "Paketstatus korrupt"
 Lines 137-142   msgstr "SSH-Verbindung zu anderem UCS Server fehlgeschlagen!" Link Here 
137
msgid "Security limits exceeded"
153
msgid "Security limits exceeded"
138
msgstr "Sicherheitslimits überschritten"
154
msgstr "Sicherheitslimits überschritten"
139
155
156
#: umc/python/diagnostic/plugins/logcheck.py:248
157
msgid "Some errors in logfiles found."
158
msgstr "Es wurden Fehler in Logdateien gefunden."
159
140
#: umc/python/diagnostic/__init__.py:262
160
#: umc/python/diagnostic/__init__.py:262
141
msgid "Test again"
161
msgid "Test again"
142
msgstr "Erneut testen"
162
msgstr "Erneut testen"
143
- 

Return to bug 44928