Skip to content
Writing
4 min readLogging

Structured Logging for AI Debugging

Logging one structured event per request with evlog, so an AI assistant can follow what happened in a request.

When an AI coding assistant tries to help you debug a production issue, it reads your logs. If your logs are scattered console.log calls with inconsistent formatting, it can't do much with them. It doesn't know which log lines belong to the same request, what the timing was, or what the error context means.

Evlog is a structured logging library by Hugo Richard, designed around the "wide event" pattern: one structured event per request, with all the context attached. I've been using it in my projects, and it's particularly useful when you're debugging with AI tools, because the log output is machine-readable by design.

The Problem with console.log

A typical Node.js app logs like this:

[INFO] Processing order 12345
[INFO] User: john@example.com
[DEBUG] Fetching inventory for SKU-789
[WARN] Inventory low: 3 remaining
[INFO] Order processed in 847ms
[ERROR] Failed to send confirmation email

That's six log lines for one request. To understand what happened you have to stitch them together by timing and work out which lines belong to which request when there are 50 concurrent users.

An AI assistant reading these logs has the same problem, and without explicit correlation it can't infer the causal chain between lines.

One Event Per Request

With evlog you build up one event over the request lifecycle:

import { createRequestLogger } from 'evlog'
 
export default defineEventHandler(async (event) => {
  const log = createRequestLogger(event)
 
  log.field('user', user.email)
  log.field('orderId', orderId)
 
  const items = await fetchInventory(skus)
  log.field('inventoryCount', items.length)
 
  if (items.some(i => i.stock < 5)) {
    log.field('lowStock', true)
  }
 
  await processOrder(order)
 
  // Duration is tracked automatically from request start
  // The event emits on request end with all fields attached
})

When the request finishes, evlog emits one structured JSON event:

{
  "level": "info",
  "message": "POST /api/orders",
  "timestamp": "2026-03-05T10:23:45.123Z",
  "duration": 847,
  "user": "john@example.com",
  "orderId": 12345,
  "inventoryCount": 3,
  "lowStock": true,
  "env": { "service": "api", "environment": "production" }
}

Every field is on the same event, and duration is filled in for you. An AI assistant can read one JSON object and see who made the request, what happened, how long it took and what was unusual.

Self-Documenting Errors

For AI debugging, the most useful part of evlog is its error structure. Where you'd normally write throw new Error("Failed to sync"), you create errors with why and fix fields:

throw new EvlogError({
  message: 'Failed to sync repository',
  status: 503,
  why: 'GitHub API rate limit exceeded',
  fix: 'Wait 1 hour or use a different token',
  link: 'https://docs.github.com/en/rest/rate-limit',
  cause: originalError,
})

An AI reading this error in a log gets the message, the root cause and a next step it can act on, which is more than it usually has to go on. That's the difference between an AI saying "there's an error on line 47" and "the GitHub sync failed because you've exceeded the rate limit: you can either wait an hour or switch to a different API token."

Automatic Context Injection

Evlog's framework integrations (Nuxt, Next.js, Express, Fastify, Hono, SvelteKit) automatically attach request context without manual wiring:

  • request method and path, from the HTTP layer
  • duration, from request start to response end
  • status code, from the response
  • environment (service name, deployment version, commit hash, region), from runtime detection

In Nuxt it's one module entry:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    env: { service: 'my-app' },
  }
})

Every server route then gets a request logger with duration tracking. The module injects it, so there's nothing to import and no middleware to set up.

Pasting logs into a model

Most of us now paste logs into a model and ask what went wrong, and the answer is only as good as the logs. With correlation, timing and the error's why and fix on one event, the model has what it needs to reason about the problem. With scattered console.log calls it has to guess how the lines relate, and it often guesses wrong. The same JSON event is easier for me to read, too.

I liked it enough to send a couple of small fixes upstream: a docs fix for the Cloudflare Workers setup and one that skips unnecessary Nitro adapter probes.

Next essayTF-IDF in OCaml: When Functional Programming ClicksBuilding a local code search engine in OCaml was a good excuse to learn TF-IDF, pure pipelines, and where immutability helps.