chiark / gitweb /
systemd-python: Moved _reader datetime usage to python
[elogind.git] / src / python-systemd / journal.py
index a1543b884e5b57b4f45ab83ad6e2dd1b16710b46..46affcee00c6226f74a8b1cab38e5244bd8475b5 100644 (file)
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with systemd; If not, see <http://www.gnu.org/licenses/>.
 
+import datetime
+import functools
+import sys
+import uuid
 import traceback as _traceback
 import os as _os
 import logging as _logging
+if sys.version_info >= (3,):
+    from collections import 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 (_Journal, NOP, APPEND, INVALIDATE,
                       LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY)
+from . import id128 as _id128
+
+_MONOTONIC_CONVERTER = lambda x: datetime.timedelta(microseconds=float(x))
+_REALTIME_CONVERTER = lambda x: datetime.datetime.fromtimestamp(float(x)/1E6)
+DEFAULT_CONVERTERS = {
+    'MESSAGE_ID': uuid.UUID,
+    'PRIORITY': int,
+    'LEADER': int,
+    'SESSION_ID': int,
+    'USERSPACE_USEC': int,
+    'INITRD_USEC': int,
+    'KERNEL_USEC': int,
+    '_UID': int,
+    '_GID': int,
+    '_PID': int,
+    'SYSLOG_FACILITY': int,
+    'SYSLOG_PID': int,
+    '_AUDIT_SESSION': int,
+    '_AUDIT_LOGINUID': int,
+    '_SYSTEMD_SESSION': int,
+    '_SYSTEMD_OWNER_UID': 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,
+    'COREDUMP': bytes,
+    'COREDUMP_PID': int,
+    'COREDUMP_UID': int,
+    'COREDUMP_GID': int,
+    'COREDUMP_SESSION': int,
+    'COREDUMP_SIGNAL': int,
+    'COREDUMP_TIMESTAMP': _REALTIME_CONVERTER,
+}
+
+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):
+    def __init__(self, converters=None, *args, **kwargs):
+        super(Journal, self).__init__(*args, **kwargs)
+        if sys.version_info >= (3,3):
+            self.converters = ChainMap()
+            if converters is not None:
+                self.converters.maps.append(converters)
+            self.converters.maps.append(DEFAULT_CONVERTERS)
+        else:
+            # suitable fallback, e.g.
+            self.converters = DEFAULT_CONVERTERS.copy()
+            if converters is not None:
+                self.converters.update(converters)
+
+    def _convert_field(self, key, value):
+        try:
+            result = self.converters[key](value)
+        except:
+            # Default conversion in unicode
+            try:
+                result = _convert_unicode(value)
+            except:
+                # Leave in default bytes
+                result = value
+        return result
+
+    def _convert_entry(self, entry):
+        result = {}
+        for key, value in entry.items():
+            if isinstance(value, list):
+                result[key] = [self._convert_field(key, val) for val in value]
+            else:
+                result[key] = self._convert_field(key, value)
+        return result
+
+    def add_match(self, *args, **kwargs):
+        args = list(args)
+        args.extend(_make_line(key, val) for key, val in kwargs.items())
+        super(Journal, self).add_match(*args)
+
+    def get_next(self, skip=1):
+        return self._convert_entry(
+            super(Journal, self).get_next(skip))
+
+    def query_unique(self, key):
+        return set(self._convert_field(key, value)
+            for value in super(Journal, self).query_unique(key))
+
+    def seek_realtime(self, timestamp):
+        if isinstance(timestamp, datetime.datetime):
+            timestamp = int(timestamp.strftime("%s%f"))
+        return super(Journal, self).seek_realtime(timestamp)
+
+    def seek_monotonic(self, timestamp, bootid=None):
+        if isinstance(timestamp, datetime.timedelta):
+            timestamp = timestamp.totalseconds()
+        return super(Journal, self).seek_monotonic(timestamp, bootid)
+
+    def log_level(self, level):
+        """Sets maximum log level by setting matches for PRIORITY."""
+        if 0 <= level <= 7:
+            for i in range(level+1):
+                self.add_match(PRIORITY="%s" % i)
+        else:
+            raise ValueError("Log level must be 0 <= level <= 7")
+
+    def this_boot(self):
+        """Add match for _BOOT_ID equal to current boot ID."""
+        self.add_match(_BOOT_ID=_id128.get_boot().hex)
+
+    def this_machine(self):
+        """Add match for _MACHINE_ID equal to the ID of this machine."""
+        self.add_match(_MACHINE_ID=_id128.get_machine().hex)
 
 def _make_line(field, value):
         if isinstance(value, bytes):