Frameworks

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 with zero boilerplate.

Prompt
Set up evlog in my Nuxt app with wide events and structured errors.

- Install evlog: pnpm add evlog
- Add 'evlog/nuxt' to modules in nuxt.config.ts
- Set evlog.env.service to my app name
- useLogger, createError, and parseError are auto-imported
- Create a server/api route using useLogger(event) and log.set() to build a wide event
- Throw errors with createError({ message, status, why, fix })
- Wide events are auto-emitted when each request completes

Docs: https://www.evlog.dev/frameworks/nuxt
Adapters: https://www.evlog.dev/adapters

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

See the Configuration reference for the full list of shared options (enabled, pretty, silent, sampling, middleware options, etc.).

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
silentbooleanfalseSuppress console output. Events are still built, sampled, and drained. Use for stdout-based platforms
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 }],
      },
    },
  },
})