npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nuxt-server-log

v0.1.3

Published

A Nuxt module for structured server-side logging

Downloads

492

Readme

npm version npm downloads License Nuxt

Structured, per-request server-side logging for Nuxt (Nitro). For every incoming request it emits a single JSON log line that bundles together:

  • the request itself (method, path, status, duration, client IP, request id),
  • every outgoing API call made on the server while handling that request, and
  • every error thrown during that request.

This works for both your API routes and your server-rendered (SSR) pages: when a page is rendered on the server, every data-fetching call your app makes during that render is captured under the page's request — so you can see exactly which upstream calls contributed to a page, how long each took, and which one made the page slow.

This gives you one correlated, machine-readable record per request — ready to ship to Elasticsearch, Loki, Datadog, or any log pipeline that ingests JSON.

Features

  • 📦  One JSON log per request — request, API calls, and errors correlated by a shared requestId.
  • 🖥️  SSR page insight — see every upstream call a server-rendered page made during its render, and exactly how long each took.
  • 🌐  Automatic outbound API tracking — server-side fetch calls are captured with URL, status, method, and duration.
  • 🧨  Automatic error capture — unhandled Nitro errors are recorded against the request that caused them.
  • 🐢  Slow-call warnings — configurable thresholds warn on slow responses and slow API calls.
  • 🔐  Query redaction — sensitive query params (tokens, passwords, …) are redacted by default.
  • 🎚️  Sampling & filtering — log a fraction of traffic and exclude noisy paths.
  • 🪪  X-Request-ID header — added to every response for end-to-end tracing.
  • 🧷  Crash-safe — circular references and BigInt never break logging.

Quick Setup

Install the module:

npx nuxt module add nuxt-server-log

Or manually:

npm install nuxt-server-log
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["nuxt-server-log"],
});

That's it. Requests are now logged to the server console as JSON. ✨

Using the logger

Besides automatic per-request logging, the module exposes a logger you can use anywhere in your server code (API routes, server plugins, middleware, utilities) to emit your own structured log lines. Each entry is enriched with the active request's requestId, so your logs line up with the per-request log.

The logger is auto-imported — just use it, no import needed:

// server/api/users.get.ts
export default defineEventHandler(async () => {
  logger.info("Fetching users");

  try {
    const users = await getUsers();
    logger.debug("Users fetched", { count: users.length });
    return users;
  } catch (error) {
    logger.error("Failed to fetch users", error as Error);
    throw error;
  }
});

If you prefer an explicit import (or your editor's auto-import setup needs it), import it from #imports:

import { logger } from "#imports";

Available methods

logger.debug(message: string, data?: Record<string, unknown>);
logger.info(message: string, data?: Record<string, unknown>);
logger.warn(message: string, data?: Record<string, unknown>);
logger.error(message: string, error?: Error, data?: Record<string, unknown>);
  • The optional data object is merged into the emitted JSON, so you can attach any structured fields you like.
  • logger.error additionally captures the error type and stack trace (up to traceDepth frames) and records it against the current request context.
  • Messages below the configured logLevel are skipped.

Configuration

Configure the module under the serverLog key in nuxt.config.ts:

export default defineNuxtConfig({
  modules: ["nuxt-server-log"],
  serverLog: {
    logLevel: "info",
    sampleRate: 1,
    excludePaths: ["/__nuxt_error", "/_nuxt"],
    apiDurationWarning: 1500,
    responseDurationWarning: 3000,
    remoteAddressHeader: "x-real-ip",
    redactQueryKeys: ["token", "password", "secret"],
    traceDepth: 10,
  },
});

| Option | Type | Default | Description | | ------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | enabled | boolean | true | Master switch. When false, the module registers nothing. | | logLevel | "debug" \| "info" \| "warn" \| "error" | "info" | Minimum level for the module's own log messages (e.g. slow-call warnings). The per-request log line is always emitted. | | sampleRate | number | 1 | Fraction of requests to log, from 0 (none) to 1 (all). E.g. 0.1 logs ~10% of requests. | | excludePaths | string[] | ["/__nuxt_error"] | Requests whose path starts with any of these are not logged. | | apiDurationWarning | number | 1500 | Emit a warn when a captured API call exceeds this many milliseconds. | | responseDurationWarning | number | 3000 | Emit a warn when total request duration exceeds this many milliseconds. | | remoteAddressHeader | string | undefined | Header to read the client IP from (e.g. behind a proxy). Falls back to X-Forwarded-For / socket address. | | redactQueryKeys | string[] | ["token", "password", "secret", "apiKey", "api_key", "auth", "authorization", "access_token", "refresh_token"] | Query-string keys whose values are replaced with [REDACTED] in logs (case-insensitive). | | traceDepth | number | 10 | Maximum number of stack-trace frames recorded per error. |

[!NOTE] remoteAddressHeader and X-Forwarded-For are client-controllable and can be spoofed unless your app sits behind a trusted proxy that overwrites them.

How it works

The module registers three pieces of Nitro runtime:

  1. A server middleware that opens a per-request context (via AsyncLocalStorage), assigns a requestId, sets the X-Request-ID response header, and writes the final JSON log line once the response finishes (or the connection closes).
  2. A fetch interceptor that wraps globalThis.fetch, so any server-side API call is recorded into the current request's context.
  3. An error hook that records unhandled Nitro errors against the active request.

Because everything is tied together through the request context, a single log line tells the full story of what happened during that request.

Example output

A server-rendered page (GET /) whose render fetched data from four upstream APIs. The whole page took ~2s, and you can immediately see that one categories call (1259ms) dominated the render time:

{
  "@timestamp": "2026-06-19T11:32:03.271Z",
  "requestId": "2ce40ef3-a8d0-4642-82d7-e15bcbf418dc",
  "userAgent": "unknown",
  "statusCode": 200,
  "method": "GET",
  "path": "/",
  "query": "",
  "remoteAddress": "::ffff:127.0.0.1",
  "duration": 2012,
  "apiCalls": [
    {
      "@timestamp": "2026-06-19T11:32:01.482Z",
      "statusCode": 200,
      "url": "https://api.example.com/v1/categories/",
      "method": "GET",
      "duration": 1259
    },
    {
      "@timestamp": "2026-06-19T11:32:02.801Z",
      "statusCode": 200,
      "url": "https://api.example.com/v1/sliders/",
      "method": "GET",
      "duration": 91
    }
  ],
  "errors": []
}

An API route that made one upstream API call:

{
  "@timestamp": "2026-06-19T11:34:41.959Z",
  "requestId": "ffbc6274-a7f0-40d6-86fe-1e4154ab1f1b",
  "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
  "statusCode": 200,
  "method": "GET",
  "path": "/api/products",
  "query": "?token=%5BREDACTED%5D&page=2",
  "remoteAddress": "203.0.113.7",
  "duration": 142,
  "apiCalls": [
    {
      "@timestamp": "2026-06-19T11:34:41.820Z",
      "statusCode": 200,
      "url": "https://api.example.com/products?page=2",
      "method": "GET",
      "duration": 98
    }
  ],
  "errors": []
}

A request where an upstream call failed and an error was thrown:

{
  "@timestamp": "2026-06-19T11:35:02.114Z",
  "requestId": "a18d2f90-1c4e-4b2a-9a77-2f0b5c9d11aa",
  "userAgent": "node",
  "statusCode": 500,
  "method": "POST",
  "path": "/api/checkout",
  "query": "",
  "remoteAddress": "203.0.113.7",
  "duration": 233,
  "apiCalls": [
    {
      "@timestamp": "2026-06-19T11:35:02.020Z",
      "statusCode": 503,
      "url": "https://payments.example.com/charge",
      "method": "POST",
      "duration": 180,
      "error": "HTTP Error 503: Service Unavailable"
    }
  ],
  "errors": [
    {
      "@timestamp": "2026-06-19T11:35:02.110Z",
      "error": "Payment provider unavailable",
      "type": "Error",
      "trace": [
        "at chargeCard (server/api/checkout.post.ts:24:11)",
        "at handler (server/api/checkout.post.ts:10:3)"
      ],
      "action": "unhandled"
    }
  ]
}

In addition to the per-request line, slow requests/calls produce standalone warn entries, e.g.:

{
  "@timestamp": "…",
  "level": "warn",
  "message": "Slow API call detected",
  "requestId": "…",
  "url": "https://api.example.com/products",
  "duration": 1820
}

Contribution

Contributions are welcome! The flow is the standard GitHub one: fork the repo, create a branch, and open a pull request.

License

MIT