Stop grepping
through chaos.

Stop grepping
through chaos.

Wide events and structured errors for TypeScript. One log per request, full context, errors that explain why and how to fix.

request logs

Three lines of code.
Full observability.

checkout.post.ts
export default defineEventHandler(async (event) => {
  const log = useLogger(event)

  log.set({ user: { id: user.id, plan: user.plan } })
  log.set({ cart: { items: 3, total: 9999 } })

  return { success: true }
})
outputINFO
INFOPOST/api/checkout(234ms)
user: { id: 1842, plan: "pro" }
cart: { items: 3, total: 9999 }
status: 200
requestId: "req_8f2k..."

One log with full context

Errors that explain why.

Root cause Fix suggestion AI-parseable
payment.post.ts
throw createError({
  message: 'Payment failed',
  status: 402,
  why: 'Card declined by issuer',
  fix: 'Try a different card',
})
outputERROR
ERRORPOST/api/payment402
message: "Payment failed"
why: "Card declined by issuer"
fix: "Try a different card"

Actionable error messages

Built for agents.

Structured fields, machine-readable context, and actionable metadata — everything an AI agent needs to diagnose and resolve issues on its own.

Structured context Machine-parseable Actionable output
outputERROR
ERRORPOST/api/payment402
message: "Payment processing failed"
why: "Card issuer declined: insufficient funds"
fix: "Retry with a different payment method"
user: { id: 1842, plan: "pro" }
links: ["stripe.com/docs/declines"]
AI Agent analyzing
Reading structured error context...
Root cause

Card declined by issuer — insufficient funds

User impact

Pro plan user (#1842) blocked on payment

Suggested fix

Prompt for alternate payment method

Documentation

stripe.com/docs/declines/codes

Auto-created issue PAY-4521

Send everywhere.

Batched writes, automatic retries with backoff, and fan-out to multiple destinations. Your logs flow through a pipeline that never blocks your response.

Batching Retry & backoff Fan-out

Non-blocking

Pipeline runs in the background. Your response ships immediately.

Guaranteed delivery

Exponential backoff with jitter ensures logs reach every destination.

Bring your own drain

Write a simple function to send logs anywhere.

evlog-drain.ts
import { createDrainPipeline } from 'evlog/pipeline'
import { createAxiomDrain } from 'evlog/axiom'
import { createSentryDrain } from 'evlog/sentry'

const pipeline = createDrainPipeline({
  drains: [
    createAxiomDrain(),
    createSentryDrain(),
  ],
  batchSize: 50,
  flushInterval: 5000,
})
evlog
BATCH · RETRY · FANOUT
Axiom
OTLP
Sentry
PostHog
Better Stack
+ Custom drains

Your stack. Covered.

One module for Nuxt. First-class Next.js support. Standalone API for everything else.

server/api/checkout.post.ts
export default defineEventHandler(async (event) => {
  const log = useLogger(event)
  const { cartId } = await readBody(event)

  const cart = await db.findCart(cartId)
  log.set({ cart: { items: cart.items.length, total: cart.total } })

  const charge = await stripe.charge(cart.total)
  log.set({ stripe: { chargeId: charge.id } })

  if (!charge.success) {
    throw createError({
      status: 402,
      message: 'Payment failed',
      why: charge.decline_reason,
      fix: 'Try a different payment method',
    })
  }

  return { orderId: charge.id }
})

Better logging
by tonight.

Wide events, structured errors, dead simple setup. Set up evlog in 10 minutes. Your future self will thank you.

© 2026 - Made by HugoRCD