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

@highwaydelite/logger

v0.1.3

Published

Standardized structured logging for NestJS services (Pino + OpenTelemetry, CloudWatch-ready).

Readme

@highwaydelite/logger

Standardized structured logging for Highway Delite NestJS services.

Built on nestjs-pino + Pino, with OpenTelemetry trace correlation and a CloudWatch-friendly output format. Every service should log through @highwaydelite/logger so that field names, redaction, and correlation behave identically everywhere.


Why this exists

If each team configures Pino itself, formats drift and dashboards break. This package bakes the agreed decisions into one versioned dependency: bump the version and the standard propagates. Do not hand-roll a Pino config in a service.


Install

npm install @highwaydelite/logger

# peer deps (most services already have the Nest ones):
npm install @nestjs/common @nestjs/core nestjs-pino pino @opentelemetry/api

Quick start

// app.module.ts
import { Module } from '@nestjs/common';
import { LoggerModule } from '@highwaydelite/logger';

@Module({
  imports: [
    LoggerModule.forRoot({ service: 'orders-api' }),
    // ...your modules
  ],
})
export class AppModule {}
// main.ts
import { NestFactory } from '@nestjs/core';
import { useSharedLogger } from '@highwaydelite/logger';
import { AppModule } from './app.module';

async function bootstrap() {
  // bufferLogs => early bootstrap logs flush through the shared logger
  const app = await NestFactory.create(AppModule, { bufferLogs: true });
  useSharedLogger(app);
  await app.listen(3000);
}
bootstrap();

That's it. Incoming requests and completed responses are logged automatically, each line carries the correlation id and (when a span is active) the trace context, and sensitive fields are redacted.

Logging in your code

Inject the typed logger and set the class name as context. Typed application logs must include operation and phase; extra JSON-safe fields are allowed.

import { Injectable } from '@nestjs/common';
import { InjectTypedLogger, TypedLogger } from '@highwaydelite/logger';

@Injectable()
export class OrderService {
  constructor(
    @InjectTypedLogger(OrderService.name) private readonly log: TypedLogger,
  ) {}

  async createOrder(dto: CreateOrderDto) {
    this.log.debug(
      {
        operation: 'createOrder',
        phase: 'validate',
        itemCount: dto.items.length,
      },
      'validating order payload',
    );
    // ...
    this.log.info(
      { operation: 'createOrder', phase: 'persisted', orderId },
      'order persisted',
    );
  }
}

PinoLogger / InjectPinoLogger remain exported as raw compatibility escape hatches for advanced Pino-specific cases. Prefer TypedLogger for application logs so schema drift is caught by TypeScript.


Configuration

LoggerModule.forRoot(options) — all options are optional:

| Option | Default | Notes | |----------------------|----------------------------------------|-------| | service | process.env.SERVICE_NAME | Service name, e.g. orders-api. | | env | process.env.NODE_ENV ?? development | Deployment environment. | | version | process.env.SERVICE_VERSION | Build/version stamp. | | level | process.env.LOG_LEVEL ?? info/debug | info in prod, debug otherwise. | | pretty | true outside production | Human-readable console output. Never enable in prod. | | redact | [] | Added to the baseline redaction; cannot remove it. | | autoLogging | true | Emits pino-http request lifecycle logs. Set false to disable them globally. | | excludeRequestLogs | [] | Suppresses automatic request lifecycle logs for matching paths. |

Recommended environment variables per service:

SERVICE_NAME=orders-api
SERVICE_VERSION=1.8.2     # inject your build sha/tag here
NODE_ENV=production
LOG_LEVEL=info

Request Log Suppression

Use excludeRequestLogs for high-volume probe endpoints such as health checks. This suppresses only the automatic pino-http lifecycle line; application logs inside a handler still emit normally.

LoggerModule.forRoot({
  service: 'orders-api',
  excludeRequestLogs: [
    '/api/v1/health',
    '/api/v1/ready',
    '/api/v1/readiness',
    /^\/internal\/probes\//,
    (req) => req.headers['user-agent'] === 'ELB-HealthChecker/2.0',
  ],
});

String matchers are exact path matches, ignore query strings, and may be provided with or without the leading slash.


The standard log schema

Every line carries these fields:

| Field | Source | Meaning | |--------------|-------------------------|---------| | time | auto (ISO 8601) | Event timestamp. | | level | auto (string in prod) | trace/debug/info/warn/error/fatal. | | service | base | Which service emitted the line. | | env | base | Environment. | | version | base | Service build/version. | | reqId | genReqId | Per-request correlation id (see below). | | trace_id | OTel mixin | Distributed trace id. Present when a span is active. | | span_id | OTel mixin | Current span id. | | context | your logger | Class name that emitted the line. | | operation | typed logger | Method/workflow name that emitted the line. | | phase | typed logger | Lifecycle point within the operation. | | msg | you | The event. | | req/res | auto (lifecycle line) | Method, url, status, response time. |

Field conventions

  • context is the class, always. Never put Class.method here — keep it consistent so filter context = "OrderService" always works.
  • operation is required for typed application logs. Use the method or workflow name, e.g. createOrder.
  • phase is required for typed application logs. Use a short lifecycle point, e.g. validate, persisted, or failed.
  • For timing a method, prefer giving it an OTel span named Class.method.
  • Do not log request/response bodies by default. Bodies are a PII and a cost risk in CloudWatch. Log them only on errors or behind an explicit debug flag.

reqId vs trace_id

  • reqId ties together one service's handling of a request. It's the same across services only if you forward the x-request-id header on outgoing internal calls (the header name is exported as REQUEST_ID_HEADER).
  • trace_id ties together the entire distributed lifecycle and propagates automatically via OpenTelemetry. Use trace_id as your cross-service join key.

OpenTelemetry

Start the real OTel SDK before importing Nest application code:

import { startOpenTelemetry, useSharedLogger } from '@highwaydelite/logger';

startOpenTelemetry({ service: 'orders-api' });

async function bootstrap() {
  const { NestFactory } = await import('@nestjs/core');
  const { AppModule } = await import('./app.module.js');

  const app = await NestFactory.create(AppModule, { bufferLogs: true });
  useSharedLogger(app);
  await app.listen(3000);
}
bootstrap();

trace_id / span_id are injected from the active span. The logger module creates a request-scoped non-recording span context automatically, so request logs include trace fields even when the service has not bootstrapped a full OTel SDK. If an inbound traceparent header is present, its trace id is preserved; otherwise a new trace id is generated for the request.

startOpenTelemetry() initializes the SDK with Node auto-instrumentations. Spans are not exported to stdout by default. To opt into console span output for local debugging, pass exporter: 'console' or set OTEL_TRACES_EXPORTER=console. To export to a collector over OTLP/HTTP, set OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, or pass exporter: 'otlp-http' in code.

With HTTP/DB auto-instrumentation, outgoing internal calls get their own spans and propagate the trace context for you — so let traces own the call graph and timing, and let logs own the rich event detail. They join on trace_id.

If you prefer automatic injection, register @opentelemetry/instrumentation-pino and it does the same job — in that case this package's mixin is harmless but redundant.


CloudWatch Logs Insights cookbook

Replay one request's full lifecycle across all services (select the relevant log groups, then):

fields @timestamp, service, context, operation, level, msg
| filter trace_id = "4bf92f3577b34da6a3ce929d0e0e4736"
| sort @timestamp asc

Everything one service did for a request:

fields @timestamp, context, operation, msg
| filter reqId = "0b9e1c4a-7f2d-4c1e-9b3a-2d6f8e0a1c44"
| sort @timestamp asc

All warnings/errors for a service in the window:

fields @timestamp, reqId, trace_id, context, operation, msg
| filter level in ["warn", "error", "fatal"]
| sort @timestamp desc

Slowest requests (from the auto lifecycle line):

fields @timestamp, reqId, req.url, res.statusCode, responseTime
| filter ispresent(responseTime)
| sort responseTime desc
| limit 50

Everything a specific method did:

fields @timestamp, reqId, trace_id, msg
| filter context = "OrderService" and operation = "createOrder"
| sort @timestamp asc

Redaction

The baseline paths in DEFAULT_REDACT_PATHS (auth headers, cookies, api keys, common credential body fields) are always removed and cannot be disabled. Add service-specific paths via the redact option:

LoggerModule.forRoot({
  service: 'orders-api',
  redact: ['req.body.cardNumber', 'res.body.ssn'],
});

Local development

pretty defaults to on outside production, giving readable colorized output via pino-pretty. In production, leave it off: the service writes raw NDJSON to stdout and the platform log driver (ECS/EKS/Lambda) ships it to CloudWatch.