summaryrefslogtreecommitdiff
path: root/test_logformat.py
diff options
context:
space:
mode:
Diffstat (limited to 'test_logformat.py')
-rwxr-xr-xtest_logformat.py222
1 files changed, 222 insertions, 0 deletions
diff --git a/test_logformat.py b/test_logformat.py
new file mode 100755
index 0000000..cbcc14d
--- /dev/null
+++ b/test_logformat.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3
+import asyncio
+import contextlib
+import datetime
+import functools
+import io
+import logging
+import textwrap
+import unittest
+import unittest.mock
+
+import logformat
+
+
+def set_up(level=logging.DEBUG):
+ stream = io.StringIO()
+ logfmt_handler = logging.StreamHandler(stream)
+ logfmt_handler.setFormatter(logformat.LogfmtFormatter())
+ logging.basicConfig(handlers=[logfmt_handler], level=level, force=True)
+ return stream
+
+
+# We're not using pytest here because pytest messes with the logging config,
+# setting it's own handlers, apparently even when disabling the pytest logging
+# plugin with `-p no:logging`.
+test_suite = unittest.TestSuite()
+
+
+def test(test):
+ @functools.wraps(test)
+ def wrapped_test():
+ t = unittest.TestCase()
+ with unittest.mock.patch("time.time_ns", return_value=1742634901036965331):
+ test(t)
+
+ test_suite.addTest(unittest.FunctionTestCase(wrapped_test))
+
+
+@test
+def test_logformat(t: unittest.TestCase):
+ stream = set_up()
+ logger = logformat.get_logger()
+ logger.debug("what's happening")
+ logger.info("something's happening")
+ logger.warning("this seems off")
+ logger.error("oh no")
+ t.assertEqual(
+ stream.getvalue(),
+ textwrap.dedent(
+ '''
+ time=2025-03-22T09:15:01Z level=debug msg="what's happening"
+ time=2025-03-22T09:15:01Z level=info msg="something's happening"
+ time=2025-03-22T09:15:01Z level=warning msg="this seems off"
+ time=2025-03-22T09:15:01Z level=error msg="oh no"
+ '''
+ ).lstrip(),
+ )
+
+
+@test
+def test_escaping(t: unittest.TestCase):
+ stream = set_up()
+ logger = logformat.get_logger()
+ logger.warning(" \t\n\x00='\"‽")
+ t.assertEqual(
+ stream.getvalue(),
+ 'time=2025-03-22T09:15:01Z level=warning msg=" \\t\\n\\u0000=\'\\"‽"\n',
+ )
+
+
+@test
+def test_fmtlogger_extra_uses_str(t: unittest.TestCase):
+ class Foo:
+ def __str__(self):
+ return "hello!"
+
+ stream = set_up()
+ logger = logformat.get_logger()
+ logger.warning("test", value=Foo())
+ t.assertEqual(
+ stream.getvalue(),
+ 'time=2025-03-22T09:15:01Z level=warning msg=test value=hello!\n',
+ )
+
+
+@test
+def test_fmtlogger_extra_none(t: unittest.TestCase):
+ stream = set_up()
+ logger = logformat.get_logger()
+ logger.warning("test", helpful_info=None)
+ t.assertEqual(
+ stream.getvalue(),
+ 'time=2025-03-22T09:15:01Z level=warning msg=test helpful_info=null\n',
+ )
+
+
+@test
+def test_fmtlogger_extra_bool(t: unittest.TestCase):
+ stream = set_up()
+ logger = logformat.get_logger()
+ logger.warning("test", off_by_one=True)
+ t.assertEqual(
+ stream.getvalue(),
+ 'time=2025-03-22T09:15:01Z level=warning msg=test off_by_one=true\n',
+ )
+
+
+@test
+def test_fmtlogger_extra_datetime(t: unittest.TestCase):
+ stream = set_up()
+ logger = logformat.get_logger()
+ logger.warning(
+ "test",
+ expiry=datetime.datetime(2025, 3, 22, 15, 30, 00, 00, tzinfo=datetime.UTC),
+ )
+ t.assertEqual(
+ stream.getvalue(),
+ 'time=2025-03-22T09:15:01Z level=warning msg=test expiry=2025-03-22T15:30:00Z\n',
+ )
+
+
+@test
+def test_stdlib_logger(t: unittest.TestCase):
+ stream = set_up()
+ logger = logging.getLogger()
+ logger.debug("what's happening %s", 1234)
+ logger.info("something's happening")
+ logger.warning("this seems off")
+ logger.error("oh no")
+ logger.critical("aaaaaaaaah!")
+ t.assertEqual(
+ stream.getvalue(),
+ textwrap.dedent(
+ '''
+ time=2025-03-22T09:15:01Z level=debug msg="what's happening 1234"
+ time=2025-03-22T09:15:01Z level=info msg="something's happening"
+ time=2025-03-22T09:15:01Z level=warning msg="this seems off"
+ time=2025-03-22T09:15:01Z level=error msg="oh no"
+ time=2025-03-22T09:15:01Z level=critical msg=aaaaaaaaah!
+ '''
+ ).lstrip(),
+ )
+
+
+@test
+def test_log_uncaught_sync_returns(t: unittest.TestCase):
+ stream = set_up()
+
+ logger = logformat.get_logger()
+
+ @logger.log_uncaught
+ def run():
+ return 42
+
+ t.assertEqual(run(), 42)
+ t.assertEqual(stream.getvalue(), "")
+
+
+@test
+def test_log_uncaught_sync_raises(t: unittest.TestCase):
+ stream = set_up()
+
+ logger = logformat.get_logger()
+
+ @logger.log_uncaught
+ def run():
+ 5 / 0
+
+ with t.assertRaises(SystemExit), stub_traceback():
+ run()
+
+ t.assertEqual(
+ stream.getvalue(),
+ 'time=2025-03-22T09:15:01Z level=error msg="unexpected exception: division by zero" traceback="stub traceback"\n',
+ )
+
+
+@test
+def test_log_uncaught_async_returns(t: unittest.TestCase):
+ stream = set_up(level=logging.INFO)
+
+ logger = logformat.get_logger()
+
+ @logger.log_uncaught
+ async def run():
+ return 42
+
+ t.assertEqual(asyncio.run(run()), 42)
+ t.assertEqual(stream.getvalue(), "")
+
+
+@test
+def test_log_uncaught_async_raises(t: unittest.TestCase):
+ stream = set_up(level=logging.INFO)
+
+ logger = logformat.get_logger()
+
+ @logger.log_uncaught
+ async def run():
+ 5 / 0
+
+ with t.assertRaises(SystemExit), stub_traceback():
+ asyncio.run(run())
+
+ t.assertEqual(
+ stream.getvalue(),
+ 'time=2025-03-22T09:15:01Z level=error msg="unexpected exception: division by zero" task=Task-4 traceback="stub traceback"\n',
+ )
+
+
+@contextlib.contextmanager
+def stub_traceback():
+ def stub_print_exception(*args, **kwargs):
+ kwargs['file'].write('stub traceback')
+
+ with unittest.mock.patch("traceback.print_exception", stub_print_exception):
+ yield
+
+
+if __name__ == "__main__":
+ runner = unittest.TextTestRunner()
+ runner.run(test_suite)