Frameworks

Nuxt

Using evlog with Nuxt — automatic wide events, structured errors, drain adapters, enrichers, tail sampling, and client transport in Nuxt applications.

evlog provides a first-class Nuxt module with auto-imported useLogger, createError, and parseError. Add it to your config and start logging — zero boilerplate.

Quick Start

1. Install

pnpm add evlog

2. Add the module

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

That's it. useLogger, createError, and parseError are auto-imported.

Wide Events

Build up context progressively throughout a request with useLogger(event). evlog emits a single wide event when the request completes.

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

  log.set({ user: { id: body.userId, plan: 'enterprise' } })

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

  const payment = await processPayment(cart)
  log.set({ payment: { method: payment.method, cardLast4: payment.last4 } })

  return { success: true, orderId: payment.orderId }
})

One request, one log line with all context:

Terminal output
10:23:45 INFO [my-app] POST /api/checkout 200 in 145ms
  ├─ user: id=usr_123 plan=enterprise
  ├─ cart: items=3 total=14999
  ├─ payment: method=card cardLast4=4242
  └─ requestId: a1b2c3d4-...

Error Handling

createError produces structured errors with why, fix, and link fields that help both humans and AI agents understand what went wrong.

server/api/payment/process.post.ts
export default defineEventHandler(async (event) => {
  const log = useLogger(event)
  const body = await readBody(event)

  log.set({ payment: { amount: body.amount } })

  if (body.amount <= 0) {
    throw createError({
      status: 400,
      message: 'Invalid payment amount',
      why: 'The amount must be a positive number',
      fix: 'Pass a positive integer in cents (e.g. 4999 for $49.99)',
      link: 'https://docs.example.com/api/payments#amount',
    })
  }

  return { success: true }
})
Nuxt's error handler automatically catches EvlogError and returns a structured JSON response with why, fix, and link fields.

Configuration

All options are set in nuxt.config.ts under the evlog key:

OptionTypeDefaultDescription
enabledbooleantrueGlobally enable/disable all logging. When false, all operations become no-ops
consolebooleantrueEnable/disable browser console output
env.servicestring'app'Service name shown in logs
env.environmentstringAuto-detectedEnvironment name
includestring[]undefinedRoute patterns to log. Supports glob (/api/**)
excludestring[]undefinedRoute patterns to exclude. Exclusions take precedence
routesRecord<string, RouteConfig>undefinedRoute-specific service configuration
prettybooleantrue in devPretty print with tree formatting
sampling.ratesobjectundefinedHead sampling rates per log level (0-100%)
sampling.keeparrayundefinedTail sampling conditions to force-keep logs
transport.enabledbooleanfalseEnable client-to-server log transport
transport.endpointstring'/api/_evlog/ingest'Transport endpoint

Route Filtering

Use include and exclude to control which routes are logged:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    include: ['/api/**', '/auth/**'],
    exclude: [
      '/api/_nuxt_icon/**',
      '/api/_content/**',
      '/api/health',
    ],
  },
})
Exclusions take precedence. If a path matches both include and exclude, it will be excluded.

Route-Based Service Names

Assign different service names to different route groups:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    env: { service: 'default-service' },
    routes: {
      '/api/auth/**': { service: 'auth-service' },
      '/api/payment/**': { service: 'payment-service' },
      '/api/booking/**': { service: 'booking-service' },
    },
  },
})

Drain & Enrichers

Use Nitro plugin hooks to send logs to external services and enrich them with additional context.

Drain Plugin

server/plugins/evlog-drain.ts
import type { DrainContext } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'

const pipeline = createDrainPipeline<DrainContext>({
  batch: { size: 50, intervalMs: 5000 },
  retry: { maxAttempts: 3 },
})
const drain = pipeline(createAxiomDrain())

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', drain)
})

Enricher Plugin

server/plugins/evlog-enrich.ts
import {
  createUserAgentEnricher,
  createGeoEnricher,
  createRequestSizeEnricher,
  createTraceContextEnricher,
} from 'evlog/enrichers'

const enrichers = [
  createUserAgentEnricher(),
  createGeoEnricher(),
  createRequestSizeEnricher(),
  createTraceContextEnricher(),
]

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:enrich', (ctx) => {
    for (const enricher of enrichers) enricher(ctx)
  })
})
See the Adapters and Enrichers docs for the full list of available drains and enrichers.

Sampling

Head Sampling

Randomly keep a percentage of logs per level. Runs before the request completes.

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    sampling: {
      rates: {
        info: 10,
        warn: 50,
        debug: 5,
        error: 100,
      },
    },
  },
})

Each level is a percentage from 0 to 100. Levels you don't configure default to 100% (keep everything). Error defaults to 100% even when other levels are configured.

Tail Sampling

Evaluate after the request completes and force-keep logs that match specific conditions, regardless of head sampling.

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    sampling: {
      rates: { info: 10 },
      keep: [
        { duration: 1000 },
        { status: 400 },
        { path: '/api/critical/**' },
      ],
    },
  },
})

Custom Tail Sampling

For conditions beyond status, duration, and path, use the evlog:emit:keep hook:

server/plugins/evlog-sampling.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
    const user = ctx.context.user as { premium?: boolean } | undefined
    if (user?.premium) {
      ctx.shouldKeep = true
    }
  })
})
Errors are always kept by default. You have to explicitly set error: 0 to drop them.

Client Transport

Send browser logs to your server for processing and draining alongside server-side events.

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    transport: {
      enabled: true,
      endpoint: '/api/_evlog/ingest',
    },
  },
})

How It Works

  1. Client calls log.info({ action: 'click', button: 'submit' })
  2. Log is sent to /api/_evlog/ingest via POST
  3. Server enriches with environment context
  4. evlog:drain hook is called with source: 'client'
  5. External services receive the log

Client Identity

Attach user context to every client log with setIdentity:

Nuxt (auto-imported)
// After login
setIdentity({ userId: 'usr_123', orgId: 'org_456' })

log.info({ action: 'checkout' })
// -> { userId: 'usr_123', orgId: 'org_456', action: 'checkout', ... }

// After logout
clearIdentity()

Syncing Identity with Auth

Use a route middleware to keep identity in sync with your auth state:

middleware/identity.global.ts
export default defineNuxtRouteMiddleware(() => {
  const { user } = useAuth()

  if (user.value) {
    setIdentity({ userId: user.value.id, email: user.value.email })
  } else {
    clearIdentity()
  }
})

Production Tips

Use Nuxt's $production override to keep full logging in development while sampling and disabling console output in production:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    env: { service: 'my-app' },
  },
  $production: {
    evlog: {
      console: false,
      sampling: {
        rates: { info: 10, warn: 50, debug: 0 },
        keep: [{ duration: 1000 }, { status: 400 }],
      },
    },
  },
})