Skip to content

Commit 4e78c98

Browse files
Handle log messages where string representation is json
1 parent dfb3cb3 commit 4e78c98

4 files changed

Lines changed: 101 additions & 9 deletions

File tree

context_logger/filter.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# SPDX-FileCopyrightText: 2024 Attila Gombos <attila.gombos@effective-range.com>
33
# SPDX-License-Identifier: MIT
44

5+
import json
56
import socket
67
from importlib.metadata import version, PackageNotFoundError
78
from logging import Filter, LogRecord
@@ -22,16 +23,19 @@ def filter(self, record: LogRecord) -> bool:
2223
record.msg = {self._message_field: record.msg % record.args if record.args else record.msg}
2324
record.args = ()
2425

25-
record.msg['hostname'] = socket.gethostname()
26-
record.msg['application'] = self._application_name
27-
record.msg['app_version'] = self._get_application_version()
26+
if isinstance(record.msg, dict):
27+
record.msg['hostname'] = socket.gethostname()
28+
record.msg['application'] = self._application_name
29+
record.msg['app_version'] = self._get_application_version()
2830

29-
if 'process_name' in record.msg:
30-
record.msg['process_name'] = record.processName
31+
if 'process_name' in record.msg:
32+
record.msg['process_name'] = record.processName
3133

32-
for key, value in self._global_context.items():
33-
if key not in record.msg:
34-
record.msg[key] = str(value)
34+
for key, value in self._global_context.items():
35+
if key not in record.msg:
36+
record.msg[key] = str(value)
37+
else:
38+
record.msg = json.loads(str(record.msg))
3539

3640
except Exception as exception:
3741
print('Failed to handle log record:', exception)

context_logger/logger.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# SPDX-FileCopyrightText: 2024 Attila Gombos <attila.gombos@effective-range.com>
33
# SPDX-License-Identifier: MIT
44

5+
import json
56
import logging.handlers
67
import os
78
import sys
@@ -161,7 +162,14 @@ def _ensure_directory_exists(self, log_file_path: str) -> None:
161162
os.makedirs(directory)
162163

163164
def _enrich_stdlib_log(self, _: logging.Logger, __: str, event_dict: dict[Any, Any]) -> dict[Any, Any]:
164-
event_dict.update(event_dict['_record'].msg)
165+
try:
166+
message = event_dict['_record'].msg
167+
if isinstance(message, dict):
168+
event_dict.update(message)
169+
else:
170+
event_dict.update(json.loads(str(message)))
171+
except Exception as exception:
172+
print('Failed to handle stdlib log record:', exception)
165173

166174
return event_dict
167175

tests/filterTest.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,25 @@ def test_filter_handles_non_mapping_message(self):
7070
# Then
7171
self.assertTrue(result)
7272
self.assertEqual(123, record.msg)
73+
print_mock.assert_not_called()
74+
75+
def test_filter_handles_unexpected_version_lookup_error(self):
76+
# Given
77+
filter_ = ContextSetupFilter('example-app', 'message')
78+
record = logging.LogRecord('ExampleClass', logging.INFO, __file__, 10, 'Hello', (), None)
79+
80+
with patch('context_logger.filter.socket.gethostname', return_value='test-host'), \
81+
patch.object(filter_, '_get_application_version', side_effect=RuntimeError('boom')), \
82+
patch('builtins.print') as print_mock:
83+
# When
84+
result = filter_.filter(record)
85+
86+
# Then
87+
self.assertTrue(result)
88+
self.assertEqual('Hello', record.msg.get('message'))
89+
self.assertEqual('test-host', record.msg.get('hostname'))
90+
self.assertEqual('example-app', record.msg.get('application'))
91+
self.assertNotIn('app_version', record.msg)
7392
print_mock.assert_called_once()
7493
self.assertEqual('Failed to handle log record:', print_mock.call_args.args[0])
7594

tests/loggerFallbackTest.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,67 @@ def test_ensure_directory_exists_does_not_create_existing_directory(self):
133133
# Then
134134
makedirs_mock.assert_not_called()
135135

136+
def test_enrich_stdlib_log_updates_event_dict_from_mapping_message(self):
137+
# Given
138+
logger = Logger('example-app', 'INFO', None, 1024, 1, False, 'message')
139+
record = MagicMock()
140+
record.msg = {'message': 'ok', 'code': 7}
141+
event_dict = {'_record': record, 'existing': True}
142+
143+
# When
144+
result = logger._enrich_stdlib_log(logging.getLogger('x'), 'info', event_dict)
145+
146+
# Then
147+
self.assertIs(result, event_dict)
148+
self.assertEqual('ok', event_dict.get('message'))
149+
self.assertEqual(7, event_dict.get('code'))
150+
self.assertTrue(event_dict.get('existing'))
151+
152+
def test_enrich_stdlib_log_updates_event_dict_from_json_message(self):
153+
# Given
154+
logger = Logger('example-app', 'INFO', None, 1024, 1, False, 'message')
155+
record = MagicMock()
156+
record.msg = '{"message": "ok", "code": 9}'
157+
event_dict = {'_record': record}
158+
159+
# When
160+
result = logger._enrich_stdlib_log(logging.getLogger('x'), 'info', event_dict)
161+
162+
# Then
163+
self.assertIs(result, event_dict)
164+
self.assertEqual('ok', event_dict.get('message'))
165+
self.assertEqual(9, event_dict.get('code'))
166+
167+
def test_enrich_stdlib_log_handles_invalid_message(self):
168+
# Given
169+
logger = Logger('example-app', 'INFO', None, 1024, 1, False, 'message')
170+
record = MagicMock()
171+
record.msg = object()
172+
event_dict = {'_record': record, 'untouched': 'value'}
173+
174+
with patch('builtins.print') as print_mock:
175+
# When
176+
result = logger._enrich_stdlib_log(logging.getLogger('x'), 'info', event_dict)
177+
178+
# Then
179+
self.assertIs(result, event_dict)
180+
self.assertEqual('value', event_dict.get('untouched'))
181+
print_mock.assert_called_once()
182+
self.assertEqual('Failed to handle stdlib log record:', print_mock.call_args.args[0])
183+
184+
def test_get_foreign_prechain_appends_stdlib_enricher(self):
185+
# Given
186+
logger = Logger('example-app', 'INFO', None, 1024, 1, False, 'message')
187+
logger._shared_processors = ['p1', 'p2']
188+
189+
# When
190+
prechain = logger._get_foreign_prechain()
191+
192+
# Then
193+
self.assertEqual(['p1', 'p2'], prechain[:-1])
194+
self.assertIs(prechain[-1].__self__, logger)
195+
self.assertIs(prechain[-1].__func__, Logger._enrich_stdlib_log)
196+
136197

137198
if __name__ == '__main__':
138199
unittest.main()

0 commit comments

Comments
 (0)