diff --git a/teeth_agent/logging.py b/teeth_agent/logging.py index bdfd9afc4..1429f9932 100644 --- a/teeth_agent/logging.py +++ b/teeth_agent/logging.py @@ -14,10 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. """ -import traceback +import string import structlog +import traceback + EXCEPTION_LOG_METHODS = ['error'] @@ -28,9 +30,35 @@ def _capture_stack_trace(logger, method, event): return event +def _format_event(logger, method, event): + """Formats the log message using keyword args. + log('hello {keyword}', keyword='world') should log: "hello world" + Removes the keywords used for formatting from the logged message. + Throws a KeyError if the log message requires formatting but doesn't + have enough keys to format. + """ + if 'event' not in event: + # nothing to format, e.g. _log_request in teeth_rest/component + return event + # Get a list of fields that need to be filled. + formatter = string.Formatter() + try: + formatted = formatter.format(event['event'], **event) + except KeyError: + keys = formatter.parse(event['event']) + # index 1 is the key name + keys = [item[1] for item in keys] + missing_keys = list(set(keys) - set(event)) + raise KeyError("Log formatter missing keys: {}, cannot format." + .format(missing_keys)) + event['event'] = formatted + return event + + def configure(pretty=False): processors = [ _capture_stack_trace, + _format_event, ] if pretty: diff --git a/teeth_agent/tests/logging.py b/teeth_agent/tests/logging.py new file mode 100644 index 000000000..fc8fde34e --- /dev/null +++ b/teeth_agent/tests/logging.py @@ -0,0 +1,44 @@ +""" +Copyright 2013 Rackspace, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import structlog +import unittest + +import teeth_agent.logging + + +def _return_event_processor(logger, method, event): + return event['event'] + + +class EventLogger(unittest.TestCase): + def test_format_event_basic(self): + processors = [teeth_agent.logging._format_event, + _return_event_processor] + structlog.configure(processors=processors) + log = structlog.wrap_logger(structlog.ReturnLogger()) + logged_msg = log.msg("hello {word}", word='world') + self.assertEqual(logged_msg, "hello world") + + def test_no_format_keys(self): + """Check that we get an exception if you don't provide enough keys to + format a log message requiring format + """ + processors = [teeth_agent.logging._format_event, + _return_event_processor] + structlog.configure(processors=processors) + log = structlog.wrap_logger(structlog.ReturnLogger()) + self.assertRaises(KeyError, log.msg, "hello {word}")