@harlan-zw/nuxt-wide-events

Minimal Nuxt Wide Events with build-time field enforcement.
18,279 13
Install
npm install @harlan-zw/nuxt-wide-events
Latest Version:0.2.0
Vue:3
License:MIT
Last Updated:Oct 21, 2026
Links: GitHub  ·  npm
Maintainer: harlan_zw

Nuxt Wide Events

npm version npm downloads License Nuxt

One structured record for each Nuxt server request, with every field checked at build time.

Why Nuxt Wide Events?

Most request logging spreads one request across many lines. Any of those lines can carry data you never meant to log.

  • 🧩 Scattered log lines: You rebuild a request from a trail of lines before you can debug it.
  • 🔓 Secrets reach logs: A logger that accepts any object logs an email or a token as soon as someone passes one.
  • 🐢 Runtime redaction on every request: Deep redaction and regular expression matching run on each record in production.

Nuxt Wide Events writes one wide event per request, and you list every field in config before code can use it. The build parses each server file and rejects unknown fields, object spreads, computed names, and dynamic objects, so reviewers and coding agents can see every field a record can carry.

Features

  • 📝 One record per request: You debug a request from one flat line, not a trail of scattered log lines.
  • 🚧 Build-time field enforcement: An unapproved key stops the build, so no secret reaches a log by accident.
  • 🪶 Small production runtime: Logging adds little cost per request, because production skips stack formatting and pretty printing.
  • 🎚️ Levels that stick: A handler that recovers from an error still logs an error, so sampling and drains see the real level.
  • ⚙️ Background records: Queue Jobs, scheduled work, and other background operations get the same single record as a request.
  • 🚰 Drain hook: You send records to D1, Sentry, or your own adapter from one Nitro hook, not from each route.
  • 📉 Route exclusion and sampling: Noisy routes stop flooding your logs, and your evlog filter config works unchanged.

Installation

npx nuxi@latest module add @harlan-zw/nuxt-wide-events

[!TIP] Generate an Agent Skill for this package using skilld:

npx skilld add @harlan-zw/nuxt-wide-events
export default defineNuxtConfig({
  modules: ['@harlan-zw/nuxt-wide-events'],

  wideEvents: {
    service: 'shop',
    fields: [
      'cart.itemCount',
      'user.id',
    ],
  },
})

Add Fields

addWideEventFields is available in server code.

export default defineEventHandler((event) => {
  addWideEventFields(event, {
    'cart.itemCount': 2,
    'user.id': 'user_123',
  })

  return { ok: true }
})

Each value must be a string, number, boolean, or null. Nested objects cannot hide unapproved data.

This code stops the build because user.email is not configured:

addWideEventFields(event, {
  'user.email': user.email,
})

Variables and spreads also stop the build:

addWideEventFields(event, fields)
addWideEventFields(event, { ...fields })

Set the level

setWideEventLevel marks a request wide event as debug, info, warn, or error.

export default defineEventHandler(async (event) => {
  try {
    return await chargeCard()
  }
  catch {
    setWideEventLevel(event, 'error')
    return { charged: false }
  }
})

A record keeps the highest level it receives. If the request handler recovers from an error, the record stays an error. Drains and sampling both see the real level.

getActiveWideEventRequestId(event) returns the request ID while the wide event collects data. Use it to tie an application logger, Sentry, or another request-scoped module to the same request. It returns undefined before collection starts and after emission.

Production output

Default production output skips stack formatting, deep redaction, regular expression matching, and pretty printing.

{ "timestamp": "2026-08-13T04:12:00.000Z", "level": "info", "kind": "request", "service": "shop", "method": "GET", "path": "/api/cart", "status": 200, "durationMs": 1.4, "requestId": "req_123", "cart.itemCount": 2, "user.id": "user_123" }

kind is request for a request record and background for a background record.

In production, an error record carries the status only. It drops every error string, because an error string can hold unapproved data.

In development, records include error messages and stacks. The terminal shows each record as a compact block, with request metadata in the header and configured fields in a tree.

Background operations

createWideEvent is available in server code for Queue Jobs, scheduled work, and other background operations.

export default defineTask({
  async run() {
    const wideEvent = createWideEvent({ 'job.id': 'job_123' })
    wideEvent.setLevel('warn')
    return await wideEvent.emit()
  },
})

A background record carries kind: "background". It has no method, path, or status, because a background operation has none.

The Nuxt auto-import selects JSON output in production and object output in development. It uses the configured service, console, sampling, and drain options. With drain: true, emit() returns a Promise and waits for background drain adapters. Without a drain, emit() stays synchronous.

If Nuxt auto-imports are unavailable, import from @harlan-zw/nuxt-wide-events/standalone. Inside Nitro, this export resolves to the same configured variant as the auto-import. So a deep import never loses service, console, sampling, or drain. Outside Nitro, it writes production JSON with no module configuration.

To turn off request collection, set request: false. Field enforcement, createWideEvent, and setWideEventLevel still work.

To stop all output, set enabled: false. Every server import still resolves, so your application code needs no change.

Migrate from evlog

Map env.service to service. Keep exclude and sampling unchanged. Do not copy console: false. In evlog it controls browser output; here it controls server output.

For requests, replace log.set({ section: { value } }) with an approved flat field:

addWideEventFields(event, { 'section.value': value })

For background operations, replace createLogger(fields) with createWideEvent(fields). Replace each .set(fields) call with addWideEventFields(wideEvent, fields). Keep .setLevel(). If drain is enabled, await or return .emit().

For requests, replace log.setLevel(level) with setWideEventLevel(event, level).

If a site only logs background operations, set request: false. Convert spreads, computed keys, arrays, and nested objects into configured primitive fields. Keep browser logging and custom error transports in the application.

Production filtering

The config shape matches evlog, so you can copy it across:

export default defineNuxtConfig({
  wideEvents: {
    exclude: ['/api/_nuxt_icon/**', '/api/_content/**'],
    sampling: {
      rates: { info: 10, warn: 50, debug: 0 },
      keep: [{ duration: 1000 }, { status: 400 }],
    },
  },
})

Rates are percentages. The module keeps a record that matches one keep condition in full. Every part of a condition must match. The module tries the conditions in order. So { duration: 1000, status: 500 } keeps a slow server error, and [{ duration: 1000 }, { status: 500 }] keeps either one.

Level rates apply to request and background records alike. A background record has no status, so a status condition never keeps one.

The build compiles route patterns. A pattern that ends with /** also matches the bare prefix, the same as Nitro. If you set no exclude or sampling, production uses a separate plugin with no filtering code.

Drain records

If D1, Sentry, or another adapter owns the record, use the Nitro hook:

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('wide-events:emit', async (record) => {
    await sendRecord(record)
  })
})

To turn on this hook, set drain: true. console then defaults to false, because the hook owns the record. To keep stdout output too, set console: true. Request drains use event.waitUntil(). Background emit() waits for every hook adapter and rejects if an adapter fails.

Options

Option Default Purpose
enabled true Emit wide events. false keeps field enforcement and server imports.
request true Collect one wide event for each request.
fields [] Allow application fields.
service none Add a service name.
exclude [] Exclude routes that match a glob pattern.
sampling none Set rates and keep conditions for production.
console true, or false with a drain Write records to stdout.
drain false Call the wide-events:emit hook for request and background records.

Benchmarks

Run the production benchmarks on the runtime you deploy to:

pnpm test:bench

The suite compares lifecycle cost and serialization against raw JSON, Pino, and evlog. It also includes 0x flamegraphs and real Nitro HTTP fixtures for this module and evlog.

See the core results, Nitro HTTP results, and CPU profile.

node bench/http/run.mjs
npx 0x --tree-debug bench/profile.mjs wide

The Cloudflare fixture builds with the Workers preset, passes a Wrangler deploy dry run, and serves a request through local workerd.

Scope

This module covers Nuxt server requests, background operations, flat primitive fields, route exclusion, sampling, stdout, and a Nitro hook.

It does not cover browser logging, transports, audit logs, or production error presentation.

Sponsors

License

Licensed under the MIT license.

Related Packages

@harlan-zw/nuxt-cloudflare

Opinionated Cloudflare deployment defaults and diagnostics for Nuxt.

24,264 13
@harlan-zw/comark-content

Markdown-only Nuxt content powered by Comark.

21,053 13
@harlan-zw/nuxt-dx

Experimental Nuxt diagnostics: dev error overlay and runtime JavaScript bundle s...

19,993 13
@harlan-zw/nuxt-cf-jobs

Experimental Nuxt module for typed Cloudflare queue jobs.

19,143 13
@harlan-zw/nuxt-use-query

Experimental Nuxt-native query composables with SWR, invalidation, polling, and...

18,565 13

Version History

Version Released License
0.2.0 MIT
0.1.4 MIT
0.1.2 MIT
0.1.1 MIT
0.1.0 MIT