Nuxt
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
npm install evlog
yarn add evlog
bun add evlog
2. Add the module
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.
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:
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.
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 }
})
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:
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Globally enable/disable all logging. When false, all operations become no-ops |
console | boolean | true | Enable/disable browser console output |
env.service | string | 'app' | Service name shown in logs |
env.environment | string | Auto-detected | Environment name |
include | string[] | undefined | Route patterns to log. Supports glob (/api/**) |
exclude | string[] | undefined | Route patterns to exclude. Exclusions take precedence |
routes | Record<string, RouteConfig> | undefined | Route-specific service configuration |
pretty | boolean | true in dev | Pretty print with tree formatting |
sampling.rates | object | undefined | Head sampling rates per log level (0-100%) |
sampling.keep | array | undefined | Tail sampling conditions to force-keep logs |
transport.enabled | boolean | false | Enable client-to-server log transport |
transport.endpoint | string | '/api/_evlog/ingest' | Transport endpoint |
Route Filtering
Use include and exclude to control which routes are logged:
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
include: ['/api/**', '/auth/**'],
exclude: [
'/api/_nuxt_icon/**',
'/api/_content/**',
'/api/health',
],
},
})
include and exclude, it will be excluded.Route-Based Service Names
Assign different service names to different route groups:
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
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
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)
})
})
Sampling
Head Sampling
Randomly keep a percentage of logs per level. Runs before the request completes.
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.
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:
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
}
})
})
error: 0 to drop them.Client Transport
Send browser logs to your server for processing and draining alongside server-side events.
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
transport: {
enabled: true,
endpoint: '/api/_evlog/ingest',
},
},
})
How It Works
- Client calls
log.info({ action: 'click', button: 'submit' }) - Log is sent to
/api/_evlog/ingestvia POST - Server enriches with environment context
evlog:drainhook is called withsource: 'client'- External services receive the log
Client Identity
Attach user context to every client log with setIdentity:
// 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:
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:
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 }],
},
},
},
})