Skip to content

Commit 4ef38b3

Browse files
fix: structured log handler drops reserved fields in json_fields (#634)
1 parent 330b53c commit 4ef38b3

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

google/cloud/logging_v2/handlers/structured_log.py

+24
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@
3636
"}"
3737
)
3838

39+
# reserved fields taken from Structured Logging documentation:
40+
# https://cloud.google.com/logging/docs/structured-logging
41+
GCP_STRUCTURED_LOGGING_FIELDS = frozenset(
42+
{
43+
"severity",
44+
"httpRequest",
45+
"time",
46+
"timestamp",
47+
"timestampSeconds",
48+
"timestampNanos",
49+
"logging.googleapis.com/insertId",
50+
"logging.googleapis.com/labels",
51+
"logging.googleapis.com/operation",
52+
"logging.googleapis.com/sourceLocation",
53+
"logging.googleapis.com/spanId",
54+
"logging.googleapis.com/trace",
55+
"logging.googleapis.com/trace_sampled",
56+
}
57+
)
58+
3959

4060
class StructuredLogHandler(logging.StreamHandler):
4161
"""Handler to format logs into the Cloud Logging structured log format,
@@ -70,6 +90,10 @@ def format(self, record):
7090
message = _format_and_parse_message(record, super(StructuredLogHandler, self))
7191

7292
if isinstance(message, collections.abc.Mapping):
93+
# remove any special fields
94+
for key in list(message.keys()):
95+
if key in GCP_STRUCTURED_LOGGING_FIELDS:
96+
del message[key]
7397
# if input is a dictionary, encode it as a json string
7498
encoded_msg = json.dumps(message, ensure_ascii=False)
7599
# strip out open and close parentheses

tests/unit/handlers/test_structured_log.py

+48
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,54 @@ def test_format_with_custom_formatter(self):
207207
self.assertIn(expected_result, result)
208208
self.assertIn("message", result)
209209

210+
def test_format_with_reserved_json_field(self):
211+
# drop json_field data with reserved names
212+
# related issue: https://github.com/googleapis/python-logging/issues/543
213+
import logging
214+
import json
215+
216+
handler = self._make_one()
217+
message = "hello world"
218+
extra = "still here"
219+
json_fields = {
220+
"message": "override?",
221+
"severity": "error",
222+
"logging.googleapis.com/trace_sampled": True,
223+
"time": "none",
224+
"extra": extra,
225+
"SEVERITY": "error",
226+
}
227+
record = logging.LogRecord(
228+
None,
229+
logging.INFO,
230+
None,
231+
None,
232+
message,
233+
None,
234+
None,
235+
)
236+
record.created = None
237+
setattr(record, "json_fields", json_fields)
238+
expected_payload = {
239+
"message": message,
240+
"severity": "INFO",
241+
"SEVERITY": "error",
242+
"logging.googleapis.com/trace": "",
243+
"logging.googleapis.com/spanId": "",
244+
"logging.googleapis.com/trace_sampled": False,
245+
"logging.googleapis.com/sourceLocation": {},
246+
"httpRequest": {},
247+
"logging.googleapis.com/labels": {},
248+
"extra": extra,
249+
}
250+
handler.filter(record)
251+
result = json.loads(handler.format(record))
252+
self.assertEqual(set(expected_payload.keys()), set(result.keys()))
253+
for (key, value) in expected_payload.items():
254+
self.assertEqual(
255+
value, result[key], f"expected_payload[{key}] != result[{key}]"
256+
)
257+
210258
def test_dict(self):
211259
"""
212260
Handler should parse json encoded as a string

0 commit comments

Comments
 (0)