testing/mozbase/mozlog/tests/test_logger.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/mozbase/mozlog/tests/test_logger.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,259 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 +# You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +import datetime
     1.9 +import json
    1.10 +import socket
    1.11 +import threading
    1.12 +import time
    1.13 +import unittest
    1.14 +
    1.15 +import mozfile
    1.16 +
    1.17 +import mozlog
    1.18 +
    1.19 +class ListHandler(mozlog.Handler):
    1.20 +    """Mock handler appends messages to a list for later inspection."""
    1.21 +
    1.22 +    def __init__(self):
    1.23 +        mozlog.Handler.__init__(self)
    1.24 +        self.messages = []
    1.25 +
    1.26 +    def emit(self, record):
    1.27 +        self.messages.append(self.format(record))
    1.28 +
    1.29 +class TestLogging(unittest.TestCase):
    1.30 +    """Tests behavior of basic mozlog api."""
    1.31 +
    1.32 +    def test_logger_defaults(self):
    1.33 +        """Tests the default logging format and behavior."""
    1.34 +
    1.35 +        default_logger = mozlog.getLogger('default.logger')
    1.36 +        self.assertEqual(default_logger.name, 'default.logger')
    1.37 +        self.assertEqual(len(default_logger.handlers), 1)
    1.38 +        self.assertTrue(isinstance(default_logger.handlers[0],
    1.39 +                                   mozlog.StreamHandler))
    1.40 +
    1.41 +        f = mozfile.NamedTemporaryFile()
    1.42 +        list_logger = mozlog.getLogger('file.logger',
    1.43 +                                       handler=mozlog.FileHandler(f.name))
    1.44 +        self.assertEqual(len(list_logger.handlers), 1)
    1.45 +        self.assertTrue(isinstance(list_logger.handlers[0],
    1.46 +                                   mozlog.FileHandler))
    1.47 +        f.close()
    1.48 +
    1.49 +        self.assertRaises(ValueError, mozlog.getLogger,
    1.50 +                          'file.logger', handler=ListHandler())
    1.51 +
    1.52 +    def test_timestamps(self):
    1.53 +        """Verifies that timestamps are included when asked for."""
    1.54 +        log_name = 'test'
    1.55 +        handler = ListHandler()
    1.56 +        handler.setFormatter(mozlog.MozFormatter())
    1.57 +        log = mozlog.getLogger(log_name, handler=handler)
    1.58 +        log.info('no timestamp')
    1.59 +        self.assertTrue(handler.messages[-1].startswith('%s ' % log_name))
    1.60 +        handler.setFormatter(mozlog.MozFormatter(include_timestamp=True))
    1.61 +        log.info('timestamp')
    1.62 +        # Just verify that this raises no exceptions.
    1.63 +        datetime.datetime.strptime(handler.messages[-1][:23],
    1.64 +                                   '%Y-%m-%d %H:%M:%S,%f')
    1.65 +
    1.66 +class TestStructuredLogging(unittest.TestCase):
    1.67 +    """Tests structured output in mozlog."""
    1.68 +
    1.69 +    def setUp(self):
    1.70 +        self.handler = ListHandler()
    1.71 +        self.handler.setFormatter(mozlog.JSONFormatter())
    1.72 +        self.logger = mozlog.MozLogger('test.Logger')
    1.73 +        self.logger.addHandler(self.handler)
    1.74 +        self.logger.setLevel(mozlog.DEBUG)
    1.75 +
    1.76 +    def check_messages(self, expected, actual):
    1.77 +        """Checks actual for equality with corresponding fields in actual.
    1.78 +        The actual message should contain all fields in expected, and
    1.79 +        should be identical, with the exception of the timestamp field.
    1.80 +        The actual message should contain no fields other than the timestamp
    1.81 +        field and those present in expected."""
    1.82 +
    1.83 +        self.assertTrue(isinstance(actual['_time'], (int, long)))
    1.84 +
    1.85 +        for k, v in expected.items():
    1.86 +            self.assertEqual(v, actual[k])
    1.87 +
    1.88 +        for k in actual.keys():
    1.89 +            if k != '_time':
    1.90 +                self.assertTrue(expected.get(k) is not None)
    1.91 +
    1.92 +    def test_structured_output(self):
    1.93 +        self.logger.log_structured('test_message',
    1.94 +                                   {'_level': mozlog.INFO,
    1.95 +                                    '_message': 'message one'})
    1.96 +        self.logger.log_structured('test_message',
    1.97 +                                   {'_level': mozlog.INFO,
    1.98 +                                    '_message': 'message two'})
    1.99 +        self.logger.log_structured('error_message',
   1.100 +                                   {'_level': mozlog.ERROR,
   1.101 +                                    'diagnostic': 'unexpected error'})
   1.102 +
   1.103 +        message_one_expected = {'_namespace': 'test.Logger',
   1.104 +                                '_level': 'INFO',
   1.105 +                                '_message': 'message one',
   1.106 +                                'action': 'test_message'}
   1.107 +        message_two_expected = {'_namespace': 'test.Logger',
   1.108 +                                '_level': 'INFO',
   1.109 +                                '_message': 'message two',
   1.110 +                                'action': 'test_message'}
   1.111 +        message_three_expected = {'_namespace': 'test.Logger',
   1.112 +                                  '_level': 'ERROR',
   1.113 +                                  'diagnostic': 'unexpected error',
   1.114 +                                  'action': 'error_message'}
   1.115 +
   1.116 +        message_one_actual = json.loads(self.handler.messages[0])
   1.117 +        message_two_actual = json.loads(self.handler.messages[1])
   1.118 +        message_three_actual = json.loads(self.handler.messages[2])
   1.119 +
   1.120 +        self.check_messages(message_one_expected, message_one_actual)
   1.121 +        self.check_messages(message_two_expected, message_two_actual)
   1.122 +        self.check_messages(message_three_expected, message_three_actual)
   1.123 +
   1.124 +    def test_unstructured_conversion(self):
   1.125 +        """ Tests that logging to a logger with a structured formatter
   1.126 +        via the traditional logging interface works as expected. """
   1.127 +        self.logger.info('%s %s %d', 'Message', 'number', 1)
   1.128 +        self.logger.error('Message number 2')
   1.129 +        self.logger.debug('Message with %s', 'some extras',
   1.130 +                          extra={'params': {'action': 'mozlog_test_output',
   1.131 +                                            'is_failure': False}})
   1.132 +        message_one_expected = {'_namespace': 'test.Logger',
   1.133 +                                '_level': 'INFO',
   1.134 +                                '_message': 'Message number 1'}
   1.135 +        message_two_expected = {'_namespace': 'test.Logger',
   1.136 +                                '_level': 'ERROR',
   1.137 +                                '_message': 'Message number 2'}
   1.138 +        message_three_expected = {'_namespace': 'test.Logger',
   1.139 +                                  '_level': 'DEBUG',
   1.140 +                                  '_message': 'Message with some extras',
   1.141 +                                  'action': 'mozlog_test_output',
   1.142 +                                  'is_failure': False}
   1.143 +
   1.144 +        message_one_actual = json.loads(self.handler.messages[0])
   1.145 +        message_two_actual = json.loads(self.handler.messages[1])
   1.146 +        message_three_actual = json.loads(self.handler.messages[2])
   1.147 +
   1.148 +        self.check_messages(message_one_expected, message_one_actual)
   1.149 +        self.check_messages(message_two_expected, message_two_actual)
   1.150 +        self.check_messages(message_three_expected, message_three_actual)
   1.151 +
   1.152 +    def message_callback(self):
   1.153 +        if len(self.handler.messages) == 3:
   1.154 +            message_one_expected = {'_namespace': 'test.Logger',
   1.155 +                                    '_level': 'DEBUG',
   1.156 +                                    '_message': 'socket message one',
   1.157 +                                    'action': 'test_message'}
   1.158 +            message_two_expected = {'_namespace': 'test.Logger',
   1.159 +                                    '_level': 'DEBUG',
   1.160 +                                    '_message': 'socket message two',
   1.161 +                                    'action': 'test_message'}
   1.162 +            message_three_expected = {'_namespace': 'test.Logger',
   1.163 +                                      '_level': 'DEBUG',
   1.164 +                                      '_message': 'socket message three',
   1.165 +                                      'action': 'test_message'}
   1.166 +
   1.167 +            message_one_actual = json.loads(self.handler.messages[0])
   1.168 +
   1.169 +            message_two_actual = json.loads(self.handler.messages[1])
   1.170 +
   1.171 +            message_three_actual = json.loads(self.handler.messages[2])
   1.172 +
   1.173 +            self.check_messages(message_one_expected, message_one_actual)
   1.174 +            self.check_messages(message_two_expected, message_two_actual)
   1.175 +            self.check_messages(message_three_expected, message_three_actual)
   1.176 +
   1.177 +    def test_log_listener(self):
   1.178 +        connection = '127.0.0.1', 0
   1.179 +        self.log_server = mozlog.LogMessageServer(connection,
   1.180 +                                                  self.logger,
   1.181 +                                                  message_callback=self.message_callback,
   1.182 +                                                  timeout=0.5)
   1.183 +
   1.184 +        message_string_one = json.dumps({'_message': 'socket message one',
   1.185 +                                         'action': 'test_message',
   1.186 +                                         '_level': 'DEBUG'})
   1.187 +        message_string_two = json.dumps({'_message': 'socket message two',
   1.188 +                                         'action': 'test_message',
   1.189 +                                         '_level': 'DEBUG'})
   1.190 +
   1.191 +        message_string_three = json.dumps({'_message': 'socket message three',
   1.192 +                                           'action': 'test_message',
   1.193 +                                           '_level': 'DEBUG'})
   1.194 +
   1.195 +        message_string = message_string_one + '\n' + \
   1.196 +                         message_string_two + '\n' + \
   1.197 +                         message_string_three + '\n'
   1.198 +
   1.199 +        server_thread = threading.Thread(target=self.log_server.handle_request)
   1.200 +        server_thread.start()
   1.201 +
   1.202 +        host, port = self.log_server.server_address
   1.203 +
   1.204 +        sock = socket.socket()
   1.205 +        sock.connect((host, port))
   1.206 +
   1.207 +        # Sleeps prevent listener from receiving entire message in a single call
   1.208 +        # to recv in order to test reconstruction of partial messages.
   1.209 +        sock.sendall(message_string[:8])
   1.210 +        time.sleep(.01)
   1.211 +        sock.sendall(message_string[8:32])
   1.212 +        time.sleep(.01)
   1.213 +        sock.sendall(message_string[32:64])
   1.214 +        time.sleep(.01)
   1.215 +        sock.sendall(message_string[64:128])
   1.216 +        time.sleep(.01)
   1.217 +        sock.sendall(message_string[128:])
   1.218 +
   1.219 +        server_thread.join()
   1.220 +
   1.221 +class Loggable(mozlog.LoggingMixin):
   1.222 +    """Trivial class inheriting from LoggingMixin"""
   1.223 +    pass
   1.224 +
   1.225 +class TestLoggingMixin(unittest.TestCase):
   1.226 +    """Tests basic use of LoggingMixin"""
   1.227 +
   1.228 +    def test_mixin(self):
   1.229 +        loggable = Loggable()
   1.230 +        self.assertTrue(not hasattr(loggable, "_logger"))
   1.231 +        loggable.log(mozlog.INFO, "This will instantiate the logger")
   1.232 +        self.assertTrue(hasattr(loggable, "_logger"))
   1.233 +        self.assertEqual(loggable._logger.name, "test_logger.Loggable")
   1.234 +
   1.235 +        self.assertRaises(ValueError, loggable.set_logger,
   1.236 +                          "not a logger")
   1.237 +
   1.238 +        logger = mozlog.MozLogger('test.mixin')
   1.239 +        handler = ListHandler()
   1.240 +        logger.addHandler(handler)
   1.241 +        loggable.set_logger(logger)
   1.242 +        self.assertTrue(isinstance(loggable._logger.handlers[0],
   1.243 +                                   ListHandler))
   1.244 +        self.assertEqual(loggable._logger.name, "test.mixin")
   1.245 +
   1.246 +        loggable.log(mozlog.WARN, 'message for "log" method')
   1.247 +        loggable.info('message for "info" method')
   1.248 +        loggable.error('message for "error" method')
   1.249 +        loggable.log_structured('test_message',
   1.250 +                                params={'_message': 'message for ' + \
   1.251 +                                        '"log_structured" method'})
   1.252 +
   1.253 +        expected_messages = ['message for "log" method',
   1.254 +                             'message for "info" method',
   1.255 +                             'message for "error" method',
   1.256 +                             'message for "log_structured" method']
   1.257 +
   1.258 +        actual_messages = loggable._logger.handlers[0].messages
   1.259 +        self.assertEqual(expected_messages, actual_messages)
   1.260 +
   1.261 +if __name__ == '__main__':
   1.262 +    unittest.main()

mercurial