diff --git a/oslo/messaging/notify/log_handler.py b/oslo/messaging/notify/log_handler.py new file mode 100644 index 000000000..679eb69d5 --- /dev/null +++ b/oslo/messaging/notify/log_handler.py @@ -0,0 +1,36 @@ +# 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 logging + +from oslo.config import cfg +from oslo import messaging + + +class PublishErrorsHandler(logging.Handler): + def __init__(self, *args, **kwargs): + logging.Handler.__init__(self, *args, **kwargs) + self._transport = messaging.get_transport(cfg.CONF) + self._notifier = messaging.Notifier(self._transport, + publisher_id='error.publisher') + + def emit(self, record): + # NOTE(bnemec): Notifier registers this opt with the transport. + if ('log' in self._transport.conf.notification_driver): + # NOTE(lbragstad): If we detect that log is one of the + # notification drivers, then return. This protects from infinite + # recursion where something bad happens, it gets logged, the log + # handler sends a notification, and the log_notifier sees the + # notification and logs it. + return + self._notifier.error(None, 'error_notification', + dict(error=record.msg)) diff --git a/tests/test_log_handler.py b/tests/test_log_handler.py new file mode 100644 index 000000000..7d81cfca2 --- /dev/null +++ b/tests/test_log_handler.py @@ -0,0 +1,66 @@ +# 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 logging +import mock + +from oslo import messaging +from oslo.messaging.notify import log_handler +from tests import test_notifier +from tests import utils as test_utils + + +class PublishErrorsHandlerTestCase(test_utils.BaseTestCase): + """Tests for log.PublishErrorsHandler""" + def setUp(self): + super(PublishErrorsHandlerTestCase, self).setUp() + self.conf.register_opts(messaging.notify.notifier._notifier_opts) + self.publisherrorshandler = (log_handler. + PublishErrorsHandler(logging.ERROR)) + + def test_emit_cfg_log_notifier_in_notifier_drivers(self): + drivers = ['messaging', 'log'] + self.config(notification_driver=drivers) + self.stub_flg = True + + transport = test_notifier._FakeTransport(self.conf) + notifier = messaging.Notifier(transport) + + def fake_notifier(*args, **kwargs): + self.stub_flg = False + + self.stubs.Set(notifier, 'error', fake_notifier) + + logrecord = logging.LogRecord(name='name', level='WARN', + pathname='/tmp', lineno=1, msg='Message', + args=None, exc_info=None) + self.publisherrorshandler.emit(logrecord) + self.assertTrue(self.stub_flg) + + @mock.patch.object(messaging.notify.notifier.Notifier, '_notify') + def test_emit_notification(self, mock_notify): + logrecord = logging.LogRecord(name='name', level='ERROR', + pathname='/tmp', lineno=1, msg='Message', + args=None, exc_info=None) + mock_init = mock.Mock(return_value=None) + with mock.patch.object(messaging.notify.notifier.Notifier, + '__init__', mock_init): + # Recreate the handler so the __init__ mock takes effect. + self.publisherrorshandler = (log_handler. + PublishErrorsHandler(logging.ERROR)) + self.publisherrorshandler.emit(logrecord) + mock_init.assert_called_with(mock.ANY, + publisher_id='error.publisher') + mock_notify.assert_called_with(None, + 'error_notification', + {'error': 'Message'}, + 'ERROR')