2013-07-25 13:27:53 -07:00
|
|
|
# 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/.
|
|
|
|
|
2014-02-19 13:42:01 -08:00
|
|
|
import datetime
|
|
|
|
import json
|
2013-07-25 13:27:53 -07:00
|
|
|
import socket
|
2014-02-18 16:12:34 -08:00
|
|
|
import threading
|
2014-02-19 13:42:01 -08:00
|
|
|
import time
|
|
|
|
import unittest
|
|
|
|
|
|
|
|
import mozfile
|
|
|
|
|
|
|
|
import mozlog
|
2013-07-25 13:27:53 -07:00
|
|
|
|
|
|
|
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())
|
|
|
|
|
2014-02-19 13:42:01 -08:00
|
|
|
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')
|
|
|
|
|
2013-07-25 13:27:53 -07:00
|
|
|
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,
|
2014-02-19 13:42:01 -08:00
|
|
|
'_message': 'message one'})
|
2013-07-25 13:27:53 -07:00
|
|
|
self.logger.log_structured('test_message',
|
|
|
|
{'_level': mozlog.INFO,
|
2014-02-19 13:42:01 -08:00
|
|
|
'_message': 'message two'})
|
|
|
|
self.logger.log_structured('error_message',
|
|
|
|
{'_level': mozlog.ERROR,
|
|
|
|
'diagnostic': 'unexpected error'})
|
2013-07-25 13:27:53 -07:00
|
|
|
|
|
|
|
message_one_expected = {'_namespace': 'test.Logger',
|
|
|
|
'_level': 'INFO',
|
2014-02-19 13:42:01 -08:00
|
|
|
'_message': 'message one',
|
2013-07-25 13:27:53 -07:00
|
|
|
'action': 'test_message'}
|
|
|
|
message_two_expected = {'_namespace': 'test.Logger',
|
|
|
|
'_level': 'INFO',
|
2014-02-19 13:42:01 -08:00
|
|
|
'_message': 'message two',
|
2013-07-25 13:27:53 -07:00
|
|
|
'action': 'test_message'}
|
2014-02-19 13:42:01 -08:00
|
|
|
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}
|
2013-07-25 13:27:53 -07:00
|
|
|
|
|
|
|
message_one_actual = json.loads(self.handler.messages[0])
|
|
|
|
message_two_actual = json.loads(self.handler.messages[1])
|
2014-02-19 13:42:01 -08:00
|
|
|
message_three_actual = json.loads(self.handler.messages[2])
|
2013-07-25 13:27:53 -07:00
|
|
|
|
|
|
|
self.check_messages(message_one_expected, message_one_actual)
|
|
|
|
self.check_messages(message_two_expected, message_two_actual)
|
2014-02-19 13:42:01 -08:00
|
|
|
self.check_messages(message_three_expected, message_three_actual)
|
2013-07-25 13:27:53 -07:00
|
|
|
|
|
|
|
def message_callback(self):
|
|
|
|
if len(self.handler.messages) == 3:
|
|
|
|
message_one_expected = {'_namespace': 'test.Logger',
|
|
|
|
'_level': 'DEBUG',
|
2014-02-19 13:42:01 -08:00
|
|
|
'_message': 'socket message one',
|
2013-07-25 13:27:53 -07:00
|
|
|
'action': 'test_message'}
|
|
|
|
message_two_expected = {'_namespace': 'test.Logger',
|
|
|
|
'_level': 'DEBUG',
|
2014-02-19 13:42:01 -08:00
|
|
|
'_message': 'socket message two',
|
2013-07-25 13:27:53 -07:00
|
|
|
'action': 'test_message'}
|
|
|
|
message_three_expected = {'_namespace': 'test.Logger',
|
|
|
|
'_level': 'DEBUG',
|
2014-02-19 13:42:01 -08:00
|
|
|
'_message': 'socket message three',
|
2013-07-25 13:27:53 -07:00
|
|
|
'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)
|
|
|
|
|
2014-02-19 13:42:01 -08:00
|
|
|
message_string_one = json.dumps({'_message': 'socket message one',
|
2013-07-25 13:27:53 -07:00
|
|
|
'action': 'test_message',
|
2014-02-19 13:42:01 -08:00
|
|
|
'_level': 'DEBUG'})
|
|
|
|
message_string_two = json.dumps({'_message': 'socket message two',
|
2013-07-25 13:27:53 -07:00
|
|
|
'action': 'test_message',
|
2014-02-19 13:42:01 -08:00
|
|
|
'_level': 'DEBUG'})
|
2013-07-25 13:27:53 -07:00
|
|
|
|
2014-02-19 13:42:01 -08:00
|
|
|
message_string_three = json.dumps({'_message': 'socket message three',
|
2013-07-25 13:27:53 -07:00
|
|
|
'action': 'test_message',
|
2014-02-19 13:42:01 -08:00
|
|
|
'_level': 'DEBUG'})
|
2013-07-25 13:27:53 -07:00
|
|
|
|
|
|
|
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()
|
|
|
|
|
2014-02-19 13:42:01 -08:00
|
|
|
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)
|
|
|
|
|
2013-07-25 13:27:53 -07:00
|
|
|
if __name__ == '__main__':
|
|
|
|
unittest.main()
|