Getting Started

Installation

Install evlog in your Nuxt, Nitro, or standalone TypeScript project.

evlog supports multiple environments: Nuxt, Nitro, and standalone TypeScript.

Nuxt

Install evlog via your preferred package manager:

pnpm add evlog

Then add it to your Nuxt config using the evlog/nuxt module:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    env: {
      service: 'my-app',
    },
    // Optional: only log specific routes (supports glob patterns)
    include: ['/api/**'],
  },
})

Configuration Options

OptionTypeDefaultDescription
env.servicestring'app'Service name shown in logs
env.environmentstringAuto-detectedEnvironment name
includestring[]undefinedRoute patterns to log. Supports glob (/api/**). If not set, all routes are logged
prettybooleantrue in devPretty print with tree formatting
sampling.ratesobjectundefinedHead sampling rates per log level (0-100%). See Sampling
sampling.keeparrayundefinedTail sampling conditions to force-keep logs. See Sampling

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:

nuxt.config.ts
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)
      },
    },
  },
})
Errors are always logged by default. Even if you don't specify 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:

nuxt.config.ts
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:

server/plugins/evlog-custom.ts
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.

server/plugins/evlog-axiom.ts
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 complete WideEvent (timestamp, level, service, and all accumulated context)
  • request: Optional request metadata (method, path, requestId)
Tip: Use Nuxt's $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

Then, add evlog as a Nitro plugin (without Nuxt) using the evlog/nitro plugin:

nitro.config.ts
export default defineNitroConfig({
  plugins: ['evlog/nitro'],
})
For early Nitro v3 support, use evlog/nitro/v3 instead of evlog/nitro. When using Nitro v3, import createError from evlog/nitro/v3 instead of evlog.

Standalone TypeScript

Install evlog via your preferred package manager:

pnpm add evlog

Then, use it as any other TypeScript library within your scripts, CLI tools, workers, or apps:

scripts/sync-job.ts
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
In standalone mode, you must call 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.

evlog requires TypeScript 5.0 or higher for optimal type inference.

Next Steps

  • Quick Start - Learn the core concepts and start using evlog
Copyright © 2026