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

@vtvlive/interactive-apm

v0.0.18

Published

APM integration package supporting both Elastic APM and OpenTelemetry with NestJS integration

Readme

@vtvlive/interactive-apm

APM integration package supporting both Elastic APM and OpenTelemetry with NestJS integration

Features

  • Dual APM Provider Support: Switch between Elastic APM and OpenTelemetry via environment variable or config
  • NestJS Integration: Ready-to-use global module with dependency injection (exposed via the /nestjs entry point)
  • Standalone Usage: Works without NestJS for vanilla TypeScript/JavaScript projects
  • Provider-Agnostic Helper: TracingHelper auto-detects the active provider so the same code works with both
  • Flexible Configuration: Configure via environment variables or programmatic options
  • Debug Mode: Comprehensive logging for troubleshooting connection issues
  • OTLP Transport Options: Support for HTTP, gRPC, and PROTO (protobuf over HTTP) transports
  • Auto Instrumentation (OpenTelemetry standalone): HTTP, Express, and ioredis out of the box
  • TypeScript Support: Fully typed with TypeScript
  • Auto Transaction Naming: HTTP requests automatically named as "METHOD /route/path"

Package Entry Points

The package ships two entry points so that the core tracing API does not pull in @nestjs/common:

| Import path | Use for | Requires @nestjs/common? | | --------------------------------- | ----------------------------------------------------------------------- | -------------------------- | | @vtvlive/interactive-apm | Core API: TracingService, TracingHelper, init functions, factory, types, providers | No | | @vtvlive/interactive-apm/nestjs | Everything above plus the TracingModule NestJS dynamic module | Yes |

⚠️ TracingModule is only exported from @vtvlive/interactive-apm/nestjs. Importing it from the root path will fail.

Installation

Private GitLab Registry Installation

This package is published to the internal GitLab NPM Package Registry for this project.

Create a GitLab deploy token with the read_package_registry scope, then expose the token value as VTVLIVE_NPM_TOKEN. In consumer projects, add an .npmrc file:

@vtvlive:registry=https://source.vtvlive.vn/api/v4/projects/<PROJECT_ID>/packages/npm/
//source.vtvlive.vn/api/v4/projects/<PROJECT_ID>/packages/npm/:_authToken=${VTVLIVE_NPM_TOKEN}
always-auth=true

Replace <PROJECT_ID> with the GitLab project ID for itv/internal/interactive-apm.

Install the package:

npm install @vtvlive/interactive-apm

For GitLab CI consumers, store VTVLIVE_NPM_TOKEN as a masked CI/CD variable before running npm ci or npm install.

Security note: do not commit real NPM or GitLab tokens to .npmrc. Rotate any token that was stored in plaintext locally or committed previously.

See PUBLISH_GITLAB.md and DEPLOYMENT.md for full publishing and installation details.

Peer Dependencies

All APM and NestJS dependencies are declared as optional peer dependencies — install only the ones you need for your chosen provider.

For OpenTelemetry

npm install @vtvlive/interactive-apm \
  @opentelemetry/api \
  @opentelemetry/sdk-node \
  @opentelemetry/sdk-trace-base \
  @opentelemetry/resources \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/instrumentation-http \
  @opentelemetry/instrumentation-express

Optional add-ons:

# gRPC or PROTO transport support
npm install @opentelemetry/exporter-trace-otlp-grpc @opentelemetry/exporter-trace-otlp-proto

# ioredis auto-instrumentation (enabled automatically when present, in standalone init)
npm install @opentelemetry/instrumentation-ioredis

For Elastic APM

npm install @vtvlive/interactive-apm elastic-apm-node

For NestJS Integration

Add the required NestJS dependencies:

npm install @nestjs/common @nestjs/config

Environment Variables

| Variable | Description | Default | | -------------------------------------- | --------------------------------------------------------------------------- | --------------------------------- | | APM_PROVIDER | Provider selection: elastic-apm or opentelemetry | opentelemetry | | APM_DEBUG | Enable debug mode with detailed logging | false | | ELASTIC_APM_SERVICE_NAME | Service name for APM | interactive-backend | | ELASTIC_APM_SERVER_URL | Elastic APM server URL (Elastic APM only) | http://localhost:8200 | | ELASTIC_APM_SECRET_TOKEN | Secret token for authentication | Optional | | ELASTIC_APM_ENVIRONMENT | Deployment environment | development | | ELASTIC_OTLP_ENDPOINT | OTLP endpoint (OpenTelemetry only) | http://localhost:8200/v1/traces | | ELASTIC_OTLP_TRANSPORT | OTLP transport: http, grpc, or proto | http | | ELASTIC_OTLP_AUTH_TOKEN | OTLP-specific auth token (takes precedence over ELASTIC_APM_SECRET_TOKEN) | Optional | | ELASTIC_OTLP_HEADERS | Additional OTLP headers as JSON string | Optional | | ELASTIC_OTLP_ENABLE_CONSOLE_EXPORTER | Enable console exporter for debugging | false |

The OTLP endpoint is normalized automatically: a port and the /v1/traces path are added for HTTP/PROTO, and /v1/traces is stripped for gRPC.

Debug Mode

Enable debug mode to see detailed logs about APM initialization, span creation, and export status:

# Enable debug mode (accepts: 1, true, yes)
APM_DEBUG=true

Debug mode provides:

  • Initialization details (service name, endpoint, transport type)
  • Span creation and lifecycle events
  • Export success/failure status
  • HTTP request/response details for troubleshooting

Usage

NestJS Integration

Import TracingModule from the /nestjs entry point. TracingModule is decorated with @Global(), so TracingService becomes injectable everywhere without re-importing the module.

Complete Example with ConfigService (recommended)

registerAsync automatically initializes the underlying provider (autoInit defaults to true), which starts the OpenTelemetry SDK / Elastic APM agent.

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TracingModule } from '@vtvlive/interactive-apm/nestjs';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.local', '.env'],
    }),

    TracingModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => {
        return {
          // Provider selection
          provider: configService.get<string>('APM_PROVIDER', 'opentelemetry'),

          // Common configuration
          serviceName: configService.get<string>('ELASTIC_APM_SERVICE_NAME', 'my-service'),
          environment: configService.get<string>('ELASTIC_APM_ENVIRONMENT', 'development'),

          // OpenTelemetry configuration
          otlpEndpoint: configService.get<string>('ELASTIC_OTLP_ENDPOINT', 'http://localhost:8200/v1/traces'),
          otlpTransport: configService.get<string>('ELASTIC_OTLP_TRANSPORT', 'http'), // 'http' | 'grpc' | 'proto'
          otlpAuthToken: configService.get<string>('ELASTIC_OTLP_AUTH_TOKEN'),
          otlpHeaders: configService.get('ELASTIC_OTLP_HEADERS'),
          enableConsoleExporter: configService.get<string>('ELASTIC_OTLP_ENABLE_CONSOLE_EXPORTER', 'false') === 'true',

          // Elastic APM configuration
          serverUrl: configService.get<string>('ELASTIC_APM_SERVER_URL', 'http://localhost:8200'),
          secretToken: configService.get<string>('ELASTIC_APM_SECRET_TOKEN'),
        };
      },
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Basic Usage (reads from environment)

// app.module.ts
import { Module } from '@nestjs/common';
import { TracingModule } from '@vtvlive/interactive-apm/nestjs';

@Module({
  imports: [
    TracingModule.registerAsync(),
    // ... other modules
  ],
})
export class AppModule {}

Note: the synchronous TracingModule.register(options) creates the provider but does not call initialize(). Prefer registerAsync() so the SDK/agent is started automatically. If you use register(), initialize the provider yourself before tracing.

In Controllers

import { Controller, Get } from '@nestjs/common';
import { TracingService } from '@vtvlive/interactive-apm/nestjs';

@Controller('healthcheck')
export class HealthController {
  constructor(private readonly tracingService: TracingService) {}

  @Get('ping')
  ping() {
    return this.tracingService.startSpanWithParent(
      'healthcheck.ping',
      async (span) => {
        span?.setAttribute('healthcheck.type', 'liveness');
        return { message: 'pong' };
      },
      { 'http.method': 'GET', 'http.route': '/healthcheck/ping' }
    );
  }
}

In Services

import { Injectable, NotFoundException } from '@nestjs/common';
import { TracingService } from '@vtvlive/interactive-apm/nestjs';

@Injectable()
export class UserService {
  constructor(
    private readonly tracingService: TracingService,
    private readonly userRepository: UserRepository
  ) {}

  async findById(id: string) {
    return this.tracingService.startSpanWithParent(
      'user.findById',
      async (span) => {
        span?.setAttribute('userId', id);

        const user = await this.userRepository.findById(id);
        if (!user) {
          throw new NotFoundException(`User ${id} not found`);
        }

        span?.setAttribute('user.exists', true);
        return user;
      },
      { 'operation.type': 'read' }
    );
  }

  async create(data: CreateUserDto) {
    return this.tracingService.startSpanWithParent(
      'user.create',
      async (span) => {
        span?.setAttribute('user.email', data.email);

        // This nested call creates a child span under the active context
        const hashedPassword = await this.tracingService.startSpanWithParent(
          'user.hashPassword',
          async () => await bcrypt.hash(data.password, 10),
          { 'operation.type': 'crypto' }
        );

        const user = await this.userRepository.create({ ...data, password: hashedPassword });

        // Attach a label to the root transaction span (not the current child span)
        this.tracingService.setTransactionLabel('user.created', true);
        return user;
      },
      { 'operation.type': 'write' }
    );
  }
}

Standalone Usage (without NestJS)

Call the init function at the very top of your entry file, before importing your app code, so auto-instrumentation can hook into http/express/ioredis.

OpenTelemetry

initOpenTelemetry is async and returns the started SDK instance ({ start, shutdown, flush? }). HTTP, Express, and ioredis instrumentation are enabled by default.

import { initOpenTelemetry } from '@vtvlive/interactive-apm';

await initOpenTelemetry({
  serviceName: 'my-service',
  otlpEndpoint: 'http://localhost:8200/v1/traces',
  environment: 'production',
  otlpTransport: 'proto', // 'http' | 'grpc' | 'proto'
  enableConsoleExporter: false,
  // enableHttpInstrumentation: false,    // opt out if needed
  // enableExpressInstrumentation: false,
});

// Your application code
import express from 'express';
const app = express();

Elastic APM

initElasticApm is synchronous and returns the started agent instance.

import { initElasticApm } from '@vtvlive/interactive-apm';

initElasticApm({
  serviceName: 'my-service',
  serverUrl: 'http://localhost:8200',
  environment: 'production',
  secretToken: 'your-secret-token',
});

// Your application code

Using TracingService standalone

Compose a provider via the factory and wrap it with TracingService:

import { createTracingProvider, TracingService } from '@vtvlive/interactive-apm';

const provider = createTracingProvider({ provider: 'opentelemetry', serviceName: 'my-service' });
// OpenTelemetry providers must be initialized before use:
await (provider as any).initialize?.();

const tracing = new TracingService(provider);
await tracing.startSpanWithParent('job.run', async (span) => {
  span?.setAttribute('job.id', '123');
});

Provider-agnostic helper

TracingHelper is a static class that auto-detects the active provider (OpenTelemetry or Elastic APM) and routes to the right API. Useful when you don't have access to DI.

import { TracingHelper, SpanKind } from '@vtvlive/interactive-apm';

await TracingHelper.startSpanWithParent(
  'cache.lookup',
  async (span) => {
    span?.setAttribute('cache.key', key);
    return await cache.get(key);
  },
  { 'operation.type': 'read' },
  SpanKind.CLIENT
);

// Or manage the span manually
const span = TracingHelper.startSpan('my.operation', { key: 'value' }, SpanKind.SERVER);
try {
  // ... code
} finally {
  TracingHelper.endSpan(span);
}

const traceId = TracingHelper.getTraceId();

API Reference

Exports overview

// From '@vtvlive/interactive-apm' (core, no NestJS)
import {
  TracingService,
  TracingHelper,
  createTracingProvider,
  TracingProviderFactory,
  TRACING_PROVIDER_TOKEN,
  OpenTelemetryTracingProvider,
  ElasticApmTracingProvider,
  initOpenTelemetry,
  initElasticApm,
  isElasticApmStarted,
  getElasticApmAgent,
  shouldUseElasticApm,
  shouldUseOpenTelemetry,
  ApmProvider,
  SpanKind,
  OtlpTransport,
} from '@vtvlive/interactive-apm';

// From '@vtvlive/interactive-apm/nestjs' (adds the module)
import { TracingModule } from '@vtvlive/interactive-apm/nestjs';

TracingModule (/nestjs)

NestJS global dynamic module for APM integration.

| Method | Description | | ------------------------ | -------------------------------------------------------------------------------------- | | register(options) | Register with synchronous options. Does not auto-initialize the provider. | | registerAsync(options) | Register with async options (e.g. ConfigService). Auto-initializes when autoInit !== false. |

TracingModuleOptions: provider, serviceName, environment, serverUrl, secretToken, otlpEndpoint, serviceVersion, autoInit.

TracingModuleAsyncOptions: imports, useFactory, inject (returns TracingModuleOptions).

TracingService

Injectable service that wraps the active ITracingProvider.

| Method | Description | | ----------------------------------------------------- | ------------------------------------------------------------------ | | startSpan(name, attributes?, spanKind?) | Start a span (default kind INTERNAL); returns ISpan \| null — call span.end() manually | | startSpanWithParent(name, fn, attributes?, spanKind?) | Run fn inside an auto-closed span (default kind SERVER) — recommended | | captureError(error) | Record an error on the active span | | setAttribute(key, value) | Set an attribute on the active span | | setTransactionLabel(key, value) | Set a label/attribute on the root transaction span | | endSpan(span?, result?) | End a span manually (active span if omitted) | | shutdown() | Flush and shut down the provider |

TracingHelper

Static, provider-agnostic helper (auto-detects provider from APM_PROVIDER or runtime state).

| Method | Description | | ------------------------------------------------------- | ------------------------------------------------- | | startSpan(name, attributes?, spanKind?) | Start a span (returns ISpan) | | startSpanWithParent(name, fn, attributes?, spanKind?) | Run fn inside an auto-closed span | | captureError(error) | Record an error on the active span | | setAttribute(key, value) | Set an attribute on the active span | | setTransactionLabel(key, value) | Set a label on the active transaction | | endSpan(span?, result?) | End a span manually | | getActiveSpan() | Get the current active span | | getTraceId() | Get the current trace ID | | resetProviderCache() | Reset the cached provider (useful for testing) |

Factory & init functions

| Export | Description | | ----------------------------------------------- | --------------------------------------------------------------------------- | | createTracingProvider(config?) | Create an ITracingProvider from config/env. Returns OpenTelemetry or Elastic provider | | TRACING_PROVIDER_TOKEN | DI token ('ITracingProvider') for custom NestJS providers | | TracingProviderFactory | Ready-made NestJS factory provider object | | initOpenTelemetry(options?) | async — start the OpenTelemetry NodeSDK (returns the SDK) | | initElasticApm(options?) | Start the Elastic APM agent (returns the agent) | | isElasticApmStarted() / getElasticApmAgent()| Inspect the Elastic APM agent | | shouldUseElasticApm() / shouldUseOpenTelemetry() | Resolve provider choice from APM_PROVIDER |

SpanKind

Types of spans (used with startSpan / startSpanWithParent):

  • INTERNAL: Internal operation (default for startSpan)
  • SERVER: Server-side request handler (API endpoints) — default for startSpanWithParent
  • CLIENT: Client-side call (outgoing HTTP, database)
  • PRODUCER: Message producer
  • CONSUMER: Message consumer
  • WEBSOCKET: WebSocket communication
  • OTHER: Other custom span kind

OtlpTransport

Transport options for the OpenTelemetry OTLP exporter:

  • HTTP (http): JSON over HTTP (default)
  • GRPC (grpc): gRPC with protobuf
  • PROTO (proto): Protobuf over HTTP (recommended for Elastic APM)

Transaction Naming

Root HTTP SERVER spans are automatically renamed in the format METHOD /route/path:

  • Request to GET /api/healthcheck/ping → Transaction: GET /api/healthcheck/ping
  • Request to POST /api/users → Transaction: POST /api/users

This applies to both OpenTelemetry and Elastic APM providers.

Provider Comparison

| Feature | OpenTelemetry | Elastic APM | | -------------------- | ----------------------------------- | ---------------- | | Transport Options | HTTP, gRPC, PROTO | HTTP/HTTPS | | Auto Instrumentation | HTTP, Express, ioredis (standalone) | Built-in | | Debug Logging | Comprehensive | Comprehensive | | Transaction Naming | Auto + Custom | Auto + Custom | | Standard | W3C Trace Context | Elastic APM spec |

Troubleshooting

No traces appearing in APM

  1. Set APM_DEBUG=true to see detailed logs
  2. Verify the endpoint URL is correct (and reachable)
  3. Confirm the provider was initialized — use TracingModule.registerAsync() or call initialize() / an init function
  4. Verify the authentication token
  5. Make sure the required peer dependencies for your provider/transport are installed

Using npm link for local development

# In the package directory
cd /path/to/interactive-apm
npm link

# In your project directory
cd /path/to/your-project
npm link @vtvlive/interactive-apm

After making changes to the package, rebuild it:

cd /path/to/interactive-apm
npm run build

Testing

npm test                  # Run all tests
npm run test:watch        # Watch mode
npm run test:coverage     # With coverage
npm run test:unit         # Unit tests only
npm run test:integration  # Integration tests only

Development

npm run build         # Build the package (tsc)
npm run build:watch   # Build in watch mode
npm run typecheck     # Type-check without emitting
npm run lint          # Lint src
npm run format        # Format src with Prettier
npm run clean         # Remove dist

License

MIT

Author

VTVLive


For more information: