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

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