summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2017-04-06 20:03:59 +0000
committerGerrit Code Review <review@openstack.org>2017-04-06 20:03:59 +0000
commit4811a433f3b88969c71dcd168e79ee3d61599e6d (patch)
tree485c7662b0ddac632167ef663e77e98016bfd1fe
parent3ee14a17a2ccf85e13ba11890da267e54621f64d (diff)
parent4a8d8d743024d42f07392ce1d3b43ca7b36751fb (diff)
downloadoslo-log-4811a433f3b88969c71dcd168e79ee3d61599e6d.tar.gz
Merge "Systemd native journal support"
-rw-r--r--oslo_log/_options.py8
-rw-r--r--oslo_log/handlers.py54
-rw-r--r--oslo_log/log.py7
-rw-r--r--oslo_log/tests/unit/test_log.py47
4 files changed, 115 insertions, 1 deletions
diff --git a/oslo_log/_options.py b/oslo_log/_options.py
index e09238a..33319d7 100644
--- a/oslo_log/_options.py
+++ b/oslo_log/_options.py
@@ -84,6 +84,14 @@ logging_cli_opts = [
'Existing syslog format is DEPRECATED '
'and will be changed later to honor RFC5424. '
+ _IGNORE_MESSAGE),
+ cfg.BoolOpt('use-journal',
+ default=False,
+ help='Enable journald for logging. '
+ 'If running in a systemd environment you may wish '
+ 'to enable journal support. Doing so will use the '
+ 'journal native protocol which includes structured '
+ 'metadata in addition to log messages.'
+ + _IGNORE_MESSAGE),
cfg.StrOpt('syslog-log-facility',
default='LOG_USER',
help='Syslog facility to receive log lines. '
diff --git a/oslo_log/handlers.py b/oslo_log/handlers.py
index b81a668..926e9c2 100644
--- a/oslo_log/handlers.py
+++ b/oslo_log/handlers.py
@@ -17,6 +17,11 @@ import logging.config
import logging.handlers
import os
import six
+
+try:
+ from systemd import journal
+except ImportError:
+ journal = None
try:
import syslog
except ImportError:
@@ -84,6 +89,55 @@ if syslog is not None:
syslog.syslog(priority, message)
+class OSJournalHandler(logging.Handler):
+ severity_map = {
+ "CRITICAL": syslog.LOG_CRIT,
+ "DEBUG": syslog.LOG_DEBUG,
+ "ERROR": syslog.LOG_ERR,
+ "INFO": syslog.LOG_INFO,
+ "WARNING": syslog.LOG_WARNING,
+ "WARN": syslog.LOG_WARNING,
+ }
+
+ custom_fields = (
+ 'project_name',
+ 'project_id',
+ 'user_name',
+ 'user_id',
+ 'request_id',
+ )
+
+ def __init__(self):
+ # Do not use super() unless type(logging.Handler) is 'type'
+ # (i.e. >= Python 2.7).
+ if not journal:
+ raise Exception("Systemd bindings do not exist")
+ logging.Handler.__init__(self)
+ self.binary_name = _get_binary_name()
+
+ def emit(self, record):
+ priority = self.severity_map.get(record.levelname,
+ syslog.LOG_DEBUG)
+ message = self.format(record)
+
+ extras = {
+ 'CODE_FILE': record.pathname,
+ 'CODE_LINE': record.lineno,
+ 'CODE_FUNC': record.funcName,
+ 'LOGGER_NAME': record.name,
+ 'LOGGER_LEVEL': record.levelname,
+ 'SYSLOG_IDENTIFIER': self.binary_name,
+ 'PRIORITY': priority
+ }
+
+ for field in self.custom_fields:
+ value = record.__dict__.get(field)
+ if value:
+ extras[field.upper()] = value
+
+ journal.send(message, **extras)
+
+
class ColorHandler(logging.StreamHandler):
LEVEL_COLORS = {
_TRACE: '\033[00;35m', # MAGENTA
diff --git a/oslo_log/log.py b/oslo_log/log.py
index 858d674..afb73a2 100644
--- a/oslo_log/log.py
+++ b/oslo_log/log.py
@@ -360,7 +360,12 @@ def _setup_logging_from_conf(conf, project, version):
streamlog = handlers.ColorHandler()
log_root.addHandler(streamlog)
- elif not logpath:
+ if conf.use_journal:
+ journal = handlers.OSJournalHandler()
+ log_root.addHandler(journal)
+
+ # if None of the above are True, then fall back to standard out
+ if not logpath and not conf.use_stderr and not conf.use_journal:
# pass sys.stdout as a positional argument
# python2.6 calls the argument strm, in 2.7 it's stream
streamlog = handlers.ColorHandler(sys.stdout)
diff --git a/oslo_log/tests/unit/test_log.py b/oslo_log/tests/unit/test_log.py
index 9c79e1c..c68838e 100644
--- a/oslo_log/tests/unit/test_log.py
+++ b/oslo_log/tests/unit/test_log.py
@@ -74,6 +74,20 @@ def _fake_context():
return ctxt
+def _fake_new_context():
+ # New style contexts have a user_name / project_name, this is done
+ # distinctly from the above context to not have to rewrite all the
+ # other tests.
+ ctxt = context.RequestContext(1, 1, overwrite=True)
+ ctxt.user_name = 'myuser'
+ ctxt.project_name = 'mytenant'
+ ctxt.domain = 'mydomain'
+ ctxt.project_domain = 'myprojectdomain'
+ ctxt.user_domain = 'myuserdomain'
+
+ return ctxt
+
+
class CommonLoggerTestsMixIn(object):
"""These tests are shared between LoggerTestCase and
LazyLoggerTestCase.
@@ -290,6 +304,39 @@ class OSSysLogHandlerTestCase(BaseTestCase):
syslog.syslog.assert_called_once_with(syslog.LOG_INFO, msg_unicode)
+class OSJournalHandlerTestCase(BaseTestCase):
+ """Test systemd journal logging.
+
+ This is a lightweight test for testing systemd journal logging. It
+ mocks out the journal interface itself, which allows us to not
+ have to have systemd-python installed (which is not possible to
+ install on non Linux environments).
+
+ Real world testing is also encouraged.
+
+ """
+ def setUp(self):
+ super(OSJournalHandlerTestCase, self).setUp()
+ self.config(use_journal=True)
+ self.journal = mock.patch("oslo_log.handlers.journal").start()
+ self.addCleanup(self.journal.stop)
+ log.setup(self.CONF, 'testing')
+
+ def test_emit(self):
+ l = log.getLogger('nova-test.foo')
+ local_context = _fake_new_context()
+ l.info("Foo", context=local_context)
+ self.assertEqual(
+ self.journal.send.call_args,
+ mock.call(mock.ANY, CODE_FILE=mock.ANY, CODE_FUNC='test_emit',
+ CODE_LINE=mock.ANY, LOGGER_LEVEL='INFO',
+ LOGGER_NAME='nova-test.foo', PRIORITY=6,
+ SYSLOG_IDENTIFIER=mock.ANY,
+ REQUEST_ID=mock.ANY,
+ PROJECT_NAME='mytenant',
+ USER_NAME='myuser'))
+
+
class LogLevelTestCase(BaseTestCase):
def setUp(self):
super(LogLevelTestCase, self).setUp()