1 # -*- Mode: python; coding:utf-8; indent-tabs-mode: nil -*- */
3 # This file is part of systemd.
5 # Copyright 2012 David Strauss <david@davidstrauss.net>
6 # Copyright 2012 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
7 # Copyright 2012 Marti Raudsepp <marti@juffo.org>
9 # systemd is free software; you can redistribute it and/or modify it
10 # under the terms of the GNU Lesser General Public License as published by
11 # the Free Software Foundation; either version 2.1 of the License, or
12 # (at your option) any later version.
14 # systemd is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 # Lesser General Public License for more details.
19 # You should have received a copy of the GNU Lesser General Public License
20 # along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 from __future__ import division
25 import datetime as _datetime
26 import functools as _functools
28 import traceback as _traceback
30 from os import SEEK_SET, SEEK_CUR, SEEK_END
31 import logging as _logging
32 if _sys.version_info >= (3,):
33 from collections import ChainMap as _ChainMap
34 from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR,
35 LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG)
36 from ._journal import sendv, stream_fd
37 from ._reader import (_Reader, NOP, APPEND, INVALIDATE,
38 LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY)
39 from . import id128 as _id128
41 if _sys.version_info >= (3,):
42 from ._reader import Monotonic
46 def _convert_monotonic(m):
47 return Monotonic((_datetime.timedelta(microseconds=m[0]),
48 _uuid.UUID(bytes=m[1])))
50 def _convert_source_monotonic(s):
51 return _datetime.timedelta(microseconds=int(s))
53 def _convert_realtime(t):
54 return _datetime.datetime.fromtimestamp(t / 1E6)
56 def _convert_timestamp(s):
57 return _datetime.datetime.fromtimestamp(int(s) / 1E6)
59 if _sys.version_info >= (3,):
61 return _uuid.UUID(s.decode())
63 _convert_uuid = _uuid.UUID
65 DEFAULT_CONVERTERS = {
66 'MESSAGE_ID': _convert_uuid,
67 '_MACHINE_ID': _convert_uuid,
68 '_BOOT_ID': _convert_uuid,
72 'USERSPACE_USEC': int,
78 'SYSLOG_FACILITY': int,
80 '_AUDIT_SESSION': int,
81 '_AUDIT_LOGINUID': int,
82 '_SYSTEMD_SESSION': int,
83 '_SYSTEMD_OWNER_UID': int,
87 '_SOURCE_REALTIME_TIMESTAMP': _convert_timestamp,
88 '__REALTIME_TIMESTAMP': _convert_realtime,
89 '_SOURCE_MONOTONIC_TIMESTAMP': _convert_source_monotonic,
90 '__MONOTONIC_TIMESTAMP': _convert_monotonic,
95 'COREDUMP_SESSION': int,
96 'COREDUMP_SIGNAL': int,
97 'COREDUMP_TIMESTAMP': _convert_timestamp,
100 class Reader(_Reader):
101 """Reader allows the access and filtering of systemd journal
102 entries. Note that in order to access the system journal, a
103 non-root user must be in the `adm` group.
105 Example usage to print out all informational or higher level
106 messages for systemd-udevd for this boot:
108 >>> j = journal.Reader()
110 >>> j.log_level(journal.LOG_INFO)
111 >>> j.add_match(_SYSTEMD_UNIT="systemd-udevd.service")
113 ... print(entry['MESSAGE'])
115 See systemd.journal-fields(7) for more info on typical fields
116 found in the journal.
118 def __init__(self, flags=LOCAL_ONLY, path=None, converters=None):
119 """Create an instance of Reader, which allows filtering and
120 return of journal entries.
122 Argument `flags` sets open flags of the journal, which can be one
123 of, or ORed combination of constants: LOCAL_ONLY (default) opens
124 journal on local machine only; RUNTIME_ONLY opens only
125 volatile journal files; and SYSTEM_ONLY opens only
126 journal files of system services and the kernel.
128 Argument `path` is the directory of journal files. Note that
129 currently flags are ignored when `path` is present as they are
130 currently not relevant.
132 Argument `converters` is a dictionary which updates the
133 DEFAULT_CONVERTERS to convert journal field values. Field
134 names are used as keys into this dictionary. The values must
135 be single argument functions, which take a `bytes` object and
136 return a converted value. When there's no entry for a field
137 name, then the default UTF-8 decoding will be attempted. If
138 the conversion fails with a ValueError, unconverted bytes
139 object will be returned. (Note that ValueEror is a superclass
140 of UnicodeDecodeError).
142 super(Reader, self).__init__(flags, path)
143 if _sys.version_info >= (3,3):
144 self.converters = _ChainMap()
145 if converters is not None:
146 self.converters.maps.append(converters)
147 self.converters.maps.append(DEFAULT_CONVERTERS)
149 self.converters = DEFAULT_CONVERTERS.copy()
150 if converters is not None:
151 self.converters.update(converters)
153 def _convert_field(self, key, value):
154 """Convert value using self.converters[key]
156 If `key` is not present in self.converters, a standard unicode
157 decoding will be attempted. If the conversion (either
158 key-specific or the default one) fails with a ValueError, the
159 original bytes object will be returned.
161 convert = self.converters.get(key, bytes.decode)
163 return convert(value)
165 # Leave in default bytes
168 def _convert_entry(self, entry):
169 """Convert entire journal entry utilising _covert_field"""
171 for key, value in entry.items():
172 if isinstance(value, list):
173 result[key] = [self._convert_field(key, val) for val in value]
175 result[key] = self._convert_field(key, value)
178 def add_match(self, *args, **kwargs):
179 """Add one or more matches to the filter journal log entries.
180 All matches of different field are combined in a logical AND,
181 and matches of the same field are automatically combined in a
183 Matches can be passed as strings of form "FIELD=value", or
184 keyword arguments FIELD="value".
187 args.extend(_make_line(key, val) for key, val in kwargs.items())
189 super(Reader, self).add_match(arg)
191 def get_next(self, skip=1):
192 """Return the next log entry as a dictionary of fields.
194 Optional skip value will return the `skip`\-th log entry.
196 Entries will be processed with converters specified during
199 return self._convert_entry(
200 super(Reader, self).get_next(skip))
202 def query_unique(self, field):
203 """Return unique values appearing in the journal for given `field`.
205 Note this does not respect any journal matches.
207 Entries will be processed with converters specified during
210 return set(self._convert_field(field, value)
211 for value in super(Reader, self).query_unique(field))
213 def seek_realtime(self, realtime):
214 """Seek to a matching journal entry nearest to `realtime` time.
216 Argument `realtime` must be either an integer unix timestamp
217 or datetime.datetime instance.
219 if isinstance(realtime, _datetime.datetime):
220 realtime = float(realtime.strftime("%s.%f"))
221 return super(Reader, self).seek_realtime(realtime)
223 def seek_monotonic(self, monotonic, bootid=None):
224 """Seek to a matching journal entry nearest to `monotonic` time.
226 Argument `monotonic` is a timestamp from boot in either
227 seconds or a datetime.timedelta instance. Argument `bootid`
228 is a string or UUID representing which boot the monotonic time
229 is reference to. Defaults to current bootid.
231 if isinstance(monotonic, _datetime.timedelta):
232 monotonic = monotonic.totalseconds()
233 if isinstance(bootid, _uuid.UUID):
234 bootid = bootid.get_hex()
235 return super(Reader, self).seek_monotonic(monotonic, bootid)
237 def log_level(self, level):
238 """Set maximum log `level` by setting matches for PRIORITY.
241 for i in range(level+1):
242 self.add_match(PRIORITY="%d" % i)
244 raise ValueError("Log level must be 0 <= level <= 7")
246 def messageid_match(self, messageid):
247 """Add match for log entries with specified `messageid`.
249 `messageid` can be string of hexadicimal digits or a UUID
250 instance. Standard message IDs can be found in systemd.id128.
252 Equivalent to add_match(MESSAGE_ID=`messageid`).
254 if isinstance(messageid, _uuid.UUID):
255 messageid = messageid.get_hex()
256 self.add_match(MESSAGE_ID=messageid)
258 def this_boot(self, bootid=None):
259 """Add match for _BOOT_ID equal to current boot ID or the specified boot ID.
261 If specified, bootid should be either a UUID or a 32 digit hex number.
263 Equivalent to add_match(_BOOT_ID='bootid').
266 bootid = _id128.get_boot().hex
268 bootid = getattr(bootid, 'hex', bootid)
269 self.add_match(_BOOT_ID=bootid)
271 def this_machine(self, machineid=None):
272 """Add match for _MACHINE_ID equal to the ID of this machine.
274 If specified, machineid should be either a UUID or a 32 digit hex number.
276 Equivalent to add_match(_MACHINE_ID='machineid').
278 if machineid is None:
279 machineid = _id128.get_machine().hex
281 machineid = getattr(machineid, 'hex', machineid)
282 self.add_match(_MACHINE_ID=machineid)
285 def _make_line(field, value):
286 if isinstance(value, bytes):
287 return field.encode('utf-8') + b'=' + value
289 return field + '=' + value
291 def send(MESSAGE, MESSAGE_ID=None,
292 CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None,
294 r"""Send a message to the journal.
296 >>> journal.send('Hello world')
297 >>> journal.send('Hello, again, world', FIELD2='Greetings!')
298 >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef')
300 Value of the MESSAGE argument will be used for the MESSAGE=
301 field. MESSAGE must be a string and will be sent as UTF-8 to
304 MESSAGE_ID can be given to uniquely identify the type of
305 message. It must be a string or a uuid.UUID object.
307 CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to
308 identify the caller. Unless at least on of the three is given,
309 values are extracted from the stack frame of the caller of
310 send(). CODE_FILE and CODE_FUNC must be strings, CODE_LINE
313 Additional fields for the journal entry can only be specified
314 as keyword arguments. The payload can be either a string or
315 bytes. A string will be sent as UTF-8, and bytes will be sent
316 as-is to the journal.
318 Other useful fields include PRIORITY, SYSLOG_FACILITY,
319 SYSLOG_IDENTIFIER, SYSLOG_PID.
322 args = ['MESSAGE=' + MESSAGE]
324 if MESSAGE_ID is not None:
325 id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID)
326 args.append('MESSAGE_ID=' + id)
328 if CODE_LINE == CODE_FILE == CODE_FUNC == None:
329 CODE_FILE, CODE_LINE, CODE_FUNC = \
330 _traceback.extract_stack(limit=2)[0][:3]
331 if CODE_FILE is not None:
332 args.append('CODE_FILE=' + CODE_FILE)
333 if CODE_LINE is not None:
334 args.append('CODE_LINE={:d}'.format(CODE_LINE))
335 if CODE_FUNC is not None:
336 args.append('CODE_FUNC=' + CODE_FUNC)
338 args.extend(_make_line(key, val) for key, val in kwargs.items())
341 def stream(identifier, priority=LOG_DEBUG, level_prefix=False):
342 r"""Return a file object wrapping a stream to journal.
344 Log messages written to this file as simple newline sepearted
345 text strings are written to the journal.
347 The file will be line buffered, so messages are actually sent
348 after a newline character is written.
350 >>> stream = journal.stream('myapp')
352 <open file '<fdopen>', mode 'w' at 0x...>
353 >>> stream.write('message...\n')
355 will produce the following message in the journal::
358 SYSLOG_IDENTIFIER=myapp
361 Using the interface with print might be more convinient:
363 >>> from __future__ import print_function
364 >>> print('message...', file=stream)
366 priority is the syslog priority, one of `LOG_EMERG`,
367 `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`,
368 `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`.
370 level_prefix is a boolean. If true, kernel-style log priority
371 level prefixes (such as '<1>') are interpreted. See
372 sd-daemon(3) for more information.
375 fd = stream_fd(identifier, priority, level_prefix)
376 return _os.fdopen(fd, 'w', 1)
378 class JournalHandler(_logging.Handler):
379 """Journal handler class for the Python logging framework.
381 Please see the Python logging module documentation for an
382 overview: http://docs.python.org/library/logging.html.
384 To create a custom logger whose messages go only to journal:
386 >>> log = logging.getLogger('custom_logger_name')
387 >>> log.propagate = False
388 >>> log.addHandler(journal.JournalHandler())
389 >>> log.warn("Some message: %s", detail)
391 Note that by default, message levels `INFO` and `DEBUG` are
392 ignored by the logging framework. To enable those log levels:
394 >>> log.setLevel(logging.DEBUG)
396 To attach journal MESSAGE_ID, an extra field is supported:
399 >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF')
400 >>> log.warn("Message with ID", extra={'MESSAGE_ID': mid})
402 To redirect all logging messages to journal regardless of where
403 they come from, attach it to the root logger:
405 >>> logging.root.addHandler(journal.JournalHandler())
407 For more complex configurations when using `dictConfig` or
408 `fileConfig`, specify `systemd.journal.JournalHandler` as the
409 handler class. Only standard handler configuration options
410 are supported: `level`, `formatter`, `filters`.
412 The following journal fields will be sent:
413 `MESSAGE`, `PRIORITY`, `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`,
414 `CODE_FUNC`, `LOGGER` (name as supplied to getLogger call),
415 `MESSAGE_ID` (optional, see above).
418 def emit(self, record):
419 """Write record as journal event.
421 MESSAGE is taken from the message provided by the
422 user, and PRIORITY, LOGGER, THREAD_NAME,
423 CODE_{FILE,LINE,FUNC} fields are appended
424 automatically. In addition, record.MESSAGE_ID will be
428 msg = self.format(record)
429 pri = self.mapPriority(record.levelno)
430 mid = getattr(record, 'MESSAGE_ID', None)
433 PRIORITY=format(pri),
435 THREAD_NAME=record.threadName,
436 CODE_FILE=record.pathname,
437 CODE_LINE=record.lineno,
438 CODE_FUNC=record.funcName)
440 self.handleError(record)
443 def mapPriority(levelno):
444 """Map logging levels to journald priorities.
446 Since Python log level numbers are "sparse", we have
447 to map numbers in between the standard levels too.
449 if levelno <= _logging.DEBUG:
451 elif levelno <= _logging.INFO:
453 elif levelno <= _logging.WARNING:
455 elif levelno <= _logging.ERROR:
457 elif levelno <= _logging.CRITICAL: