| # This Source Code Form is subject to the terms of the Mozilla Public |
| # License, v. 2.0. If a copy of the MPL was not distributed with this file, |
| # You can obtain one at http://mozilla.org/MPL/2.0/. |
| |
| import datetime |
| import json |
| import socket |
| import threading |
| import time |
| import unittest |
| |
| import mozfile |
| |
| import mozlog.unstructured as mozlog |
| |
| class ListHandler(mozlog.Handler): |
| """Mock handler appends messages to a list for later inspection.""" |
| |
| def __init__(self): |
| mozlog.Handler.__init__(self) |
| self.messages = [] |
| |
| def emit(self, record): |
| self.messages.append(self.format(record)) |
| |
| class TestLogging(unittest.TestCase): |
| """Tests behavior of basic mozlog api.""" |
| |
| def test_logger_defaults(self): |
| """Tests the default logging format and behavior.""" |
| |
| default_logger = mozlog.getLogger('default.logger') |
| self.assertEqual(default_logger.name, 'default.logger') |
| self.assertEqual(len(default_logger.handlers), 1) |
| self.assertTrue(isinstance(default_logger.handlers[0], |
| mozlog.StreamHandler)) |
| |
| f = mozfile.NamedTemporaryFile() |
| list_logger = mozlog.getLogger('file.logger', |
| handler=mozlog.FileHandler(f.name)) |
| self.assertEqual(len(list_logger.handlers), 1) |
| self.assertTrue(isinstance(list_logger.handlers[0], |
| mozlog.FileHandler)) |
| f.close() |
| |
| self.assertRaises(ValueError, mozlog.getLogger, |
| 'file.logger', handler=ListHandler()) |
| |
| def test_timestamps(self): |
| """Verifies that timestamps are included when asked for.""" |
| log_name = 'test' |
| handler = ListHandler() |
| handler.setFormatter(mozlog.MozFormatter()) |
| log = mozlog.getLogger(log_name, handler=handler) |
| log.info('no timestamp') |
| self.assertTrue(handler.messages[-1].startswith('%s ' % log_name)) |
| handler.setFormatter(mozlog.MozFormatter(include_timestamp=True)) |
| log.info('timestamp') |
| # Just verify that this raises no exceptions. |
| datetime.datetime.strptime(handler.messages[-1][:23], |
| '%Y-%m-%d %H:%M:%S,%f') |
| |
| class TestStructuredLogging(unittest.TestCase): |
| """Tests structured output in mozlog.""" |
| |
| def setUp(self): |
| self.handler = ListHandler() |
| self.handler.setFormatter(mozlog.JSONFormatter()) |
| self.logger = mozlog.MozLogger('test.Logger') |
| self.logger.addHandler(self.handler) |
| self.logger.setLevel(mozlog.DEBUG) |
| |
| def check_messages(self, expected, actual): |
| """Checks actual for equality with corresponding fields in actual. |
| The actual message should contain all fields in expected, and |
| should be identical, with the exception of the timestamp field. |
| The actual message should contain no fields other than the timestamp |
| field and those present in expected.""" |
| |
| self.assertTrue(isinstance(actual['_time'], (int, long))) |
| |
| for k, v in expected.items(): |
| self.assertEqual(v, actual[k]) |
| |
| for k in actual.keys(): |
| if k != '_time': |
| self.assertTrue(expected.get(k) is not None) |
| |
| def test_structured_output(self): |
| self.logger.log_structured('test_message', |
| {'_level': mozlog.INFO, |
| '_message': 'message one'}) |
| self.logger.log_structured('test_message', |
| {'_level': mozlog.INFO, |
| '_message': 'message two'}) |
| self.logger.log_structured('error_message', |
| {'_level': mozlog.ERROR, |
| 'diagnostic': 'unexpected error'}) |
| |
| message_one_expected = {'_namespace': 'test.Logger', |
| '_level': 'INFO', |
| '_message': 'message one', |
| 'action': 'test_message'} |
| message_two_expected = {'_namespace': 'test.Logger', |
| '_level': 'INFO', |
| '_message': 'message two', |
| 'action': 'test_message'} |
| message_three_expected = {'_namespace': 'test.Logger', |
| '_level': 'ERROR', |
| 'diagnostic': 'unexpected error', |
| 'action': 'error_message'} |
| |
| message_one_actual = json.loads(self.handler.messages[0]) |
| message_two_actual = json.loads(self.handler.messages[1]) |
| message_three_actual = json.loads(self.handler.messages[2]) |
| |
| self.check_messages(message_one_expected, message_one_actual) |
| self.check_messages(message_two_expected, message_two_actual) |
| self.check_messages(message_three_expected, message_three_actual) |
| |
| def test_unstructured_conversion(self): |
| """ Tests that logging to a logger with a structured formatter |
| via the traditional logging interface works as expected. """ |
| self.logger.info('%s %s %d', 'Message', 'number', 1) |
| self.logger.error('Message number 2') |
| self.logger.debug('Message with %s', 'some extras', |
| extra={'params': {'action': 'mozlog_test_output', |
| 'is_failure': False}}) |
| message_one_expected = {'_namespace': 'test.Logger', |
| '_level': 'INFO', |
| '_message': 'Message number 1'} |
| message_two_expected = {'_namespace': 'test.Logger', |
| '_level': 'ERROR', |
| '_message': 'Message number 2'} |
| message_three_expected = {'_namespace': 'test.Logger', |
| '_level': 'DEBUG', |
| '_message': 'Message with some extras', |
| 'action': 'mozlog_test_output', |
| 'is_failure': False} |
| |
| message_one_actual = json.loads(self.handler.messages[0]) |
| message_two_actual = json.loads(self.handler.messages[1]) |
| message_three_actual = json.loads(self.handler.messages[2]) |
| |
| self.check_messages(message_one_expected, message_one_actual) |
| self.check_messages(message_two_expected, message_two_actual) |
| self.check_messages(message_three_expected, message_three_actual) |
| |
| def message_callback(self): |
| if len(self.handler.messages) == 3: |
| message_one_expected = {'_namespace': 'test.Logger', |
| '_level': 'DEBUG', |
| '_message': 'socket message one', |
| 'action': 'test_message'} |
| message_two_expected = {'_namespace': 'test.Logger', |
| '_level': 'DEBUG', |
| '_message': 'socket message two', |
| 'action': 'test_message'} |
| message_three_expected = {'_namespace': 'test.Logger', |
| '_level': 'DEBUG', |
| '_message': 'socket message three', |
| 'action': 'test_message'} |
| |
| message_one_actual = json.loads(self.handler.messages[0]) |
| |
| message_two_actual = json.loads(self.handler.messages[1]) |
| |
| message_three_actual = json.loads(self.handler.messages[2]) |
| |
| self.check_messages(message_one_expected, message_one_actual) |
| self.check_messages(message_two_expected, message_two_actual) |
| self.check_messages(message_three_expected, message_three_actual) |
| |
| def test_log_listener(self): |
| connection = '127.0.0.1', 0 |
| self.log_server = mozlog.LogMessageServer(connection, |
| self.logger, |
| message_callback=self.message_callback, |
| timeout=0.5) |
| |
| message_string_one = json.dumps({'_message': 'socket message one', |
| 'action': 'test_message', |
| '_level': 'DEBUG'}) |
| message_string_two = json.dumps({'_message': 'socket message two', |
| 'action': 'test_message', |
| '_level': 'DEBUG'}) |
| |
| message_string_three = json.dumps({'_message': 'socket message three', |
| 'action': 'test_message', |
| '_level': 'DEBUG'}) |
| |
| message_string = message_string_one + '\n' + \ |
| message_string_two + '\n' + \ |
| message_string_three + '\n' |
| |
| server_thread = threading.Thread(target=self.log_server.handle_request) |
| server_thread.start() |
| |
| host, port = self.log_server.server_address |
| |
| sock = socket.socket() |
| sock.connect((host, port)) |
| |
| # Sleeps prevent listener from receiving entire message in a single call |
| # to recv in order to test reconstruction of partial messages. |
| sock.sendall(message_string[:8]) |
| time.sleep(.01) |
| sock.sendall(message_string[8:32]) |
| time.sleep(.01) |
| sock.sendall(message_string[32:64]) |
| time.sleep(.01) |
| sock.sendall(message_string[64:128]) |
| time.sleep(.01) |
| sock.sendall(message_string[128:]) |
| |
| server_thread.join() |
| |
| class Loggable(mozlog.LoggingMixin): |
| """Trivial class inheriting from LoggingMixin""" |
| pass |
| |
| class TestLoggingMixin(unittest.TestCase): |
| """Tests basic use of LoggingMixin""" |
| |
| def test_mixin(self): |
| loggable = Loggable() |
| self.assertTrue(not hasattr(loggable, "_logger")) |
| loggable.log(mozlog.INFO, "This will instantiate the logger") |
| self.assertTrue(hasattr(loggable, "_logger")) |
| self.assertEqual(loggable._logger.name, "test_logger.Loggable") |
| |
| self.assertRaises(ValueError, loggable.set_logger, |
| "not a logger") |
| |
| logger = mozlog.MozLogger('test.mixin') |
| handler = ListHandler() |
| logger.addHandler(handler) |
| loggable.set_logger(logger) |
| self.assertTrue(isinstance(loggable._logger.handlers[0], |
| ListHandler)) |
| self.assertEqual(loggable._logger.name, "test.mixin") |
| |
| loggable.log(mozlog.WARN, 'message for "log" method') |
| loggable.info('message for "info" method') |
| loggable.error('message for "error" method') |
| loggable.log_structured('test_message', |
| params={'_message': 'message for ' + \ |
| '"log_structured" method'}) |
| |
| expected_messages = ['message for "log" method', |
| 'message for "info" method', |
| 'message for "error" method', |
| 'message for "log_structured" method'] |
| |
| actual_messages = loggable._logger.handlers[0].messages |
| self.assertEqual(expected_messages, actual_messages) |
| |
| if __name__ == '__main__': |
| unittest.main() |