diff options
author | Jenkins <jenkins@review.openstack.org> | 2017-04-06 20:03:59 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2017-04-06 20:03:59 +0000 |
commit | 4811a433f3b88969c71dcd168e79ee3d61599e6d (patch) | |
tree | 485c7662b0ddac632167ef663e77e98016bfd1fe | |
parent | 3ee14a17a2ccf85e13ba11890da267e54621f64d (diff) | |
parent | 4a8d8d743024d42f07392ce1d3b43ca7b36751fb (diff) | |
download | oslo-log-4811a433f3b88969c71dcd168e79ee3d61599e6d.tar.gz |
Merge "Systemd native journal support"
-rw-r--r-- | oslo_log/_options.py | 8 | ||||
-rw-r--r-- | oslo_log/handlers.py | 54 | ||||
-rw-r--r-- | oslo_log/log.py | 7 | ||||
-rw-r--r-- | oslo_log/tests/unit/test_log.py | 47 |
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() |