From 9ba82c6623e8cf82bf02e79e2189479ed25de09a Mon Sep 17 00:00:00 2001
From: Marti Raudsepp <marti@juffo.org>
Date: Tue, 9 Oct 2012 18:12:02 +0300
Subject: [PATCH] python: integration with Python logging framework

Supports Python versions 2.5 through 3.3 (tested on 2.7 and 3.2).
See JournalHandler docstring for usage details.
---
 src/python-systemd/journal.py | 77 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py
index 760d2db..84a9dd7 100644
--- a/src/python-systemd/journal.py
+++ b/src/python-systemd/journal.py
@@ -19,6 +19,7 @@
 
 import traceback as _traceback
 import os as _os
+import logging as _logging
 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
@@ -114,3 +115,79 @@ def stream(identifier, priority=LOG_DEBUG, level_prefix=False):
 
         fd = stream_fd(identifier, priority, level_prefix)
         return _os.fdopen(fd, 'w', 1)
+
+class JournalHandler(_logging.Handler):
+        r"""Journal handler class for the Python logging framework.
+
+        Please see the Python logging module documentation for an
+        overview: http://docs.python.org/library/logging.html
+
+        To create a custom logger whose messages go only to journal:
+
+        >>> log = logging.getLogger('custom_logger_name')
+        >>> log.propagate = False
+        >>> log.addHandler(journal.JournalHandler())
+        >>> log.warn("Some message: %s", detail)
+
+        Note that by default, message levels INFO and DEBUG are ignored
+        by the logging framework. To enable those log levels:
+
+        >>> log.setLevel(logging.DEBUG)
+
+        To attach journal MESSAGE_ID, an extra field is supported:
+
+        >>> log.warn("Message with ID",
+        >>>     extra={'MESSAGE_ID': '22bb01335f724c959ac4799627d1cb61'})
+
+        To redirect all logging messages to journal regardless of where
+        they come from, attach it to the root logger:
+
+        >>> logging.root.addHandler(journal.JournalHandler())
+
+        For more complex configurations when using dictConfig/fileConfig,
+        specify 'systemd.journal.JournalHandler' as the handler class.
+        Only standard handler configuration options are supported:
+        level, formatter, filters.
+
+        The following journal fields are supported: MESSAGE, PRIORITY,
+        LOGGER (name as supplied to getLogger call), THREAD_NAME,
+        CODE_FILE, CODE_LINE, CODE_FUNC, MESSAGE_ID (optional, see above)
+        """
+
+        def emit(self, record):
+                try:
+                        msg = self.format(record)
+
+                        args = ['MESSAGE=' + msg,
+                                'PRIORITY=%d' % self.mapPriority(record.levelno),
+                                'LOGGER=' + record.name,
+                                'THREAD_NAME=' + record.threadName,
+                                'CODE_FILE=' + record.pathname,
+                                'CODE_LINE=%d' % record.lineno,
+                                'CODE_FUNC=' + record.funcName]
+
+                        if hasattr(record, 'MESSAGE_ID'):
+                                args.append('MESSAGE_ID=' + record.MESSAGE_ID)
+
+                        sendv(*args)
+                except (KeyboardInterrupt, SystemExit):
+                        raise
+                except:
+                        self.handleError(record)
+
+        def mapPriority(self, levelno):
+                r"""Since Python log level numbers are "sparse", we have
+                to map numbers in between the standard levels too.
+                """
+                if levelno <= _logging.DEBUG:
+                        return LOG_DEBUG
+                elif levelno <= _logging.INFO:
+                        return LOG_INFO
+                elif levelno <= _logging.WARNING:
+                        return LOG_WARNING
+                elif levelno <= _logging.ERROR:
+                        return LOG_ERR
+                elif levelno <= _logging.CRITICAL:
+                        return LOG_CRIT
+                else: # ???
+                        return LOG_ALERT
-- 
1.7.12.2

