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

@xenterprises/fastify-xlogger

v1.1.2

Published

Fastify plugin for standardized logging with Pino - context, redaction, and canonical schema

Downloads

41

Readme

xLogger

A Fastify plugin for standardized logging with Pino. Provides automatic request context, secret redaction, canonical log schema, boundary logging for external APIs, and background job correlation.

Philosophy

Logging is infrastructure. Centralize it and keep it boring.

  • Use Fastify's built-in Pino logger as the single logging engine
  • Never create a second logger
  • Log structured objects, not concatenated strings
  • Automatically redact secrets at the logger level
  • Standardize context across all log entries

Installation

npm install xlogger

Quick Start

import Fastify from "fastify";
import xLogger, { getLoggerOptions } from "xlogger";

// Create Fastify with xLogger-configured Pino options
const fastify = Fastify({
  logger: getLoggerOptions({
    serviceName: "my-api",
  }),
});

// Register the plugin
await fastify.register(xLogger, {
  serviceName: "my-api",
});

// Use structured logging everywhere
fastify.get("/users/:id", async (request, reply) => {
  const { id } = request.params;

  // Use the context-aware logger
  request.contextLog.info({ userId: id }, "Fetching user");

  // Or use the standard logger with context extraction
  fastify.xlogger.logEvent({
    event: "user.fetched",
    data: { userId: id },
    request,
  });

  return { id };
});

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | active | boolean | true | Enable/disable the plugin | | serviceName | string | process.env.SERVICE_NAME | Service name for logs | | environment | string | process.env.NODE_ENV | Environment name | | redactPaths | string[] | [] | Additional paths to redact | | redactClobber | boolean | false | Replace default redact paths | | contextExtractor | function | null | Custom context extraction function | | enableBoundaryLogging | boolean | true | Enable boundary logging helpers |

Features

1. Automatic Request Context

Every log entry automatically includes:

  • requestId - Unique request identifier
  • orgId - Tenant/organization ID (from headers or user object)
  • userId - User ID (from headers or user object)
  • route - Route pattern
  • method - HTTP method
  • traceId / spanId - OpenTelemetry context (if present)
// Context is automatically added to request.contextLog
request.contextLog.info({ action: "invite_sent" }, "Invite sent");

// Output includes requestId, orgId, userId, route, method automatically

2. Secret Redaction

Secrets are automatically redacted at the logger level:

// These fields are automatically redacted:
// - authorization, cookie, set-cookie headers
// - password, token, secret, apiKey fields
// - cardNumber, cvv, ssn, creditCard
// - Nested paths like *.password, *.token

fastify.log.info({
  user: {
    email: "[email protected]",
    password: "secret123"  // Will be logged as [REDACTED]
  }
}, "User data");

Default Redact Paths:

  • req.headers.authorization
  • req.headers.cookie
  • req.headers['set-cookie']
  • req.headers['x-api-key']
  • password, token, secret, apiKey, api_key
  • accessToken, refreshToken, privateKey
  • cardNumber, cvv, ssn, creditCard
  • *.password, *.token, *.secret, *.apiKey

3. Canonical Log Schema

Recommended fields for consistent log structure:

| Field | Description | |-------|-------------| | event | Event name (e.g., "user.created", "payment.completed") | | msg | Human-readable message | | requestId | Request identifier | | orgId | Organization/tenant ID | | userId | User ID | | route | Route pattern | | method | HTTP method | | statusCode | Response status code | | durationMs | Duration in milliseconds | | err | Error object | | vendor | External service name | | externalId | External resource ID |

4. Boundary Logging (External API Calls)

Log external API calls with timing, vendor IDs, and retry info:

// Simple boundary logging
fastify.xlogger.logBoundary({
  vendor: "stripe",
  operation: "createCustomer",
  externalId: "cus_123",
  durationMs: 150,
  success: true,
  request,
});

// Or use the boundary logger helper with automatic timing
const boundary = fastify.xlogger.createBoundaryLogger("stripe", "createCustomer", request);

try {
  const customer = await stripe.customers.create({ email });
  boundary.success({ externalId: customer.id, statusCode: 200 });
} catch (err) {
  boundary.fail(err, { statusCode: err.statusCode });
}

// With retries
const boundary = fastify.xlogger.createBoundaryLogger("twilio", "sendSMS", request);
for (let i = 0; i < 3; i++) {
  try {
    const result = await twilio.messages.create({ to, body });
    boundary.success({ externalId: result.sid });
    break;
  } catch (err) {
    boundary.retry();
    if (i === 2) boundary.fail(err);
  }
}

5. Background Job Correlation

Pass context to background jobs for tracing:

// In your route handler
fastify.post("/process", async (request, reply) => {
  const context = fastify.xlogger.extractContext(request);

  // Queue the job with context
  await queue.add("processData", {
    data: request.body,
    ...context,
  });

  return { queued: true };
});

// In your job processor
async function processJob(job) {
  const { requestId, orgId, userId, data } = job.data;

  const jobContext = fastify.xlogger.createJobContext({
    jobName: "processData",
    requestId,
    orgId,
    userId,
  });

  jobContext.start({ itemCount: data.length });

  try {
    await processData(data);
    jobContext.complete({ processed: data.length });
  } catch (err) {
    jobContext.fail(err);
    throw err;
  }
}

6. Environment-Aware Output

  • Production: JSON logs for aggregation systems
  • Development: Pretty-printed logs with colors
// Automatically detected from NODE_ENV
const options = getLoggerOptions();

// Or force pretty printing
const options = getLoggerOptions({ pretty: true });

7. Custom Transports (Betterstack, Logtail, etc.)

Send logs to centralized logging services like Betterstack/Logtail using custom transports:

Install Transport (Optional Peer Dependency)

npm install @logtail/pino

Single Transport (Betterstack)

import Fastify from "fastify";
import xLogger, { getLoggerOptions } from "xlogger";

const fastify = Fastify({
  logger: getLoggerOptions({
    serviceName: "my-api",
    transport: {
      target: "@logtail/pino",
      options: {
        sourceToken: process.env.BETTERSTACK_SOURCE_TOKEN,
      },
    },
  }),
});

await fastify.register(xLogger, {
  serviceName: "my-api",
});

Multiple Transports

Send logs to both Betterstack and a local file:

const fastify = Fastify({
  logger: getLoggerOptions({
    serviceName: "my-api",
    transport: {
      targets: [
        {
          target: "@logtail/pino",
          options: {
            sourceToken: process.env.BETTERSTACK_SOURCE_TOKEN,
          },
        },
        {
          target: "pino/file",
          options: {
            destination: "/var/log/app.log",
          },
        },
      ],
    },
  }),
});

Environment-Based Configuration

// In development: use pino-pretty (default)
// In production: use Betterstack if token is set, otherwise JSON stdout
const transport = process.env.BETTERSTACK_SOURCE_TOKEN
  ? {
      target: "@logtail/pino",
      options: {
        sourceToken: process.env.BETTERSTACK_SOURCE_TOKEN,
      },
    }
  : undefined;

const fastify = Fastify({
  logger: getLoggerOptions({
    serviceName: "my-api",
    transport,
  }),
});

Note: When a custom transport is provided, it overrides the default environment-based transport configuration (pino-pretty in development, JSON in production).

API Reference

Decorators

| Decorator | Description | |-----------|-------------| | fastify.xlogger.config | Plugin configuration | | fastify.xlogger.extractContext(request) | Extract context from request | | fastify.xlogger.logEvent(params) | Log a business event | | fastify.xlogger.logBoundary(params) | Log an external API call | | fastify.xlogger.createBoundaryLogger(vendor, op, req) | Create timed boundary logger | | fastify.xlogger.createJobContext(params) | Create background job context | | fastify.xlogger.levels | Log level constants | | fastify.xlogger.redactPaths | Configured redact paths | | request.contextLog | Child logger with request context |

logEvent(params)

Log a business event with canonical schema.

fastify.xlogger.logEvent({
  event: "user.created",           // Required: event name
  msg: "User was created",         // Optional: message
  level: "info",                   // Optional: log level (default: "info")
  data: { email: "[email protected]" },  // Optional: additional data
  request,                         // Optional: request for context
});

logBoundary(params)

Log an external API call.

fastify.xlogger.logBoundary({
  vendor: "stripe",        // Required: service name
  operation: "createCustomer",  // Required: operation name
  externalId: "cus_123",   // Optional: external resource ID
  durationMs: 150,         // Optional: call duration
  statusCode: 200,         // Optional: response status
  success: true,           // Optional: success flag (default: true)
  retryCount: 0,           // Optional: number of retries
  metadata: {},            // Optional: additional metadata
  err: null,               // Optional: error if failed
  request,                 // Optional: request for context
});

createBoundaryLogger(vendor, operation, request)

Create a boundary logger with automatic timing.

const boundary = fastify.xlogger.createBoundaryLogger("stripe", "createCustomer", request);

boundary.retry();          // Increment retry counter
boundary.success(params);  // Log success with timing
boundary.fail(err, params); // Log failure with timing

createJobContext(params)

Create a correlation context for background jobs.

const job = fastify.xlogger.createJobContext({
  jobName: "processPayments",  // Required: job name
  requestId: "req_123",        // Optional: original request ID
  orgId: "org_456",            // Optional: organization ID
  userId: "user_789",          // Optional: user ID
  correlationId: "corr_abc",   // Optional: correlation ID (auto-generated if not provided)
});

job.context;       // { correlationId, jobName, orgId, userId, originalRequestId }
job.log;           // Child logger with context
job.start(data);   // Log job started
job.complete(data); // Log job completed
job.fail(err, data); // Log job failed

getLoggerOptions(options)

Get Pino logger options for Fastify initialization.

import { getLoggerOptions } from "xlogger";

const fastify = Fastify({
  logger: getLoggerOptions({
    level: "debug",           // Optional: log level
    serviceName: "my-api",    // Optional: service name
    redactPaths: ["custom"],  // Optional: additional redact paths
    pretty: false,            // Optional: force pretty printing
    transport: {              // Optional: custom transport configuration
      target: "@logtail/pino",
      options: {
        sourceToken: process.env.BETTERSTACK_SOURCE_TOKEN,
      },
    },
  }),
});

Log Levels

| Level | Value | Use For | |-------|-------|---------| | fatal | 60 | Process cannot continue | | error | 50 | Failures requiring attention | | warn | 40 | Recoverable issues | | info | 30 | Business events, normal operations | | debug | 20 | Detailed debugging information | | trace | 10 | Very detailed tracing |

Best Practices

Do

// Log structured objects
fastify.log.info({ userId, action: "login" }, "User logged in");

// Use the canonical schema
fastify.xlogger.logEvent({
  event: "payment.completed",
  data: { amount: 100, currency: "USD" },
  request,
});

// Use boundary logging for external calls
const boundary = fastify.xlogger.createBoundaryLogger("stripe", "charge", request);

Don't

// Don't concatenate strings
fastify.log.info("User " + userId + " logged in"); // Bad!

// Don't log sensitive data (it will be redacted, but still)
fastify.log.info({ password: userPassword }); // Bad!

// Don't log full request/response payloads
fastify.log.info({ body: request.body }); // Usually bad!

Integration with Error Tracking

Use logs for aggregation/search and Sentry for error tracking:

fastify.setErrorHandler((error, request, reply) => {
  // Log the error
  request.contextLog.error({ err: error }, "Request failed");

  // Send to Sentry with context
  Sentry.captureException(error, {
    extra: fastify.xlogger.extractContext(request),
  });

  reply.status(500).send({ error: "Internal Server Error" });
});

Testing

npm test

License

MIT