What a log is for, structured log lines, what an agent should record, and what must never be written.
When your agent misbehaves, the first thing you want is a record of what happened. A log is that record: lines your program writes as it works, with a time and a description. Python's standard logging module is enough to start. Three habits make logs useful:
Use levels honestly.DEBUG for fine details, INFO for normal events, WARNING for something odd that was handled, ERROR for a failure.
Log events with fields, not sentences. Write log.info("model call finished", extra={"request_id": rid, "latency_ms": 412}), not "model call finished in 412ms for user 42".
Put a request ID on every line, so that you can pull out one whole run and see everything it did.
Same event, two styles. The structured one can be filtered by user, request, model or latency.
The example project's JsonFormatter turns each record into one JSON line, including anything passed in extra. This is real output:
Because every field has a name, a tool can answer questions such as "show me every request that took longer than 2 seconds" or "count errors per model", which a free-text sentence cannot.
What to log in an agent
Log
Why
Request ID, user or session ID
Follow one run end to end
Each model call: model name, token counts, latency
Cost and speed
Each tool call: name, arguments (redacted), duration, success or error
See what the agent actually did
Each retry and its reason
Spot rate limits and flaky tools
Step number and stop reason
Why the loop ended
What never to log
Keys and tokens. The example adds a redaction filter as a safety net: it masks anything that looks like a key before the line is written.
Passwords and full personal data. Prompts and model replies may contain private information. Log sizes and IDs by default, and full text only in a protected debug mode.
Here is the safety net catching two accidents, with real output:
log.info("calling with Authorization: Bearer abcdef123456789")log.warning("config api_key=sk-live-abcdefghijklmnop loaded")
{"ts":"...","level":"INFO","logger":"app","message":"calling with Authorization: Bearer [REDACTED]"}{"ts":"...","level":"WARNING","logger":"app","message":"config api_key=[REDACTED] loaded"}
A redaction filter catches accidents. It is not a licence to log secrets: it only knows the patterns it was given.
For the curious How the redaction filter works
The filter is a small class that Python's logging system calls for every record before it is written. It runs a few regular expressions over the message: one for text shaped like sk- followed by letters and digits, one for the word Bearer followed by a long token, and one for api_key=, token: or password= followed by a value. Each match is replaced with [REDACTED]. Because it works on patterns, a key with an unusual shape will pass through, so extend the patterns for the providers you use and keep the rule "do not log secrets" as the first line of defence.
Check yourself
Why is a JSON log line more useful than a sentence?
Name two things an agent should log for every model call.
What is the redaction filter for, and what is it not?