comparison test/test_config.py @ 8446:14c7c07b32d8

feature: add thread local trace_id and trace_reason to logging. Added trace_id to default logging so that all logs for a given request share the same trace_id. This allows correlation of logs across a request. admin_guide.txt, upgrading.txt: add docs update sample configs to include trace_id. rewrite logging docs in admin_guide. Hopefully they are clearer now. clean up some stuff in the logging config file docs. admin.py: add decorators to run_command to enable trace_id. change calls to db.commit() to use run_command to get trace_id. configuration.py: clean up imports. update docstrings, comments and inline docs. add trace_id to default log format. add function for testing decorated with trace_id. add support for dumping stack trace in logging. add check for pytest in sys.modules to enable log propagation when pytest is running. Otherwise tests fail as the caplog logger doesn't see the roundup logs. logcontext.py: new file to handle thread local contextvar mangement. mailgw.py: add decorators for trace_id etc. scripts/roundup_xlmrpc_server.py: add decorators for trace_id etc. fix encoding bug turning bytes into a string. fix command line issue where we can't set encoding. (not sure if changing encoding via command line even works) cgi/client.py decorate two entry points for trace_id etc. cgi/wsgi_handler.py: decorate entry point for trace_id etc. test/test_config.py: add test for trace_id in new log format. test various cases for sinfo and errors in formating msg.
author John Rouillard <rouilj@ieee.org>
date Tue, 16 Sep 2025 22:53:00 -0400
parents 39a6825d10ca
children d06be9346c68
comparison
equal deleted inserted replaced
8445:4b6e9a0e13ee 8446:14c7c07b32d8
416 416
417 417
418 @pytest.mark.usefixtures("save_restore_logging") 418 @pytest.mark.usefixtures("save_restore_logging")
419 class TrackerConfig(unittest.TestCase): 419 class TrackerConfig(unittest.TestCase):
420 420
421 @pytest.fixture(autouse=True)
422 def inject_fixtures(self, caplog):
423 self._caplog = caplog
424
421 @pytest.fixture(scope="class") 425 @pytest.fixture(scope="class")
422 def save_restore_logging(self): 426 def save_restore_logging(self):
423 """Save logger state and try to restore it after all tests in 427 """Save logger state and try to restore it after all tests in
424 this class have finished. 428 this class have finished.
425 429
1007 config.load(self.dirname) 1011 config.load(self.dirname)
1008 1012
1009 print(cm.exception) 1013 print(cm.exception)
1010 self.assertEqual(cm.exception.args[0], self.dirname) 1014 self.assertEqual(cm.exception.args[0], self.dirname)
1011 1015
1012 1016 def testFormattedLogging(self):
1017 """Depends on using default logging format with %(trace_id)"""
1018
1019 def find_file_occurances(string):
1020 return len(re.findall(r'\bFile\b', string))
1021
1022 config = configuration.CoreConfig()
1023
1024 config.LOGGING_LEVEL = "DEBUG"
1025 config.init_logging()
1026
1027
1028 # format the record and verify the logformat/trace_id.
1029 config._logging_test(None, msg="message")
1030 tuple = self._caplog.record_tuples[0]
1031 self.assertEqual(tuple[1], 20)
1032 self.assertEqual("message", tuple[2])
1033 logger = logging.getLogger('roundup')
1034 hdlr = logger.handlers[0]
1035 log = hdlr.format(self._caplog.records[0])
1036 # verify that %(trace_id) was set and substituted
1037 # Note: trace_id is not initialized in this test case
1038 log_parts = log.split()
1039 self.assertRegex(log_parts[2], r'^[A-Za-z0-9]{22}')
1040 self._caplog.clear()
1041
1042 # the rest check various values of sinfo and msg formating.
1043
1044 # sinfo = 1 - one line of stack starting with log call
1045 config._logging_test(1)
1046 tuple = self._caplog.record_tuples[0]
1047 self.assertEqual(tuple[1], 20)
1048 self.assertIn("test a_var\n File", tuple[2])
1049 self.assertIn("in _logging_test", tuple[2])
1050 self.assertIn("logger.info(msg, extra=", tuple[2])
1051 self.assertEqual(find_file_occurances(tuple[2]), 1)
1052 self._caplog.clear()
1053
1054 # sinfo = None - 5 lines of stack starting with log call
1055 config._logging_test(None)
1056 tuple = self._caplog.record_tuples[0]
1057 self.assertEqual(tuple[1], 20)
1058 self.assertIn("test a_var\n File", tuple[2])
1059 self.assertIn("in _logging_test", tuple[2])
1060 self.assertIn("logger.info(msg, extra=", tuple[2])
1061 self.assertEqual(find_file_occurances(tuple[2]), 5)
1062 self._caplog.clear()
1063
1064 # sinfo = 0 - whole stack starting with log call
1065 config._logging_test(0)
1066 tuple = self._caplog.record_tuples[0]
1067 self.assertEqual(tuple[1], 20)
1068 self.assertIn("test a_var\n File", tuple[2])
1069 self.assertIn("in _logging_test", tuple[2])
1070 self.assertIn("logger.info(msg, extra=", tuple[2])
1071 # A file in 'pytest' directory should be at the top of stack.
1072 self.assertIn("pytest", tuple[2])
1073 # no idea how deep the actual stack is, could change with python
1074 # versions, but 3.12 is 33 so ....
1075 self.assertTrue(find_file_occurances(tuple[2]) > 10)
1076 self._caplog.clear()
1077
1078 # sinfo = -1 - one line of stack starting with extract_stack()
1079 config._logging_test(-1)
1080 tuple = self._caplog.record_tuples[0]
1081 self.assertEqual(tuple[1], 20)
1082 self.assertIn("test a_var\n File", tuple[2])
1083 # The call to extract_stack should be included as the frame
1084 # at bottom of stack.
1085 self.assertIn("extract_stack()", tuple[2])
1086 # only one frame included
1087 self.assertEqual(find_file_occurances(tuple[2]), 1)
1088 self._caplog.clear()
1089
1090 # sinfo = 1000 - whole stack starting with log call 1000>stack size
1091 config._logging_test(1000)
1092 tuple = self._caplog.record_tuples[0]
1093 self.assertEqual(tuple[1], 20)
1094 self.assertIn("test a_var\n File", tuple[2])
1095 self.assertIn("in _logging_test", tuple[2])
1096 self.assertIn("logger.info(msg, extra=", tuple[2])
1097 # A file in 'pytest' directory should be at the top of stack.
1098 self.assertIn("pytest", tuple[2])
1099 # no idea how deep the actual stack is, could change with python
1100 # versions, but 3.12 is 33 so ....
1101 self.assertTrue(find_file_occurances(tuple[2]) > 10)
1102 self._caplog.clear()
1103
1104 # sinfo = -1000 - whole stack starting with extract_stack
1105 config._logging_test(-1000)
1106 tuple = self._caplog.record_tuples[0]
1107 self.assertEqual(tuple[1], 20)
1108 self.assertIn("test a_var\n File", tuple[2])
1109 self.assertIn("in _logging_test", tuple[2])
1110 self.assertIn("logger.info(msg, extra=", tuple[2])
1111 # The call to extract_stack should be included as the frame
1112 # at bottom of stack.
1113 self.assertIn("extract_stack()", tuple[2])
1114 # A file in 'pytest' directory should be at the top of stack.
1115 # no idea how deep the actual stack is, could change with python
1116 # versions, but 3.12 is 33 so ....
1117 self.assertTrue(find_file_occurances(tuple[2]) > 10)
1118 self.assertIn("pytest", tuple[2])
1119 self._caplog.clear()
1120
1121 # pass args and compatible message
1122 config._logging_test(None, args=(1,2,3),
1123 msg="one: %s, two: %s, three: %s"
1124 )
1125 tuple = self._caplog.record_tuples[0]
1126 self.assertEqual(tuple[1], 20)
1127 self.assertEqual('one: 1, two: 2, three: 3', tuple[2])
1128 self._caplog.clear()
1129
1130 # error case for incorrect placeholder
1131 config._logging_test(None, msg="%(a)")
1132 tuple = self._caplog.record_tuples[0]
1133 self.assertEqual(tuple[1], 20)
1134 self.assertEqual("%(a)", tuple[2])
1135 self._caplog.clear()
1136
1137 # error case for incompatible format record is the first argument
1138 # and it can't be turned into floating point.
1139 config._logging_test(None, msg="%f")
1140 tuple = self._caplog.record_tuples[0]
1141 self.assertEqual(tuple[1], 20)
1142 self.assertEqual("%f", tuple[2])
1143 self._caplog.clear()
1144
1145
1013 def testXhtmlRaisesOptionError(self): 1146 def testXhtmlRaisesOptionError(self):
1014 self.munge_configini(mods=[ ("html_version = ", "xhtml") ]) 1147 self.munge_configini(mods=[ ("html_version = ", "xhtml") ])
1015 1148
1016 config = configuration.CoreConfig() 1149 config = configuration.CoreConfig()
1017 1150

Roundup Issue Tracker: http://roundup-tracker.org/