chiark / gitweb /
systemd-python: refuse path and flags together in __init__
[elogind.git] / src / python-systemd / journal.py
index d94934cfa80e817f855235f08a2c471e57b586be..1ed726c33a7fab9b1fae0eea42e19416636c0f81 100644 (file)
@@ -27,23 +27,44 @@ import functools as _functools
 import uuid as _uuid
 import traceback as _traceback
 import os as _os
 import uuid as _uuid
 import traceback as _traceback
 import os as _os
-from os import SEEK_SET, SEEK_CUR, SEEK_END
 import logging as _logging
 if _sys.version_info >= (3,):
     from collections import ChainMap as _ChainMap
 from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR,
                     LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG)
 from ._journal import sendv, stream_fd
 import logging as _logging
 if _sys.version_info >= (3,):
     from collections import ChainMap as _ChainMap
 from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR,
                     LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG)
 from ._journal import sendv, stream_fd
-from ._reader import (_Journal, NOP, APPEND, INVALIDATE,
+from ._reader import (_Reader, NOP, APPEND, INVALIDATE,
                       LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY)
 from . import id128 as _id128
 
                       LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY)
 from . import id128 as _id128
 
-_MONOTONIC_CONVERTER = lambda x: _datetime.timedelta(microseconds=x)
-_REALTIME_CONVERTER = lambda x: _datetime.datetime.fromtimestamp(x / 1E6)
+if _sys.version_info >= (3,):
+    from ._reader import Monotonic
+else:
+    Monotonic = tuple
+
+def _convert_monotonic(m):
+    return Monotonic((_datetime.timedelta(microseconds=m[0]),
+                      _uuid.UUID(bytes=m[1])))
+
+def _convert_source_monotonic(s):
+    return _datetime.timedelta(microseconds=int(s))
+
+def _convert_realtime(t):
+    return _datetime.datetime.fromtimestamp(t / 1E6)
+
+def _convert_timestamp(s):
+    return _datetime.datetime.fromtimestamp(int(s) / 1E6)
+
+if _sys.version_info >= (3,):
+    def _convert_uuid(s):
+        return _uuid.UUID(s.decode())
+else:
+    _convert_uuid = _uuid.UUID
+
 DEFAULT_CONVERTERS = {
 DEFAULT_CONVERTERS = {
-    'MESSAGE_ID': _uuid.UUID,
-    '_MACHINE_ID': _uuid.UUID,
-    '_BOOT_ID': _uuid.UUID,
+    'MESSAGE_ID': _convert_uuid,
+    '_MACHINE_ID': _convert_uuid,
+    '_BOOT_ID': _convert_uuid,
     'PRIORITY': int,
     'LEADER': int,
     'SESSION_ID': int,
     'PRIORITY': int,
     'LEADER': int,
     'SESSION_ID': int,
@@ -62,57 +83,61 @@ DEFAULT_CONVERTERS = {
     'CODE_LINE': int,
     'ERRNO': int,
     'EXIT_STATUS': int,
     'CODE_LINE': int,
     'ERRNO': int,
     'EXIT_STATUS': int,
-    '_SOURCE_REALTIME_TIMESTAMP': _REALTIME_CONVERTER,
-    '__REALTIME_TIMESTAMP': _REALTIME_CONVERTER,
-    '_SOURCE_MONOTONIC_TIMESTAMP': _MONOTONIC_CONVERTER,
-    '__MONOTONIC_TIMESTAMP': _MONOTONIC_CONVERTER,
+    '_SOURCE_REALTIME_TIMESTAMP': _convert_timestamp,
+    '__REALTIME_TIMESTAMP': _convert_realtime,
+    '_SOURCE_MONOTONIC_TIMESTAMP': _convert_source_monotonic,
+    '__MONOTONIC_TIMESTAMP': _convert_monotonic,
     'COREDUMP': bytes,
     'COREDUMP_PID': int,
     'COREDUMP_UID': int,
     'COREDUMP_GID': int,
     'COREDUMP_SESSION': int,
     'COREDUMP_SIGNAL': int,
     'COREDUMP': bytes,
     'COREDUMP_PID': int,
     'COREDUMP_UID': int,
     'COREDUMP_GID': int,
     'COREDUMP_SESSION': int,
     'COREDUMP_SIGNAL': int,
-    'COREDUMP_TIMESTAMP': _REALTIME_CONVERTER,
+    'COREDUMP_TIMESTAMP': _convert_timestamp,
 }
 
 }
 
-if _sys.version_info >= (3,):
-    _convert_unicode = _functools.partial(str, encoding='utf-8')
-else:
-    _convert_unicode = _functools.partial(unicode, encoding='utf-8')
-
-class Journal(_Journal):
-    """Journal allows the access and filtering of systemd journal
+class Reader(_Reader):
+    """Reader allows the access and filtering of systemd journal
     entries. Note that in order to access the system journal, a
     non-root user must be in the `adm` group.
 
     entries. Note that in order to access the system journal, a
     non-root user must be in the `adm` group.
 
-    Example usage to print out all error or higher level messages
-    for systemd-udevd for the boot:
+    Example usage to print out all informational or higher level
+    messages for systemd-udevd for this boot:
 
 
-    >>> myjournal = journal.Journal()
-    >>> myjournal.add_boot_match(journal.CURRENT_BOOT)
-    >>> myjournal.add_loglevel_matches(journal.LOG_ERR)
-    >>> myjournal.add_match(_SYSTEMD_UNIT="systemd-udevd.service")
-    >>> for entry in myjournal:
+    >>> j = journal.Reader()
+    >>> j.this_boot()
+    >>> j.log_level(journal.LOG_INFO)
+    >>> j.add_match(_SYSTEMD_UNIT="systemd-udevd.service")
+    >>> for entry in j:
     ...    print(entry['MESSAGE'])
 
     See systemd.journal-fields(7) for more info on typical fields
     found in the journal.
     """
     ...    print(entry['MESSAGE'])
 
     See systemd.journal-fields(7) for more info on typical fields
     found in the journal.
     """
-    def __init__(self, converters=None, flags=LOCAL_ONLY, path=None):
-        """Creates instance of Journal, which allows filtering and
+    def __init__(self, flags=0, path=None, converters=None):
+        """Create an instance of Reader, which allows filtering and
         return of journal entries.
         return of journal entries.
-        Argument `converters` is a dictionary which updates the
-        DEFAULT_CONVERTERS to convert journal field values.
+
         Argument `flags` sets open flags of the journal, which can be one
         of, or ORed combination of constants: LOCAL_ONLY (default) opens
         journal on local machine only; RUNTIME_ONLY opens only
         volatile journal files; and SYSTEM_ONLY opens only
         journal files of system services and the kernel.
         Argument `flags` sets open flags of the journal, which can be one
         of, or ORed combination of constants: LOCAL_ONLY (default) opens
         journal on local machine only; RUNTIME_ONLY opens only
         volatile journal files; and SYSTEM_ONLY opens only
         journal files of system services and the kernel.
+
         Argument `path` is the directory of journal files. Note that
         Argument `path` is the directory of journal files. Note that
-        currently flags are ignored when `path` is present as they are
-        currently not relevant.
+        `flags` and `path` are exclusive.
+
+        Argument `converters` is a dictionary which updates the
+        DEFAULT_CONVERTERS to convert journal field values. Field
+        names are used as keys into this dictionary. The values must
+        be single argument functions, which take a `bytes` object and
+        return a converted value. When there's no entry for a field
+        name, then the default UTF-8 decoding will be attempted. If
+        the conversion fails with a ValueError, unconverted bytes
+        object will be returned. (Note that ValueEror is a superclass
+        of UnicodeDecodeError).
         """
         """
-        super(Journal, self).__init__(flags, path)
+        super(Reader, self).__init__(flags, path)
         if _sys.version_info >= (3,3):
             self.converters = _ChainMap()
             if converters is not None:
         if _sys.version_info >= (3,3):
             self.converters = _ChainMap()
             if converters is not None:
@@ -124,18 +149,19 @@ class Journal(_Journal):
                 self.converters.update(converters)
 
     def _convert_field(self, key, value):
                 self.converters.update(converters)
 
     def _convert_field(self, key, value):
-        """Convert value based on callable from self.converters
-        based of field/key"""
+        """Convert value using self.converters[key]
+
+        If `key` is not present in self.converters, a standard unicode
+        decoding will be attempted.  If the conversion (either
+        key-specific or the default one) fails with a ValueError, the
+        original bytes object will be returned.
+        """
+        convert = self.converters.get(key, bytes.decode)
         try:
         try:
-            result = self.converters[key](value)
-        except:
-            # Default conversion in unicode
-            try:
-                result = _convert_unicode(value)
-            except UnicodeDecodeError:
-                # Leave in default bytes
-                result = value
-        return result
+            return convert(value)
+        except ValueError:
+            # Leave in default bytes
+            return value
 
     def _convert_entry(self, entry):
         """Convert entire journal entry utilising _covert_field"""
 
     def _convert_entry(self, entry):
         """Convert entire journal entry utilising _covert_field"""
@@ -158,7 +184,7 @@ class Journal(_Journal):
         args = list(args)
         args.extend(_make_line(key, val) for key, val in kwargs.items())
         for arg in args:
         args = list(args)
         args.extend(_make_line(key, val) for key, val in kwargs.items())
         for arg in args:
-            super(Journal, self).add_match(arg)
+            super(Reader, self).add_match(arg)
 
     def get_next(self, skip=1):
         """Return the next log entry as a dictionary of fields.
 
     def get_next(self, skip=1):
         """Return the next log entry as a dictionary of fields.
@@ -166,21 +192,21 @@ class Journal(_Journal):
         Optional skip value will return the `skip`\-th log entry.
 
         Entries will be processed with converters specified during
         Optional skip value will return the `skip`\-th log entry.
 
         Entries will be processed with converters specified during
-        Journal creation.
+        Reader creation.
         """
         return self._convert_entry(
         """
         return self._convert_entry(
-            super(Journal, self).get_next(skip))
+            super(Reader, self).get_next(skip))
 
     def query_unique(self, field):
 
     def query_unique(self, field):
-        """Return unique values appearing in the Journal for given `field`.
+        """Return unique values appearing in the journal for given `field`.
 
         Note this does not respect any journal matches.
 
         Entries will be processed with converters specified during
 
         Note this does not respect any journal matches.
 
         Entries will be processed with converters specified during
-        Journal creation.
+        Reader creation.
         """
         return set(self._convert_field(field, value)
         """
         return set(self._convert_field(field, value)
-            for value in super(Journal, self).query_unique(field))
+            for value in super(Reader, self).query_unique(field))
 
     def seek_realtime(self, realtime):
         """Seek to a matching journal entry nearest to `realtime` time.
 
     def seek_realtime(self, realtime):
         """Seek to a matching journal entry nearest to `realtime` time.
@@ -190,7 +216,7 @@ class Journal(_Journal):
         """
         if isinstance(realtime, _datetime.datetime):
             realtime = float(realtime.strftime("%s.%f"))
         """
         if isinstance(realtime, _datetime.datetime):
             realtime = float(realtime.strftime("%s.%f"))
-        return super(Journal, self).seek_realtime(realtime)
+        return super(Reader, self).seek_realtime(realtime)
 
     def seek_monotonic(self, monotonic, bootid=None):
         """Seek to a matching journal entry nearest to `monotonic` time.
 
     def seek_monotonic(self, monotonic, bootid=None):
         """Seek to a matching journal entry nearest to `monotonic` time.
@@ -204,14 +230,14 @@ class Journal(_Journal):
             monotonic = monotonic.totalseconds()
         if isinstance(bootid, _uuid.UUID):
             bootid = bootid.get_hex()
             monotonic = monotonic.totalseconds()
         if isinstance(bootid, _uuid.UUID):
             bootid = bootid.get_hex()
-        return super(Journal, self).seek_monotonic(monotonic, bootid)
+        return super(Reader, self).seek_monotonic(monotonic, bootid)
 
     def log_level(self, level):
         """Set maximum log `level` by setting matches for PRIORITY.
         """
         if 0 <= level <= 7:
             for i in range(level+1):
 
     def log_level(self, level):
         """Set maximum log `level` by setting matches for PRIORITY.
         """
         if 0 <= level <= 7:
             for i in range(level+1):
-                self.add_match(PRIORITY="%s" % i)
+                self.add_match(PRIORITY="%d" % i)
         else:
             raise ValueError("Log level must be 0 <= level <= 7")
 
         else:
             raise ValueError("Log level must be 0 <= level <= 7")