Skip to course content
Free LangGraph course

LangGraph for Agentic Workflows

Unit 10.04: The trace you wish you had logged

Every field in this record exists because someone once needed it during an incident and did not have it.

State before, state after, and the routing reason

Node name and duration are the obvious fields. The three that matter under pressure are the state on either side of the node and the reason the router chose what it chose.

The code prints one complete trace entry.

import json

entry = {
    "run_id": "run-8841",
    "thread_id": "order-8841",
    "node": "charge",
    "attempt": 2,
    "started_at": "2026-07-29T10:14:02Z",
    "duration_ms": 340,
    "state_before": {"validated": True, "charged": False, "attempts": 1},
    "state_after": {"validated": True, "charged": True, "attempts": 2},
    "routed_to": "notify",
    "route_reason": "charged is True",
    "error": None,
}
print(json.dumps(entry, indent=2))

print("""
`state_before` and `state_after` per node are what let you find the first wrong
value without re-running anything. `route_reason` is the one people leave out,
and it is what turns "why did it go to notify" from an archaeology exercise
into a field you read.

Log the diff if full state is too large -- but log the routing reason always.
""")

state_before and state_after per node let you find the first wrong value without re-running anything - the technique from the first unit of this module, available on runs you never expected to investigate.

route_reason is the field most often omitted and the one that saves the most time. "Why did it go to notify?" becomes a string you read rather than an exercise in reconstructing the router's logic against the state at that moment.

The mistake this prevents

Before turning this on everywhere, note what it collects. state_before and state_after contain whatever was in the state - the user's question, retrieved documents, tool arguments, sometimes a customer's details. A full trace store is a second copy of your most sensitive data in a system that was built for debugging and often reviewed less carefully than the primary one. Give it the same access control and retention limits as the data it mirrors, and redact the fields you know are sensitive on the way in rather than promising to clean it later.

The mistake is logging only failures. The trace you need is nearly always from a run that completed successfully and produced a wrong answer - no exception, no error field, nothing that a failure-triggered log would have captured. Log successful runs too, at whatever sampling rate you can afford.

Takeaway

Log state before and after each node, plus the routing reason. If full state is too large, log the diff - but log the routing reason always, and log successful runs, not only failures.