We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Config, Logs and Untrusted Input step 13 of 16
Turn Log Fields into JSON-Safe Data
So far in this track, each log call has produced a line of text, such as
order 17 failed after 3 retries. To find every failed order, someone has
to parse that text. A structured log writes each fact as a named field
instead, usually one JSON object per line:
{"event": "order.failed", "order_id": 17, "retries": 3}.
Two things can go wrong when you log those fields. First, json.dumps
raises TypeError on a value such as a Decimal or a date. mypy cannot
warn you, because it accepts json.dumps on any dict[str, object].
Second, a field name can clash with a name the log record already uses.
Logging's extra argument, which adds fields to a record, raises
KeyError: "Attempt to overwrite 'module' in LogRecord" for a field called
"module". Your function fixes both before you log anything.
build_log_payload(
{"event": "created", "module": "billing"},
frozenset({"module"}),
{"request_id": "r1"},
)
# {"event": "created", "context": {"request_id": "r1"}, "_dropped": ["module"]}
What to write
Fill in build_log_payload(record_fields, reserved, context). It returns a
new dict[str, object]. It does not log anything or build a JSON string.
- Copy each entry of
record_fieldsto the top level, with its value converted by the rules below. Do not change the input mappings. - Leave out a field whose name is in
reservedor inENVELOPE_KEYS, which holds"context"and"_dropped". Do not convert its value. Leave out only those names. - Put the names you left out under
"_dropped", as a sorted list. Leave"_dropped"out when you dropped nothing. - If
contextis not empty, convert its values and put the result under"context". Leave"context"out whencontextis empty. - Check names only at the top level of
record_fields. A nested key such as"module", or any key incontext, is ordinary data and stays.
Convert each value
Try these rows in order, for every kept field value and every context value,
at every level of nesting. Use isinstance, so subclasses match too.
| Value | Becomes |
|---|---|
None, str, int, float or bool |
the value itself |
a Mapping |
a new dict, with str(key) for each key and each value converted |
a Sequence other than str, bytes and bytearray |
a new list of the converted items |
| anything else | repr(value) |
Some results to check against:
- The tuple
(1, "x")becomes[1, "x"]. b"raw"becomes"b'raw'", not a list of numbers.Decimal("1.50")becomes"Decimal('1.50')", not a float or"1.50".- A date becomes
"datetime.date(2026, 7, 28)". - Sets are not sequences, so
frozenset({7})becomes"frozenset({7})". - If two keys of one mapping turn into the same string, the later one in iteration order wins.
The driver
Keep solve and _INJECTABLE as they are. solve copies record_fields
and context, then sets values that JSON cannot hold. Each entry of
inject_record maps a field name to one of these keys of _INJECTABLE:
decimal, date, bytes, frozenset, tuple or nested. solve sets
that field to the value, replacing any field of the same name. inject does
the same for context. Missing inputs default to empty, and reserved
reaches your function as a frozenset. Your code must pass mypy --strict.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.