A log event is a claim made by a producer at a particular point in a program. Useful investigation starts by preserving what that claim actually contains. A sentence such as “checkout failed again” is readable, but its fields are unclear: is checkout a service, an operation or a customer? A structured event gives each role a documented name. Stable event names such as request.completed support queries; attributes carry values such as route or outcome.
This course investigates deterministic local fixtures. You will build parsers and evidence transformations using Python's standard library. You will not start a service, configure a logging backend, export telemetry or establish operational readiness. Native C++ microbenchmarks measure another population and environment; a short loop's runtime does not substitute for request observability.
Our first schema has service, level, event and attributes. JSON syntax is only the first boundary. A JSON array is syntactically valid but is not an event object here. An object with an extra field is also rejected rather than silently becoming an undocumented schema version. Attributes use string values in this small fixture; later lessons deliberately use richer, separately contracted records.
import json
row = json.loads('{"service":"checkout","level":"warn","event":"request.failed","attributes":{"route":"/pay"}}')
print(row["level"].upper(), row["event"], row["attributes"]["route"])The severity vocabulary is INFO, WARN and ERROR. Normalize case because the contract permits it, then validate membership. Do not guess that an unknown value means INFO. Preserve service and event identifiers verbatim: undocumented trimming or renaming can merge distinct producers. Severity is the producer's classification, not evidence of the customer impact or root cause.
A default JSON decoder silently keeps the last value for a repeated key. That is a poor evidence policy when two service fields disagree. The build uses object_pairs_hook to reject duplicates before dictionary construction, including duplicates inside attributes. It also rejects nonstandard constants instead of accepting NaN-like values accidentally. These checks address input interpretation; they cannot verify that the producer told the truth.
Process each physical line once and retain its original line number. Ignore whitespace-only lines, but count them toward error locations. If physical line three fails after an ignored blank, report line three. Raise a concise exception without embedding the entire record: a useful location should not copy unknown secrets into a second output channel. Fail the transformation instead of returning an apparently complete prefix.
In the exercises, first explain which boundary each invalid record violates. Then predict what the default JSON decoder does, diagnose severity and duplicate-key defects, and complete the parser. Test empty input, invalid shapes, mixed-case severity and nested duplicate keys. A successful parser gives later analysis well-defined records; it does not redact sensitive values or prove that missing events never happened.