Installation
evlog supports multiple environments: Nuxt, Nitro, and standalone TypeScript.
Nuxt
Install evlog via your preferred package manager:
pnpm add evlog
npm install evlog
yarn add evlog
bun add evlog
Then add it to your Nuxt config using the evlog/nuxt module:
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
env: {
service: 'my-app',
},
// Optional: only log specific routes (supports glob patterns)
include: ['/api/**'],
// Optional: exclude specific routes from logging
exclude: ['/api/_nuxt_icon/**'],
},
})
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
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/**). If not set, all routes are logged |
exclude | string[] | undefined | Route patterns to exclude from logging. Supports glob (/api/_nuxt_icon/**). Exclusions take precedence over inclusions |
pretty | boolean | true in dev | Pretty print with tree formatting |
sampling.rates | object | undefined | Head sampling rates per log level (0-100%). See Sampling |
sampling.keep | array | undefined | Tail sampling conditions to force-keep logs. See Sampling |
transport.enabled | boolean | false | Enable sending client logs to the server. See Client Transport |
transport.endpoint | string | '/api/_evlog/ingest' | API endpoint for client log ingestion |
Route Filtering
Use include and exclude to control which routes are logged. Both support glob patterns.
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
// Log all API and auth routes...
include: ['/api/**', '/auth/**'],
// ...except internal/noisy routes
exclude: [
'/api/_nuxt_icon/**', // Nuxt Icon requests
'/api/_content/**', // Nuxt Content queries
'/api/health', // Health checks
],
},
})
include and exclude, it will be excluded.Sampling
At scale, logging everything can become expensive. evlog supports two sampling strategies:
Head Sampling (rates)
Random sampling based on log level, decided before the request completes:
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
sampling: {
rates: {
info: 10, // Keep 10% of info logs
warn: 50, // Keep 50% of warning logs
debug: 5, // Keep 5% of debug logs
error: 100, // Always keep errors (default)
},
},
},
})
error: 100, error logs are never sampled out unless you explicitly set error: 0.Tail Sampling (keep)
Force-keep logs based on request outcome, evaluated after the request completes. Useful to always capture slow requests or critical paths even when head sampling would drop them:
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
sampling: {
rates: { info: 10 }, // Only 10% of info logs
keep: [
{ duration: 1000 }, // Always keep if duration >= 1000ms
{ status: 400 }, // Always keep if status >= 400
{ path: '/api/critical/**' }, // Always keep critical paths
],
},
},
})
Conditions use >= comparison and follow OR logic (any match = keep).
Custom Tail Sampling Hook
For business-specific conditions (premium users, feature flags, etc.), use the evlog:emit:keep Nitro hook:
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
// Always keep logs for premium users
const user = ctx.context.user as { premium?: boolean } | undefined
if (user?.premium) {
ctx.shouldKeep = true
}
})
})
The hook receives a TailSamplingContext with status, duration, path, method, and the full accumulated context.
Log Draining
Send logs to external services like Axiom, Loki, or custom endpoints using the evlog:drain hook. The hook is called in fire-and-forget mode, meaning it never blocks the HTTP response.
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
await fetch('https://api.axiom.co/v1/datasets/logs/ingest', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.AXIOM_TOKEN}` },
body: JSON.stringify([ctx.event])
})
})
})
The hook receives a DrainContext with:
event: The completeWideEvent(timestamp, level, service, and all accumulated context)request: Optional request metadata (method,path,requestId)
Client Transport
Send browser logs to your server for centralized logging. When enabled, client-side log.info(), log.error(), etc. calls are automatically sent to the server via the /api/_evlog/ingest endpoint.
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
transport: {
enabled: true, // Enable client log transport
endpoint: '/api/_evlog/ingest', // default
},
},
})
How it works
- Client calls
log.info({ action: 'click', button: 'submit' }) - Log is sent to
/api/_evlog/ingestvia POST - Server enriches with environment context (service, version, region, etc.)
evlog:drainhook is called withsource: 'client'- External services receive the log (Axiom, Loki, etc.)
service, environment, or version from the client.In your drain hook, you can identify client logs by the source: 'client' field:
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
if (ctx.event.source === 'client') {
// Handle client logs specifically
console.log('[CLIENT]', ctx.event)
}
// Send to external service...
})
})
$production override to sample only in production while keeping full visibility in development:export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
env: { service: 'my-app' },
},
$production: {
evlog: {
sampling: {
rates: { info: 10, warn: 50, debug: 0 },
keep: [{ duration: 1000 }, { status: 400 }],
},
},
},
})
That's it! You can now use useLogger(event) in any API route.
Nitro
Install evlog via your preferred package manager:
pnpm add evlog
npm install evlog
yarn add evlog
bun add evlog
Then, add evlog as a Nitro plugin (without Nuxt) using the evlog/nitro plugin:
export default defineNitroConfig({
plugins: ['evlog/nitro'],
})
Standalone TypeScript
Install evlog via your preferred package manager:
pnpm add evlog
npm install evlog
yarn add evlog
bun add evlog
Then, use it as any other TypeScript library within your scripts, CLI tools, workers, or apps:
import { initLogger, createRequestLogger } from 'evlog'
// Initialize once at startup
initLogger({
env: {
service: 'my-worker',
environment: 'production',
},
// Optional: sample logs
sampling: {
rates: { info: 10, debug: 5 },
},
})
// Create a logger for each operation
const log = createRequestLogger({ jobId: job.id })
log.set({ source: job.source, target: job.target })
log.set({ recordsSynced: 150 })
log.emit() // Manual emit required in standalone mode
log.emit() manually. In Nuxt/Nitro, this happens automatically at request end.TypeScript Configuration
evlog is written in TypeScript and ships with full type definitions. No additional configuration is required.
Next Steps
- Quick Start - Learn the core concepts and start using evlog