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

@pawells/nestjs-open-telemetry

v1.1.2

Published

NestJS OpenTelemetry integration with tracing, metrics, and logger adapter

Readme

NestJS OpenTelemetry Module

GitHub Release CI npm version Node License: MIT GitHub Sponsors

OpenTelemetry integration for NestJS applications. Provides distributed tracing with the @Traced decorator, manual span creation helpers, HTTP metrics recording, and a NestJS logger adapter that automatically injects trace context.

Installation

yarn add @pawells/nestjs-open-telemetry

Requirements

  • Node.js: >= 24.0.0
  • NestJS: >= 10.0.0
  • @opentelemetry/api: >= 1.0.0
  • @pawells/nestjs-shared: peer dependency (provides InstrumentationRegistry)

Quick Start

Module Setup

Import OpenTelemetryModule in your root application module. The module must be imported after CommonModule from @pawells/nestjs-shared (which provides the InstrumentationRegistry).

import { Module } from '@nestjs/common';
import { CommonModule } from '@pawells/nestjs-shared';
import { OpenTelemetryModule } from '@pawells/nestjs-open-telemetry';

@Module({
  imports: [
    CommonModule,            // Provides InstrumentationRegistry
    OpenTelemetryModule.forRoot(),  // Registers OpenTelemetry exporter
  ],
})
export class AppModule {}

Using @Traced Decorator

Automatically wrap any method in a distributed tracing span with the @Traced() decorator:

import { Injectable } from '@nestjs/common';
import { Traced, SpanKind } from '@pawells/nestjs-open-telemetry';

@Injectable()
export class UserService {
  // Basic usage — spans are INTERNAL by default
  @Traced()
  async getUserById(userId: string) {
    return await this.db.findUser(userId);
  }

  // Custom span name and attributes
  @Traced({
    name: 'UserService.fetchFromAPI',
    attributes: { 'service.layer': 'business-logic' },
    captureReturn: true,  // Include return value in span
  })
  async fetchUserFromAPI(userId: string) {
    return await this.httpClient.get(`/api/users/${userId}`);
  }

  // CLIENT span for external HTTP calls
  @Traced({
    name: 'getUserDataFromExternalAPI',
    kind: SpanKind.CLIENT,
    attributes: { 'http.method': 'GET' },
  })
  async getDataFromExternal(userId: string) {
    return await fetch(`https://api.example.com/users/${userId}`);
  }

  // SERVER span for request handlers
  @Traced({
    kind: SpanKind.SERVER,
    captureArgs: true,
    captureReturn: true,
  })
  async createUser(dto: CreateUserDto) {
    return await this.db.createUser(dto);
  }
}

@Traced Decorator Options

interface TracedOptions {
  /**
   * Custom span name. Defaults to "ClassName.methodName".
   */
  name?: string;

  /**
   * Span kind. Defaults to SpanKind.INTERNAL.
   * Common values:
   * - SpanKind.INTERNAL: Business logic (default)
   * - SpanKind.CLIENT: External API calls
   * - SpanKind.SERVER: Request handlers
   * - SpanKind.PRODUCER: Message producers
   * - SpanKind.CONSUMER: Message consumers
   */
  kind?: SpanKind;

  /**
   * Additional span attributes to always set.
   */
  attributes?: Record<string, string | number | boolean>;

  /**
   * Capture method arguments as span attributes. Defaults to true.
   * Arguments > 100 chars or complex objects are omitted for security.
   * PII (email, phone, SSN, credit cards) is automatically redacted.
   */
  captureArgs?: boolean;

  /**
   * Capture method return value as span attribute. Defaults to false.
   * Return values > 100 chars or complex objects are omitted for security.
   */
  captureReturn?: boolean;
}

SpanKind Values

Exported from @pawells/nestjs-open-telemetry:

  • INTERNAL (default) — Internal business logic, synchronous operations
  • SERVER — Request handlers, server-side operations
  • CLIENT — External API calls, outbound requests
  • PRODUCER — Message producers
  • CONSUMER — Message consumers

Manual Span Creation

For more control, use the tracing helpers from lib/tracing.ts:

getTracer(name, version?)

Get or create a tracer instance with namespace conventions.

import { getTracer } from '@pawells/nestjs-open-telemetry';

const tracer = getTracer('user-service', '1.2.0');
// Actual tracer name: 'pawells.user-service'

createSpan(tracer, name, options?, makeActive?)

Create a span and optionally set it as active in context.

import { getTracer, createSpan } from '@pawells/nestjs-open-telemetry';

const tracer = getTracer('user-service');
const { span, ctx } = createSpan(tracer, 'getUserById', {
  attributes: { 'user.id': '123' },
});

try {
  // Do work within context
  context.with(ctx, () => {
    // Span is active here
  });
  span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
  span.recordException(error);
  span.setStatus({ code: SpanStatusCode.ERROR });
} finally {
  span.end();
}

withSpan(tracer, name, fn, options?)

Execute a function within a span, automatically handling success/error status and cleanup.

import { getTracer, withSpan } from '@pawells/nestjs-open-telemetry';

const tracer = getTracer('user-service');

// Works with async or sync functions
const user = await withSpan(tracer, 'getUserById', async () => {
  return await db.findUser(userId);
}, {
  attributes: { 'user.id': userId },
});

addAttributes(attributes, ctx?)

Add attributes to the currently active span. Silently no-ops if no span is active.

import { addAttributes } from '@pawells/nestjs-open-telemetry';

addAttributes({
  'user.id': userId,
  'user.role': 'admin',
  'request.method': 'POST',
});

HTTP Metrics

Record HTTP request metrics following OpenTelemetry semantic conventions:

recordHttpMetrics(method, route, statusCode, duration, requestSize?, responseSize?)

Record all HTTP request metrics at once.

import { recordHttpMetrics } from '@pawells/nestjs-open-telemetry';

// In a middleware or HTTP interceptor
recordHttpMetrics(
  'GET',                    // HTTP method
  '/users/:id',            // Route pattern (normalized)
  200,                      // Status code
  45.2,                     // Duration in milliseconds
  0,                        // Optional: request body size in bytes
  1024,                     // Optional: response body size in bytes
);

Metrics recorded (following OpenTelemetry semantic conventions):

  • http.server.request.count — Total HTTP requests
  • http.server.request.duration — Request duration histogram
  • http.server.request.size — Request body size histogram
  • http.server.response.size — Response body size histogram

trackActiveRequests(delta, attributes?)

Track the number of active HTTP requests. Call with +1 when a request starts, -1 when it completes.

import { trackActiveRequests } from '@pawells/nestjs-open-telemetry';

// Request started
trackActiveRequests(1, { method: 'GET' });

// Later, request completed
trackActiveRequests(-1, { method: 'GET' });

Metric recorded:

  • http.server.active_requests — UpDownCounter tracking active requests

OpenTelemetryLogger Adapter

Use OpenTelemetryLogger as your NestJS logger to automatically inject trace context (trace_id, span_id) into all logs.

import { NestFactory } from '@nestjs/core';
import { OpenTelemetryLogger } from '@pawells/nestjs-open-telemetry';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: new OpenTelemetryLogger({
      service: 'my-app',      // Optional: service name
      level: 'info',          // Optional: log level
      format: 'json',         // Optional: log format
    }),
  });
  await app.listen(3000);
}

bootstrap();

The logger automatically adds OpenTelemetry context to each log:

{
  "timestamp": "2024-01-15T10:30:00.000Z",
  "level": "info",
  "message": "Request completed",
  "trace_id": "a1b2c3d4e5f6g7h8...",
  "span_id": "x1y2z3a4b5c6d7e8",
  "trace_flags": "01",
  "service": "my-app"
}

OpenTelemetryExporter

The OpenTelemetryExporter is automatically registered by the module and implements the IMetricsExporter interface from @pawells/nestjs-shared.

It converts metrics recorded through the InstrumentationRegistry to OpenTelemetry instruments:

  • counter → OpenTelemetry Counter
  • histogram → OpenTelemetry Histogram
  • gauge → OpenTelemetry UpDownCounter (push-based gauge semantics)
  • updown_counter → OpenTelemetry UpDownCounter

Typically you don't interact with the exporter directly; the module handles registration automatically.

Architecture

The module integrates with NestJS and OpenTelemetry as follows:

  1. @Traced Decorator — Wraps methods in spans with automatic error handling and PII redaction
  2. Tracing Helpers — Provide low-level span creation and context management
  3. HTTP Metrics — Record request latency, status, and size metrics
  4. OpenTelemetryExporter — Converts application metrics to OpenTelemetry instruments
  5. OpenTelemetryLogger — Injects trace context into all logs
  6. InstrumentationRegistry Integration — Works with @pawells/nestjs-shared to export metrics

Configuration Reference

OpenTelemetryModule.forRoot()

The module is initialized with no required configuration:

@Module({
  imports: [
    OpenTelemetryModule.forRoot(),
  ],
})
export class AppModule {}

Testing Utilities

The package exports two internal helper functions for test isolation and cleanup:

resetTracerNamespace()

Resets the tracer namespace to its default value ('pawells'). Used in test teardown to ensure namespace isolation between tests.

import { resetTracerNamespace } from '@pawells/nestjs-open-telemetry';

afterEach(() => {
  resetTracerNamespace();  // Clean up for next test
});

resetHttpMetrics()

Resets the cached HTTP metrics to null, forcing re-initialization on next access. Used in test teardown to ensure metric state isolation between tests.

import { resetHttpMetrics } from '@pawells/nestjs-open-telemetry';

afterEach(() => {
  resetHttpMetrics();  // Clean up for next test
});

Re-exports

The package re-exports useful types from OpenTelemetry for convenience:

import {
  Span,
  SpanContext,
  Attributes,
  SpanKind,
} from '@pawells/nestjs-open-telemetry';

// Also available from @opentelemetry/api

Logger configuration types:

import {
  ILoggerConfig,
  LogLevel,
} from '@pawells/nestjs-open-telemetry';

// Also available from @pawells/logger

Security Features

PII Redaction

The @Traced decorator automatically detects and redacts Personally Identifiable Information (PII):

  • Email addresses[REDACTED_EMAIL]
  • Phone numbers[REDACTED_PHONE]
  • Social Security Numbers (SSN)[REDACTED_SSN]
  • Credit card numbers (Luhn-validated) → [REDACTED_CREDIT_CARD]

Argument Sanitization

  • Arguments longer than 100 characters are truncated
  • Complex objects are summarized (e.g., Object(5 keys) instead of stringified)
  • Arrays > 5 items are summarized
  • Null/undefined are converted to strings

Return Value Redaction

Return values are only captured if explicitly enabled with captureReturn: true, and the same sanitization rules apply.

Integration with Other Packages

Examples

Complete Service Example

import { Injectable } from '@nestjs/common';
import { Traced, SpanKind, getTracer, withSpan, addAttributes } from '@pawells/nestjs-open-telemetry';

@Injectable()
export class OrderService {
  constructor(private db: Database, private httpClient: HttpClient) {}

  // Automatic tracing with decorator
  @Traced({
    name: 'OrderService.getOrder',
    kind: SpanKind.INTERNAL,
  })
  async getOrder(orderId: string) {
    return await this.db.orders.findById(orderId);
  }

  // Manual span management with helpers
  async processOrder(orderId: string) {
    const tracer = getTracer('order-service');

    return withSpan(tracer, 'processOrder', async () => {
      addAttributes({
        'order.id': orderId,
        'operation': 'process',
      });

      const order = await this.getOrder(orderId);
      await this.validateOrder(order);
      await this.chargePayment(order);
      await this.shipOrder(order);

      return order;
    }, {
      attributes: { 'order.id': orderId },
    });
  }

  // CLIENT span for external calls
  @Traced({
    kind: SpanKind.CLIENT,
    name: 'ExternalPaymentAPI.charge',
  })
  private async chargePayment(order: Order) {
    return await this.httpClient.post('https://payment.example.com/charge', {
      orderId: order.id,
      amount: order.total,
    });
  }

  @Traced()
  private async validateOrder(order: Order) {
    addAttributes({ 'validation.status': 'passed' });
    return true;
  }

  @Traced()
  private async shipOrder(order: Order) {
    addAttributes({ 'shipping.method': 'standard' });
  }
}

HTTP Middleware Example

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { recordHttpMetrics, trackActiveRequests } from '@pawells/nestjs-open-telemetry';

@Injectable()
export class HttpMetricsMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    trackActiveRequests(1, { method: req.method });
    const startTime = Date.now();

    res.on('finish', () => {
      const duration = Date.now() - startTime;
      recordHttpMetrics(
        req.method,
        req.route?.path || req.path,
        res.statusCode,
        duration,
        req.get('content-length') ? parseInt(req.get('content-length')!, 10) : 0,
        res.get('content-length') ? parseInt(res.get('content-length')!, 10) : 0,
      );
      trackActiveRequests(-1, { method: req.method });
    });

    next();
  }
}

Troubleshooting

Spans not appearing in traces

Ensure:

  1. OpenTelemetry SDK is initialized before your NestJS app
  2. OpenTelemetryModule is imported after CommonModule
  3. A span exporter is configured in your OpenTelemetry SDK

PII redaction not working

PII redaction only applies to method arguments when captureArgs: true (default). If you're seeing PII in custom attributes, redact them manually:

@Traced({
  attributes: {
    'user.email': sanitizeEmail(email),  // Redact manually
  },
})

No trace context in logs

Ensure OpenTelemetryLogger is set as the NestJS logger during app creation:

const app = await NestFactory.create(AppModule, {
  logger: new OpenTelemetryLogger(),
});

License

MIT