Write Logs Your AI Agent Can Actually Debug

·5 min read·Updated
loggingai-agentsobservabilitymcpdebugging

When you hand an AI agent access to your app, the main thing it will do is read your logs — that's how it forms a theory about what's broken. But it reads them differently from you: it has no memory of your architecture, no idea which parts are new, and a limited context window that a noisy log fills instantly. Logs tuned for a human who already knows the system are close to useless to it. Three changes cover most of the gap: make each line self-describing, include the identifiers that connect lines to each other, and keep the volume low enough that the interesting part hasn't already scrolled away.

Volume matters more than you think

Container logs here are capped and rotate — roughly 10 MB retained per app. That's generous for a quiet service and nothing at all for a chatty one.

The consequence is specific: a fast failure loop scrolls its own cause out of retention. By the time you or an agent looks, the original stack trace is gone and all that remains is the hundredth identical retry. An agent given only the tail will confidently diagnose the symptom.

So the first improvement isn't a format, it's restraint. One line per meaningful event, not per iteration of a loop. Debug-level output behind a flag you turn on deliberately, rather than on by default because it was useful once.

Make each line stand on its own

You know what retry 3 refers to. An agent reading a hundred lines out of context doesn't.

# hard to work with
retry 3
failed
ok

# self-describing
webhook.delivery attempt=3 target=payments status=timeout after_ms=5000
webhook.delivery attempt=4 target=payments status=ok after_ms=210

Each line names the operation, the thing it acted on, the outcome, and a number. That's enough for an agent to form a hypothesis from a single line — which matters, because it may only get a fragment.

Structured output helps here. JSON lines parse reliably, survive being quoted out of order, and let an agent filter rather than skim. If plain text is easier to live with, key=value pairs get most of the benefit.

Give it a thread to follow

A single request usually produces several lines, and an agent can't tell which belong together in an interleaved log.

Attach an identifier — a request ID, a message ID, a job ID — to every line produced by one unit of work:

req=8f2a1c order.create user=1042 items=3
req=8f2a1c payment.charge amount=1999 status=declined code=insufficient_funds
req=8f2a1c order.create status=failed reason=payment_declined

Now "why did this order fail" is one filter away, and the agent can reconstruct the sequence without guessing. This is the single highest-value change for anything that handles concurrent work.

Say what to do, not just what happened

The difference between a fast fix and a long session is often one sentence.

# a traceback ending in KeyError: 'DISCORD_TOKEN'
# vs
config.error DISCORD_TOKEN is not set - set it as an environment variable on the app

The second tells the agent both the cause and the remedy, in the platform's own terms. Do this for anything unrecoverable: missing configuration, failed authentication, an unreachable dependency. It costs one string at the point where you already know exactly what went wrong.

And exit non-zero on unrecoverable errors, so the exit code lands in the event history and matches what the log says. Restarts are triggered by your process exiting; an agent checking the status of an app that stayed up will conclude nothing is wrong.

Log the state the agent can't see

An agent can read your logs and run commands, but it can't see what your process decided at startup. Print it:

app.start version=2026-08-08.3 commit=a41f9c env=production
app.config db=sqlite path=/data/app.db port=8000 bind=0.0.0.0
app.config vars_present=DISCORD_TOKEN,DATABASE_URL vars_missing=SENTRY_DSN

Three lines that answer most first questions: which build is running, where data is going, what it's listening on, and which configuration arrived. Given these, an agent stops guessing at exactly the points where guessing is most expensive.

Names, never values. vars_present=DISCORD_TOKEN is useful; the token itself is a secret you've now published to your logs — and into whatever conversation the agent is having, which becomes part of a transcript you might paste somewhere.

Don't make the agent parse a wall

Two habits that waste an agent's context:

  • Don't log full request or response bodies by default. Log sizes, status codes, and identifiers, and put bodies behind a debug flag.
  • Truncate user-supplied strings. A message that contains a novel is a message that pushes the useful lines out of view — and user text in your logs is also an injection path into your agent's context, so keeping it short and clearly delimited is doubly worthwhile.

The startup line earns its place

If you adopt one thing from this post, make it the startup banner. A single line naming the version and the loaded configuration turns "it exits instantly" into "it exits after config load", which is a completely different investigation — and it's the line most likely to survive rotation, because it appears once per restart rather than once per event.

For a crash-looping app, it also lets an agent count restarts by counting banners in the log stream.

FAQ

How should I format logs for an AI agent to read?

One self-describing line per event, ideally structured as JSON lines or key=value, with an identifier tying related lines together.

Why does my agent misdiagnose problems from the logs?

It's probably reading the tail after the original error rotated away. Logs are capped, so a noisy failure loop hides its own cause.

Should I log request bodies?

Not by default. Log sizes, statuses, and IDs; keep bodies behind a debug flag so they don't fill the retained window.

What's the most useful single line to add?

A startup line with the version and the configuration it loaded. It distinguishes "never started" from "started and then failed".

Is it safe to let an agent read my logs?

Yes, if the logs don't contain secrets. Log variable names rather than values, and truncate user-supplied text.

Do I still need exit codes if my logs are good?

Yes. The exit code appears in the event history and is often what an agent checks first.

Wrapping up

Writing logs for an agent isn't a new discipline so much as an honest one — every improvement here also helps the human reading them at 2am with no memory of what they wrote last spring. Name the operation, carry an identifier, say what to do about the failure, and keep the volume low enough that the cause is still there when someone goes looking. The agent gets a diagnosis instead of a guess, and so do you.