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
27 import traceback as _traceback
29 import logging as _logging
30 if _sys.version_info >= (3,3):
31 from collections import ChainMap as _ChainMap
32 from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR,
33 LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG)
34 from ._journal import __version__, sendv, stream_fd
35 from ._reader import (_Reader, NOP, APPEND, INVALIDATE,
36 LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY,
38 from . import id128 as _id128
40 if _sys.version_info >= (3,):
41 from ._reader import Monotonic
45 def _convert_monotonic(m):
46 return Monotonic((_datetime.timedelta(microseconds=m[0]),
47 _uuid.UUID(bytes=m[1])))
49 def _convert_source_monotonic(s):
50 return _datetime.timedelta(microseconds=int(s))
52 def _convert_realtime(t):
53 return _datetime.datetime.fromtimestamp(t / 1000000)
55 def _convert_timestamp(s):
56 return _datetime.datetime.fromtimestamp(int(s) / 1000000)
58 def _convert_trivial(x):
61 if _sys.version_info >= (3,):
63 return _uuid.UUID(s.decode())
65 _convert_uuid = _uuid.UUID
67 DEFAULT_CONVERTERS = {
68 'MESSAGE_ID': _convert_uuid,
69 '_MACHINE_ID': _convert_uuid,
70 '_BOOT_ID': _convert_uuid,
74 'USERSPACE_USEC': int,
80 'SYSLOG_FACILITY': int,
82 '_AUDIT_SESSION': int,
83 '_AUDIT_LOGINUID': int,
84 '_SYSTEMD_SESSION': int,
85 '_SYSTEMD_OWNER_UID': int,
89 '_SOURCE_REALTIME_TIMESTAMP': _convert_timestamp,
90 '__REALTIME_TIMESTAMP': _convert_realtime,
91 '_SOURCE_MONOTONIC_TIMESTAMP': _convert_source_monotonic,
92 '__MONOTONIC_TIMESTAMP': _convert_monotonic,
93 '__CURSOR': _convert_trivial,
98 'COREDUMP_SESSION': int,
99 'COREDUMP_SIGNAL': int,
100 'COREDUMP_TIMESTAMP': _convert_timestamp,
103 _IDENT_LETTER = set('ABCDEFGHIJKLMNOPQRTSUVWXYZ_')
105 def _valid_field_name(s):
106 return not (set(s) - _IDENT_LETTER)
108 class Reader(_Reader):
109 """Reader allows the access and filtering of systemd journal
110 entries. Note that in order to access the system journal, a
111 non-root user must be in the `systemd-journal` group.
113 Example usage to print out all informational or higher level
114 messages for systemd-udevd for this boot:
116 >>> j = journal.Reader()
118 >>> j.log_level(journal.LOG_INFO)
119 >>> j.add_match(_SYSTEMD_UNIT="systemd-udevd.service")
121 ... print(entry['MESSAGE'])
123 See systemd.journal-fields(7) for more info on typical fields
124 found in the journal.
126 def __init__(self, flags=0, path=None, converters=None):
127 """Create an instance of Reader, which allows filtering and
128 return of journal entries.
130 Argument `flags` sets open flags of the journal, which can be one
131 of, or ORed combination of constants: LOCAL_ONLY (default) opens
132 journal on local machine only; RUNTIME_ONLY opens only
133 volatile journal files; and SYSTEM_ONLY opens only
134 journal files of system services and the kernel.
136 Argument `path` is the directory of journal files. Note that
137 `flags` and `path` are exclusive.
139 Argument `converters` is a dictionary which updates the
140 DEFAULT_CONVERTERS to convert journal field values. Field
141 names are used as keys into this dictionary. The values must
142 be single argument functions, which take a `bytes` object and
143 return a converted value. When there's no entry for a field
144 name, then the default UTF-8 decoding will be attempted. If
145 the conversion fails with a ValueError, unconverted bytes
146 object will be returned. (Note that ValueEror is a superclass
147 of UnicodeDecodeError).
149 Reader implements the context manager protocol: the journal
150 will be closed when exiting the block.
152 super(Reader, self).__init__(flags, path)
153 if _sys.version_info >= (3,3):
154 self.converters = _ChainMap()
155 if converters is not None:
156 self.converters.maps.append(converters)
157 self.converters.maps.append(DEFAULT_CONVERTERS)
159 self.converters = DEFAULT_CONVERTERS.copy()
160 if converters is not None:
161 self.converters.update(converters)
163 def _convert_field(self, key, value):
164 """Convert value using self.converters[key]
166 If `key` is not present in self.converters, a standard unicode
167 decoding will be attempted. If the conversion (either
168 key-specific or the default one) fails with a ValueError, the
169 original bytes object will be returned.
171 convert = self.converters.get(key, bytes.decode)
173 return convert(value)
175 # Leave in default bytes
178 def _convert_entry(self, entry):
179 """Convert entire journal entry utilising _covert_field"""
181 for key, value in entry.items():
182 if isinstance(value, list):
183 result[key] = [self._convert_field(key, val) for val in value]
185 result[key] = self._convert_field(key, value)
189 """Part of iterator protocol.
195 """Part of iterator protocol.
196 Returns self.get_next() or raises StopIteration.
198 ans = self.get_next()
202 raise StopIteration()
204 if _sys.version_info < (3,):
207 def add_match(self, *args, **kwargs):
208 """Add one or more matches to the filter journal log entries.
209 All matches of different field are combined in a logical AND,
210 and matches of the same field are automatically combined in a
212 Matches can be passed as strings of form "FIELD=value", or
213 keyword arguments FIELD="value".
216 args.extend(_make_line(key, val) for key, val in kwargs.items())
218 super(Reader, self).add_match(arg)
220 def get_next(self, skip=1):
221 """Return the next log entry as a mapping type, currently
222 a standard dictionary of fields.
224 Optional skip value will return the `skip`\-th log entry.
226 Entries will be processed with converters specified during
229 if super(Reader, self)._next(skip):
230 entry = super(Reader, self)._get_all()
232 entry['__REALTIME_TIMESTAMP'] = self._get_realtime()
233 entry['__MONOTONIC_TIMESTAMP'] = self._get_monotonic()
234 entry['__CURSOR'] = self._get_cursor()
235 return self._convert_entry(entry)
238 def get_previous(self, skip=1):
239 """Return the previous log entry as a mapping type,
240 currently a standard dictionary of fields.
242 Optional skip value will return the -`skip`\-th log entry.
244 Entries will be processed with converters specified during
247 Equivalent to get_next(-skip).
249 return self.get_next(-skip)
251 def query_unique(self, field):
252 """Return unique values appearing in the journal for given `field`.
254 Note this does not respect any journal matches.
256 Entries will be processed with converters specified during
259 return set(self._convert_field(field, value)
260 for value in super(Reader, self).query_unique(field))
262 def wait(self, timeout=None):
263 """Wait for a change in the journal. `timeout` is the maximum
264 time in seconds to wait, or None, to wait forever.
266 Returns one of NOP (no change), APPEND (new entries have been
267 added to the end of the journal), or INVALIDATE (journal files
268 have been added or removed).
270 us = -1 if timeout is None else int(timeout * 1000000)
271 return super(Reader, self).wait(us)
273 def seek_realtime(self, realtime):
274 """Seek to a matching journal entry nearest to `realtime` time.
276 Argument `realtime` must be either an integer unix timestamp
277 or datetime.datetime instance.
279 if isinstance(realtime, _datetime.datetime):
280 realtime = float(realtime.strftime("%s.%f")) * 1000000
281 return super(Reader, self).seek_realtime(int(realtime))
283 def seek_monotonic(self, monotonic, bootid=None):
284 """Seek to a matching journal entry nearest to `monotonic` time.
286 Argument `monotonic` is a timestamp from boot in either
287 seconds or a datetime.timedelta instance. Argument `bootid`
288 is a string or UUID representing which boot the monotonic time
289 is reference to. Defaults to current bootid.
291 if isinstance(monotonic, _datetime.timedelta):
292 monotonic = monotonic.totalseconds()
293 monotonic = int(monotonic * 1000000)
294 if isinstance(bootid, _uuid.UUID):
295 bootid = bootid.get_hex()
296 return super(Reader, self).seek_monotonic(monotonic, bootid)
298 def log_level(self, level):
299 """Set maximum log `level` by setting matches for PRIORITY.
302 for i in range(level+1):
303 self.add_match(PRIORITY="%d" % i)
305 raise ValueError("Log level must be 0 <= level <= 7")
307 def messageid_match(self, messageid):
308 """Add match for log entries with specified `messageid`.
310 `messageid` can be string of hexadicimal digits or a UUID
311 instance. Standard message IDs can be found in systemd.id128.
313 Equivalent to add_match(MESSAGE_ID=`messageid`).
315 if isinstance(messageid, _uuid.UUID):
316 messageid = messageid.get_hex()
317 self.add_match(MESSAGE_ID=messageid)
319 def this_boot(self, bootid=None):
320 """Add match for _BOOT_ID equal to current boot ID or the specified boot ID.
322 If specified, bootid should be either a UUID or a 32 digit hex number.
324 Equivalent to add_match(_BOOT_ID='bootid').
327 bootid = _id128.get_boot().hex
329 bootid = getattr(bootid, 'hex', bootid)
330 self.add_match(_BOOT_ID=bootid)
332 def this_machine(self, machineid=None):
333 """Add match for _MACHINE_ID equal to the ID of this machine.
335 If specified, machineid should be either a UUID or a 32 digit hex number.
337 Equivalent to add_match(_MACHINE_ID='machineid').
339 if machineid is None:
340 machineid = _id128.get_machine().hex
342 machineid = getattr(machineid, 'hex', machineid)
343 self.add_match(_MACHINE_ID=machineid)
346 def get_catalog(mid):
347 if isinstance(mid, _uuid.UUID):
349 return _get_catalog(mid)
351 def _make_line(field, value):
352 if isinstance(value, bytes):
353 return field.encode('utf-8') + b'=' + value
355 return field + '=' + value
357 def send(MESSAGE, MESSAGE_ID=None,
358 CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None,
360 r"""Send a message to the journal.
362 >>> journal.send('Hello world')
363 >>> journal.send('Hello, again, world', FIELD2='Greetings!')
364 >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef')
366 Value of the MESSAGE argument will be used for the MESSAGE=
367 field. MESSAGE must be a string and will be sent as UTF-8 to
370 MESSAGE_ID can be given to uniquely identify the type of
371 message. It must be a string or a uuid.UUID object.
373 CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to
374 identify the caller. Unless at least on of the three is given,
375 values are extracted from the stack frame of the caller of
376 send(). CODE_FILE and CODE_FUNC must be strings, CODE_LINE
379 Additional fields for the journal entry can only be specified
380 as keyword arguments. The payload can be either a string or
381 bytes. A string will be sent as UTF-8, and bytes will be sent
382 as-is to the journal.
384 Other useful fields include PRIORITY, SYSLOG_FACILITY,
385 SYSLOG_IDENTIFIER, SYSLOG_PID.
388 args = ['MESSAGE=' + MESSAGE]
390 if MESSAGE_ID is not None:
391 id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID)
392 args.append('MESSAGE_ID=' + id)
394 if CODE_LINE == CODE_FILE == CODE_FUNC == None:
395 CODE_FILE, CODE_LINE, CODE_FUNC = \
396 _traceback.extract_stack(limit=2)[0][:3]
397 if CODE_FILE is not None:
398 args.append('CODE_FILE=' + CODE_FILE)
399 if CODE_LINE is not None:
400 args.append('CODE_LINE={:d}'.format(CODE_LINE))
401 if CODE_FUNC is not None:
402 args.append('CODE_FUNC=' + CODE_FUNC)
404 args.extend(_make_line(key, val) for key, val in kwargs.items())
407 def stream(identifier, priority=LOG_DEBUG, level_prefix=False):
408 r"""Return a file object wrapping a stream to journal.
410 Log messages written to this file as simple newline sepearted
411 text strings are written to the journal.
413 The file will be line buffered, so messages are actually sent
414 after a newline character is written.
416 >>> stream = journal.stream('myapp')
418 <open file '<fdopen>', mode 'w' at 0x...>
419 >>> stream.write('message...\n')
421 will produce the following message in the journal::
424 SYSLOG_IDENTIFIER=myapp
427 Using the interface with print might be more convinient:
429 >>> from __future__ import print_function
430 >>> print('message...', file=stream)
432 priority is the syslog priority, one of `LOG_EMERG`,
433 `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`,
434 `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`.
436 level_prefix is a boolean. If true, kernel-style log priority
437 level prefixes (such as '<1>') are interpreted. See
438 sd-daemon(3) for more information.
441 fd = stream_fd(identifier, priority, level_prefix)
442 return _os.fdopen(fd, 'w', 1)
444 class JournalHandler(_logging.Handler):
445 """Journal handler class for the Python logging framework.
447 Please see the Python logging module documentation for an
448 overview: http://docs.python.org/library/logging.html.
450 To create a custom logger whose messages go only to journal:
452 >>> log = logging.getLogger('custom_logger_name')
453 >>> log.propagate = False
454 >>> log.addHandler(journal.JournalHandler())
455 >>> log.warn("Some message: %s", detail)
457 Note that by default, message levels `INFO` and `DEBUG` are
458 ignored by the logging framework. To enable those log levels:
460 >>> log.setLevel(logging.DEBUG)
462 To redirect all logging messages to journal regardless of where
463 they come from, attach it to the root logger:
465 >>> logging.root.addHandler(journal.JournalHandler())
467 For more complex configurations when using `dictConfig` or
468 `fileConfig`, specify `systemd.journal.JournalHandler` as the
469 handler class. Only standard handler configuration options
470 are supported: `level`, `formatter`, `filters`.
472 To attach journal MESSAGE_ID, an extra field is supported:
475 >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF')
476 >>> log.warn("Message with ID", extra={'MESSAGE_ID': mid})
478 Fields to be attached to all messages sent through this
479 handler can be specified as keyword arguments. This probably
480 makes sense only for SYSLOG_IDENTIFIER and similar fields
481 which are constant for the whole program:
483 >>> journal.JournalHandler(SYSLOG_IDENTIFIER='my-cool-app')
485 The following journal fields will be sent:
486 `MESSAGE`, `PRIORITY`, `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`,
487 `CODE_FUNC`, `LOGGER` (name as supplied to getLogger call),
488 `MESSAGE_ID` (optional, see above), `SYSLOG_IDENTIFIER` (defaults
492 def __init__(self, level=_logging.NOTSET, **kwargs):
493 super(JournalHandler, self).__init__(level)
496 if not _valid_field_name(name):
497 raise ValueError('Invalid field name: ' + name)
498 if 'SYSLOG_IDENTIFIER' not in kwargs:
499 kwargs['SYSLOG_IDENTIFIER'] = _sys.argv[0]
502 def emit(self, record):
503 """Write record as journal event.
505 MESSAGE is taken from the message provided by the
506 user, and PRIORITY, LOGGER, THREAD_NAME,
507 CODE_{FILE,LINE,FUNC} fields are appended
508 automatically. In addition, record.MESSAGE_ID will be
512 msg = self.format(record)
513 pri = self.mapPriority(record.levelno)
514 mid = getattr(record, 'MESSAGE_ID', None)
517 PRIORITY=format(pri),
519 THREAD_NAME=record.threadName,
520 CODE_FILE=record.pathname,
521 CODE_LINE=record.lineno,
522 CODE_FUNC=record.funcName,
525 self.handleError(record)
528 def mapPriority(levelno):
529 """Map logging levels to journald priorities.
531 Since Python log level numbers are "sparse", we have
532 to map numbers in between the standard levels too.
534 if levelno <= _logging.DEBUG:
536 elif levelno <= _logging.INFO:
538 elif levelno <= _logging.WARNING:
540 elif levelno <= _logging.ERROR:
542 elif levelno <= _logging.CRITICAL: