]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/python-systemd/journal.py
networkd: rename link_update_flags to link_update
[thirdparty/systemd.git] / src / python-systemd / journal.py
CommitLineData
927e9632 1# -*- Mode: python; coding:utf-8; indent-tabs-mode: nil -*- */
6b91ae13
LP
2#
3# This file is part of systemd.
4#
927e9632
ZJS
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>
6b91ae13
LP
8#
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.
13#
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.
18#
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/>.
21
6a6633a1
ZJS
22from __future__ import division
23
b8359823
SH
24import sys as _sys
25import datetime as _datetime
b8359823 26import uuid as _uuid
8d7e170a
LP
27import traceback as _traceback
28import os as _os
73c0495f 29import logging as _logging
b5edbff0 30if _sys.version_info >= (3,3):
5c083fa8 31 from collections import ChainMap as _ChainMap
8d7e170a
LP
32from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR,
33 LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG)
5afbe712 34from ._journal import __version__, sendv, stream_fd
7f41820b 35from ._reader import (_Reader, NOP, APPEND, INVALIDATE,
affba8e9
ZJS
36 LOCAL_ONLY, RUNTIME_ONLY,
37 SYSTEM, SYSTEM_ONLY, CURRENT_USER,
811de196 38 _get_catalog)
6a71de70 39from . import id128 as _id128
8d7e170a 40
86e3d32a
ZJS
41if _sys.version_info >= (3,):
42 from ._reader import Monotonic
43else:
44 Monotonic = tuple
45
aaf08061
ZJS
46def _convert_monotonic(m):
47 return Monotonic((_datetime.timedelta(microseconds=m[0]),
48 _uuid.UUID(bytes=m[1])))
49
50def _convert_source_monotonic(s):
51 return _datetime.timedelta(microseconds=int(s))
52
53def _convert_realtime(t):
806bc1cb 54 return _datetime.datetime.fromtimestamp(t / 1000000)
aaf08061
ZJS
55
56def _convert_timestamp(s):
806bc1cb 57 return _datetime.datetime.fromtimestamp(int(s) / 1000000)
aaf08061 58
0a0c35d1
ZJS
59def _convert_trivial(x):
60 return x
61
aaf08061
ZJS
62if _sys.version_info >= (3,):
63 def _convert_uuid(s):
64 return _uuid.UUID(s.decode())
65else:
66 _convert_uuid = _uuid.UUID
67
3aa8f086 68DEFAULT_CONVERTERS = {
aaf08061
ZJS
69 'MESSAGE_ID': _convert_uuid,
70 '_MACHINE_ID': _convert_uuid,
71 '_BOOT_ID': _convert_uuid,
3aa8f086
SH
72 'PRIORITY': int,
73 'LEADER': int,
74 'SESSION_ID': int,
75 'USERSPACE_USEC': int,
76 'INITRD_USEC': int,
77 'KERNEL_USEC': int,
78 '_UID': int,
79 '_GID': int,
80 '_PID': int,
81 'SYSLOG_FACILITY': int,
82 'SYSLOG_PID': int,
83 '_AUDIT_SESSION': int,
84 '_AUDIT_LOGINUID': int,
85 '_SYSTEMD_SESSION': int,
86 '_SYSTEMD_OWNER_UID': int,
87 'CODE_LINE': int,
88 'ERRNO': int,
89 'EXIT_STATUS': int,
aaf08061
ZJS
90 '_SOURCE_REALTIME_TIMESTAMP': _convert_timestamp,
91 '__REALTIME_TIMESTAMP': _convert_realtime,
92 '_SOURCE_MONOTONIC_TIMESTAMP': _convert_source_monotonic,
93 '__MONOTONIC_TIMESTAMP': _convert_monotonic,
0a0c35d1 94 '__CURSOR': _convert_trivial,
3aa8f086
SH
95 'COREDUMP': bytes,
96 'COREDUMP_PID': int,
97 'COREDUMP_UID': int,
98 'COREDUMP_GID': int,
99 'COREDUMP_SESSION': int,
100 'COREDUMP_SIGNAL': int,
aaf08061 101 'COREDUMP_TIMESTAMP': _convert_timestamp,
3aa8f086
SH
102}
103
b5edbff0
ZJS
104_IDENT_LETTER = set('ABCDEFGHIJKLMNOPQRTSUVWXYZ_')
105
106def _valid_field_name(s):
107 return not (set(s) - _IDENT_LETTER)
108
7f41820b
ZJS
109class Reader(_Reader):
110 """Reader allows the access and filtering of systemd journal
cac40fbe 111 entries. Note that in order to access the system journal, a
614e5fbb 112 non-root user must be in the `systemd-journal` group.
cac40fbe 113
aaf08061
ZJS
114 Example usage to print out all informational or higher level
115 messages for systemd-udevd for this boot:
cac40fbe 116
aaf08061
ZJS
117 >>> j = journal.Reader()
118 >>> j.this_boot()
119 >>> j.log_level(journal.LOG_INFO)
120 >>> j.add_match(_SYSTEMD_UNIT="systemd-udevd.service")
121 >>> for entry in j:
cac40fbe
SH
122 ... print(entry['MESSAGE'])
123
2c076467
ZJS
124 See systemd.journal-fields(7) for more info on typical fields
125 found in the journal.
cac40fbe 126 """
affba8e9 127 def __init__(self, flags=0, path=None, files=None, converters=None):
7f41820b 128 """Create an instance of Reader, which allows filtering and
c71f26eb 129 return of journal entries.
aaf08061 130
c71f26eb
SH
131 Argument `flags` sets open flags of the journal, which can be one
132 of, or ORed combination of constants: LOCAL_ONLY (default) opens
133 journal on local machine only; RUNTIME_ONLY opens only
134 volatile journal files; and SYSTEM_ONLY opens only
135 journal files of system services and the kernel.
aaf08061 136
c71f26eb 137 Argument `path` is the directory of journal files. Note that
0eff0f3b 138 `flags` and `path` are exclusive.
aaf08061
ZJS
139
140 Argument `converters` is a dictionary which updates the
141 DEFAULT_CONVERTERS to convert journal field values. Field
142 names are used as keys into this dictionary. The values must
143 be single argument functions, which take a `bytes` object and
144 return a converted value. When there's no entry for a field
145 name, then the default UTF-8 decoding will be attempted. If
146 the conversion fails with a ValueError, unconverted bytes
147 object will be returned. (Note that ValueEror is a superclass
148 of UnicodeDecodeError).
811de196
ZJS
149
150 Reader implements the context manager protocol: the journal
151 will be closed when exiting the block.
c71f26eb 152 """
affba8e9 153 super(Reader, self).__init__(flags, path, files)
b8359823 154 if _sys.version_info >= (3,3):
5c083fa8 155 self.converters = _ChainMap()
3aa8f086
SH
156 if converters is not None:
157 self.converters.maps.append(converters)
158 self.converters.maps.append(DEFAULT_CONVERTERS)
2d0603bd 159 else:
3aa8f086
SH
160 self.converters = DEFAULT_CONVERTERS.copy()
161 if converters is not None:
162 self.converters.update(converters)
163
164 def _convert_field(self, key, value):
aaf08061
ZJS
165 """Convert value using self.converters[key]
166
167 If `key` is not present in self.converters, a standard unicode
168 decoding will be attempted. If the conversion (either
169 key-specific or the default one) fails with a ValueError, the
170 original bytes object will be returned.
171 """
172 convert = self.converters.get(key, bytes.decode)
3aa8f086 173 try:
aaf08061
ZJS
174 return convert(value)
175 except ValueError:
176 # Leave in default bytes
177 return value
3aa8f086
SH
178
179 def _convert_entry(self, entry):
2c076467 180 """Convert entire journal entry utilising _covert_field"""
3aa8f086 181 result = {}
bf1ced55 182 for key, value in entry.items():
3aa8f086
SH
183 if isinstance(value, list):
184 result[key] = [self._convert_field(key, val) for val in value]
185 else:
186 result[key] = self._convert_field(key, value)
187 return result
188
6a58bf41
SH
189 def __iter__(self):
190 """Part of iterator protocol.
191 Returns self.
192 """
193 return self
194
1070f974
ZJS
195 def __next__(self):
196 """Part of iterator protocol.
197 Returns self.get_next() or raises StopIteration.
198 """
199 ans = self.get_next()
200 if ans:
201 return ans
202 else:
203 raise StopIteration()
204
205 if _sys.version_info < (3,):
206 next = __next__
6a58bf41 207
bf1ced55 208 def add_match(self, *args, **kwargs):
c71f26eb
SH
209 """Add one or more matches to the filter journal log entries.
210 All matches of different field are combined in a logical AND,
2c076467 211 and matches of the same field are automatically combined in a
c71f26eb 212 logical OR.
2c076467
ZJS
213 Matches can be passed as strings of form "FIELD=value", or
214 keyword arguments FIELD="value".
215 """
bf1ced55
SH
216 args = list(args)
217 args.extend(_make_line(key, val) for key, val in kwargs.items())
5bb2b8d5 218 for arg in args:
7f41820b 219 super(Reader, self).add_match(arg)
bf1ced55 220
71766afa 221 def get_next(self, skip=1):
6a58bf41
SH
222 """Return the next log entry as a mapping type, currently
223 a standard dictionary of fields.
2c076467
ZJS
224
225 Optional skip value will return the `skip`\-th log entry.
226
227 Entries will be processed with converters specified during
7f41820b 228 Reader creation.
2c076467 229 """
6a58bf41
SH
230 if super(Reader, self)._next(skip):
231 entry = super(Reader, self)._get_all()
232 if entry:
233 entry['__REALTIME_TIMESTAMP'] = self._get_realtime()
234 entry['__MONOTONIC_TIMESTAMP'] = self._get_monotonic()
235 entry['__CURSOR'] = self._get_cursor()
236 return self._convert_entry(entry)
237 return dict()
238
239 def get_previous(self, skip=1):
240 """Return the previous log entry as a mapping type,
241 currently a standard dictionary of fields.
242
243 Optional skip value will return the -`skip`\-th log entry.
244
245 Entries will be processed with converters specified during
246 Reader creation.
247
d2bbca68 248 Equivalent to get_next(-skip).
6a58bf41
SH
249 """
250 return self.get_next(-skip)
3aa8f086 251
c71f26eb 252 def query_unique(self, field):
7f41820b 253 """Return unique values appearing in the journal for given `field`.
2c076467
ZJS
254
255 Note this does not respect any journal matches.
256
257 Entries will be processed with converters specified during
7f41820b 258 Reader creation.
2c076467 259 """
c71f26eb 260 return set(self._convert_field(field, value)
7f41820b 261 for value in super(Reader, self).query_unique(field))
c71f26eb 262
806bc1cb
ZJS
263 def wait(self, timeout=None):
264 """Wait for a change in the journal. `timeout` is the maximum
265 time in seconds to wait, or None, to wait forever.
266
267 Returns one of NOP (no change), APPEND (new entries have been
268 added to the end of the journal), or INVALIDATE (journal files
269 have been added or removed).
270 """
271 us = -1 if timeout is None else int(timeout * 1000000)
7c962c86 272 return super(Reader, self).wait(us)
806bc1cb 273
c71f26eb 274 def seek_realtime(self, realtime):
2c076467
ZJS
275 """Seek to a matching journal entry nearest to `realtime` time.
276
277 Argument `realtime` must be either an integer unix timestamp
278 or datetime.datetime instance.
279 """
b8359823 280 if isinstance(realtime, _datetime.datetime):
806bc1cb
ZJS
281 realtime = float(realtime.strftime("%s.%f")) * 1000000
282 return super(Reader, self).seek_realtime(int(realtime))
c71f26eb
SH
283
284 def seek_monotonic(self, monotonic, bootid=None):
2c076467
ZJS
285 """Seek to a matching journal entry nearest to `monotonic` time.
286
287 Argument `monotonic` is a timestamp from boot in either
288 seconds or a datetime.timedelta instance. Argument `bootid`
289 is a string or UUID representing which boot the monotonic time
290 is reference to. Defaults to current bootid.
291 """
b8359823 292 if isinstance(monotonic, _datetime.timedelta):
c71f26eb 293 monotonic = monotonic.totalseconds()
806bc1cb 294 monotonic = int(monotonic * 1000000)
c71f26eb
SH
295 if isinstance(bootid, _uuid.UUID):
296 bootid = bootid.get_hex()
7f41820b 297 return super(Reader, self).seek_monotonic(monotonic, bootid)
7a1b9cd5 298
25523db4 299 def log_level(self, level):
2c076467
ZJS
300 """Set maximum log `level` by setting matches for PRIORITY.
301 """
25523db4
SH
302 if 0 <= level <= 7:
303 for i in range(level+1):
aaf08061 304 self.add_match(PRIORITY="%d" % i)
25523db4
SH
305 else:
306 raise ValueError("Log level must be 0 <= level <= 7")
307
89d9a233 308 def messageid_match(self, messageid):
2c076467
ZJS
309 """Add match for log entries with specified `messageid`.
310
311 `messageid` can be string of hexadicimal digits or a UUID
312 instance. Standard message IDs can be found in systemd.id128.
313
314 Equivalent to add_match(MESSAGE_ID=`messageid`).
315 """
89d9a233
SH
316 if isinstance(messageid, _uuid.UUID):
317 messageid = messageid.get_hex()
318 self.add_match(MESSAGE_ID=messageid)
319
301ae164
SH
320 def this_boot(self, bootid=None):
321 """Add match for _BOOT_ID equal to current boot ID or the specified boot ID.
322
c71f26eb
SH
323 If specified, bootid should be either a UUID or a 32 digit hex number.
324
325 Equivalent to add_match(_BOOT_ID='bootid').
301ae164
SH
326 """
327 if bootid is None:
328 bootid = _id128.get_boot().hex
329 else:
330 bootid = getattr(bootid, 'hex', bootid)
331 self.add_match(_BOOT_ID=bootid)
332
333 def this_machine(self, machineid=None):
334 """Add match for _MACHINE_ID equal to the ID of this machine.
335
c71f26eb
SH
336 If specified, machineid should be either a UUID or a 32 digit hex number.
337
338 Equivalent to add_match(_MACHINE_ID='machineid').
301ae164
SH
339 """
340 if machineid is None:
341 machineid = _id128.get_machine().hex
342 else:
343 machineid = getattr(machineid, 'hex', machineid)
344 self.add_match(_MACHINE_ID=machineid)
6a71de70 345
6a71de70 346
811de196
ZJS
347def get_catalog(mid):
348 if isinstance(mid, _uuid.UUID):
349 mid = mid.get_hex()
350 return _get_catalog(mid)
351
8d7e170a 352def _make_line(field, value):
0aee68ad
LP
353 if isinstance(value, bytes):
354 return field.encode('utf-8') + b'=' + value
8ff8ee83
RM
355 elif isinstance(value, int):
356 return field + '=' + str(value)
0aee68ad
LP
357 else:
358 return field + '=' + value
8d7e170a
LP
359
360def send(MESSAGE, MESSAGE_ID=None,
361 CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None,
362 **kwargs):
afcd68c1 363 r"""Send a message to the journal.
8d7e170a 364
0aee68ad
LP
365 >>> journal.send('Hello world')
366 >>> journal.send('Hello, again, world', FIELD2='Greetings!')
367 >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef')
8d7e170a 368
0aee68ad 369 Value of the MESSAGE argument will be used for the MESSAGE=
afcd68c1
ZJS
370 field. MESSAGE must be a string and will be sent as UTF-8 to
371 the journal.
8d7e170a 372
0aee68ad 373 MESSAGE_ID can be given to uniquely identify the type of
afcd68c1 374 message. It must be a string or a uuid.UUID object.
8d7e170a 375
0aee68ad
LP
376 CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to
377 identify the caller. Unless at least on of the three is given,
378 values are extracted from the stack frame of the caller of
379 send(). CODE_FILE and CODE_FUNC must be strings, CODE_LINE
380 must be an integer.
8d7e170a 381
afcd68c1
ZJS
382 Additional fields for the journal entry can only be specified
383 as keyword arguments. The payload can be either a string or
384 bytes. A string will be sent as UTF-8, and bytes will be sent
385 as-is to the journal.
386
0aee68ad
LP
387 Other useful fields include PRIORITY, SYSLOG_FACILITY,
388 SYSLOG_IDENTIFIER, SYSLOG_PID.
389 """
8d7e170a 390
0aee68ad 391 args = ['MESSAGE=' + MESSAGE]
8d7e170a 392
0aee68ad 393 if MESSAGE_ID is not None:
afcd68c1
ZJS
394 id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID)
395 args.append('MESSAGE_ID=' + id)
8d7e170a 396
0aee68ad
LP
397 if CODE_LINE == CODE_FILE == CODE_FUNC == None:
398 CODE_FILE, CODE_LINE, CODE_FUNC = \
399 _traceback.extract_stack(limit=2)[0][:3]
400 if CODE_FILE is not None:
401 args.append('CODE_FILE=' + CODE_FILE)
402 if CODE_LINE is not None:
403 args.append('CODE_LINE={:d}'.format(CODE_LINE))
404 if CODE_FUNC is not None:
405 args.append('CODE_FUNC=' + CODE_FUNC)
8d7e170a 406
0aee68ad
LP
407 args.extend(_make_line(key, val) for key, val in kwargs.items())
408 return sendv(*args)
8d7e170a
LP
409
410def stream(identifier, priority=LOG_DEBUG, level_prefix=False):
0aee68ad 411 r"""Return a file object wrapping a stream to journal.
8d7e170a 412
0aee68ad
LP
413 Log messages written to this file as simple newline sepearted
414 text strings are written to the journal.
8d7e170a 415
0aee68ad
LP
416 The file will be line buffered, so messages are actually sent
417 after a newline character is written.
8d7e170a 418
0aee68ad
LP
419 >>> stream = journal.stream('myapp')
420 >>> stream
421 <open file '<fdopen>', mode 'w' at 0x...>
422 >>> stream.write('message...\n')
8d7e170a 423
9015fa64 424 will produce the following message in the journal::
8d7e170a 425
9015fa64
ZJS
426 PRIORITY=7
427 SYSLOG_IDENTIFIER=myapp
428 MESSAGE=message...
8d7e170a 429
0aee68ad 430 Using the interface with print might be more convinient:
8d7e170a 431
0aee68ad
LP
432 >>> from __future__ import print_function
433 >>> print('message...', file=stream)
8d7e170a 434
9015fa64
ZJS
435 priority is the syslog priority, one of `LOG_EMERG`,
436 `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`,
437 `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`.
8d7e170a 438
0aee68ad
LP
439 level_prefix is a boolean. If true, kernel-style log priority
440 level prefixes (such as '<1>') are interpreted. See
441 sd-daemon(3) for more information.
442 """
8d7e170a 443
0aee68ad
LP
444 fd = stream_fd(identifier, priority, level_prefix)
445 return _os.fdopen(fd, 'w', 1)
73c0495f
MR
446
447class JournalHandler(_logging.Handler):
448 """Journal handler class for the Python logging framework.
449
450 Please see the Python logging module documentation for an
927e9632 451 overview: http://docs.python.org/library/logging.html.
73c0495f
MR
452
453 To create a custom logger whose messages go only to journal:
454
455 >>> log = logging.getLogger('custom_logger_name')
456 >>> log.propagate = False
457 >>> log.addHandler(journal.JournalHandler())
458 >>> log.warn("Some message: %s", detail)
459
9015fa64
ZJS
460 Note that by default, message levels `INFO` and `DEBUG` are
461 ignored by the logging framework. To enable those log levels:
73c0495f
MR
462
463 >>> log.setLevel(logging.DEBUG)
464
73c0495f
MR
465 To redirect all logging messages to journal regardless of where
466 they come from, attach it to the root logger:
467
468 >>> logging.root.addHandler(journal.JournalHandler())
469
9015fa64
ZJS
470 For more complex configurations when using `dictConfig` or
471 `fileConfig`, specify `systemd.journal.JournalHandler` as the
73c0495f 472 handler class. Only standard handler configuration options
9015fa64 473 are supported: `level`, `formatter`, `filters`.
73c0495f 474
b5edbff0
ZJS
475 To attach journal MESSAGE_ID, an extra field is supported:
476
477 >>> import uuid
478 >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF')
479 >>> log.warn("Message with ID", extra={'MESSAGE_ID': mid})
480
481 Fields to be attached to all messages sent through this
482 handler can be specified as keyword arguments. This probably
483 makes sense only for SYSLOG_IDENTIFIER and similar fields
484 which are constant for the whole program:
485
486 >>> journal.JournalHandler(SYSLOG_IDENTIFIER='my-cool-app')
487
73c0495f 488 The following journal fields will be sent:
9015fa64
ZJS
489 `MESSAGE`, `PRIORITY`, `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`,
490 `CODE_FUNC`, `LOGGER` (name as supplied to getLogger call),
b5edbff0
ZJS
491 `MESSAGE_ID` (optional, see above), `SYSLOG_IDENTIFIER` (defaults
492 to sys.argv[0]).
73c0495f
MR
493 """
494
b5edbff0
ZJS
495 def __init__(self, level=_logging.NOTSET, **kwargs):
496 super(JournalHandler, self).__init__(level)
497
498 for name in kwargs:
499 if not _valid_field_name(name):
500 raise ValueError('Invalid field name: ' + name)
501 if 'SYSLOG_IDENTIFIER' not in kwargs:
502 kwargs['SYSLOG_IDENTIFIER'] = _sys.argv[0]
503 self._extra = kwargs
504
73c0495f
MR
505 def emit(self, record):
506 """Write record as journal event.
507
508 MESSAGE is taken from the message provided by the
509 user, and PRIORITY, LOGGER, THREAD_NAME,
510 CODE_{FILE,LINE,FUNC} fields are appended
511 automatically. In addition, record.MESSAGE_ID will be
512 used if present.
513 """
514 try:
515 msg = self.format(record)
516 pri = self.mapPriority(record.levelno)
517 mid = getattr(record, 'MESSAGE_ID', None)
518 send(msg,
519 MESSAGE_ID=mid,
520 PRIORITY=format(pri),
521 LOGGER=record.name,
522 THREAD_NAME=record.threadName,
523 CODE_FILE=record.pathname,
524 CODE_LINE=record.lineno,
b5edbff0
ZJS
525 CODE_FUNC=record.funcName,
526 **self._extra)
73c0495f
MR
527 except Exception:
528 self.handleError(record)
529
530 @staticmethod
531 def mapPriority(levelno):
532 """Map logging levels to journald priorities.
533
534 Since Python log level numbers are "sparse", we have
535 to map numbers in between the standard levels too.
536 """
537 if levelno <= _logging.DEBUG:
538 return LOG_DEBUG
539 elif levelno <= _logging.INFO:
540 return LOG_INFO
541 elif levelno <= _logging.WARNING:
542 return LOG_WARNING
543 elif levelno <= _logging.ERROR:
544 return LOG_ERR
545 elif levelno <= _logging.CRITICAL:
546 return LOG_CRIT
547 else:
548 return LOG_ALERT