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

otel-tracing-channel

v0.2.0

Published

A fixed wrapper around Node.js's tracingChannel that properly propagates otel context

Downloads

30

Readme

otel-tracing-channel

A lightweight wrapper around Node.js's tracingChannel that properly propagates OpenTelemetry context.

The Problem

Node.js's native tracingChannel doesn't automatically propagate OpenTelemetry context between the start event and the callback execution. This breaks distributed tracing when using diagnostic channels.

While creating spans works fine, the parent-child relationship between spans is broken - traces get created as siblings rather than children, which can paint a misleading picture for end users.

The Solution

This package solves the problem by binding OpenTelemetry's internal AsyncLocalStorage to the tracing channel's start event using bindStore. This ensures that:

  • The OpenTelemetry context is automatically propagated throughout the traced operation
  • Parent-child span relationships are maintained correctly

Installation

npm install otel-tracing-channel

Usage

Basic Example

import { tracingChannel } from 'otel-tracing-channel';
import { trace } from '@opentelemetry/api';

// Create a channel with a transform function that creates your span
const channel = tracingChannel('my-operation', (data) => {
  // Create and return a span from the channel data
  const span = trace.getTracer('my-app').startSpan('my-operation', {
    attributes: {
      userId: data.userId,
      // ... other attributes from data
    },
  });

  // Return OTEL Span
  return span;
});

// Subscribe to events to handle span lifecycle
channel.subscribe({
  asyncEnd(data) {
    // The span is available on data.span
    data.span?.end();
  },
  error(data) {
    data.span?.recordException(data.error);
    data.span?.end();
  },
});

await channel.tracePromise(
  async () => {
    // Your async work - OpenTelemetry context is properly propagated
    await doSomething();
  },
  { userId: '123' },
);

With Sentry

import { tracingChannel } from 'otel-tracing-channel';
import * as Sentry from '@sentry/node';

const channel = tracingChannel('database:query', (data) => {
  return Sentry.startSpanManual(
    {
      name: 'db.query',
      op: 'db',
      attributes: {
        'db.statement': data.query,
        'db.system': 'postgresql',
      },
    },
    (span) => span,
  );
});

channel.subscribe({
  asyncEnd: (data) => {
    data.span?.end();
  },
});

// Execute with automatic context propagation
await channel.tracePromise(
  async () => {
    return await db.query('SELECT * FROM users');
  },
  { query: 'SELECT * FROM users' },
);

Wrapping Existing Channels

You can also wrap existing TracingChannel instances:

import { tracingChannel as nativeTracingChannel } from 'node:diagnostics_channel';
import { tracingChannel } from 'otel-tracing-channel';

const existingChannel = nativeTracingChannel('my-channel');
const wrappedChannel = tracingChannel(existingChannel, (data) =>
  createMySpan(data),
);

API

tracingChannel<TData>(channelNameOrInstance, transformStart)

Creates or wraps a tracing channel with OpenTelemetry context propagation.

Parameters:

  • channelNameOrInstance: Either a string channel name or an existing TracingChannel instance
  • transformStart: A function that receives the channel data and returns an OpenTelemetry Span

Returns: A TracingChannel instance with OTel context binding

The transformStart function is called during the start event and:

  • Receives the channel data as its parameter
  • Should create and return an OpenTelemetry Span
  • The returned span is automatically stored on data.span for access in event handlers
  • The span's context is automatically propagated throughout the traced operation

TracingChannelTransform<TData>

Type definition for the transform function:

type TracingChannelTransform<TData = any> = (data: TData) => Span;

channel.subscribe(subscribers)

Subscribe to channel events. All handlers are optional:

  • start(data) - Called when operation starts
  • asyncStart(data) - Called for async operations
  • asyncEnd(data) - Called when async operation ends (good place to end spans)
  • end(data) - Called when operation ends
  • error(data) - Called on errors (access error via data.error)

The span created in transformStart is available as data.span in all handlers.

channel.tracePromise(fn, context, ...args)

Execute an async function with tracing. Context is properly propagated.

channel.traceSync(fn, context, ...args)

Execute a sync function with tracing. Context is properly propagated.

Debug Logging

Enable debug logs to see what's happening under the hood:

import { setDebugFlag } from 'otel-tracing-channel';

setDebugFlag(true); // Enable debug logs
setDebugFlag(false); // Disable debug logs

Debug logs will show:

  • Whether OpenTelemetry AsyncLocalStorage was found
  • When spans are created in the transform
  • When context is stored in AsyncLocalStorage

How It Works

Under the hood, this package:

  1. Accesses OpenTelemetry's internal AsyncLocalStorage instance via context._getContextManager()
  2. Binds it to the channel's start event using bindStore
  3. In the transform function:
    • Calls your transformStart to create the span
    • Stores the span on data.span for handler access
    • Wraps the span in an OTel context
    • Returns the context to be stored in AsyncLocalStorage

This ensures the OpenTelemetry context (and your span) is active throughout the entire traced operation.

Graceful Degradation

If OpenTelemetry context is not available (e.g., no SDK initialized), the library:

  • Logs a debug message (if debug logging is enabled)
  • Returns the channel without OTel binding
  • The channel still works normally, just without automatic context propagation

TypeScript Support

Full TypeScript support with generics for channel data:

interface QueryData {
  query: string;
  params: any[];
}

const channel = tracingChannel<QueryData>('db:query', (data) => {
  // data is typed as QueryData
  return createSpan(data.query, data.params);
});

Publishing

This package uses npm Trusted Publishers with GitHub Actions. No npm tokens required!

Version Options:

  • as-is - Publish current version in package.json (no auto-bump)
  • patch - Bug fixes (0.1.0 → 0.1.1)
  • minor - New features (0.1.0 → 0.2.0)
  • major - Breaking changes (0.1.0 → 1.0.0)

You can manually edit package.json version and use as-is, or let the workflow bump it automatically.

License

Apache-2.0