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