1.1.0 • Published 5 months ago

@middleware.io/nestjs-apm v1.1.0

Weekly downloads
-
License
ISC
Repository
github
Last release
5 months ago

Middleware NestJS APM

Introduction

@middleware.io/nestjs-apm is the official Middleware APM client for NestJS applications that automatically instruments your application with OpenTelemetry, sending runtime metrics, traces/spans, and console logs to Middleware.io.

Installation

npm install @middleware.io/nestjs-apm

Usage

Import the MiddlewareApmModule in your app.module.ts:

import { MiddlewareApmModule } from "@middleware.io/nestjs-apm";

@Module({
  imports: [
    MiddlewareApmModule.forRoot({
      projectName: "Your application name",
      serviceName: "Your service name",
    }),
    // ... other modules
  ],
})
export class AppModule {}

Features

  • Automatic instrumentation of NestJS controllers and services
  • Console log capture (info, warn, error)
  • Distributed tracing
  • Performance metrics
  • Error tracking
  • Custom span attributes
  • Advanced instrumentation decorators
  • OpenTelemetry log integration
  • Exception tracking with OTEL events

Basic Usage

Ignoring Routes

You can use the @IgnoreApmTrace() decorator to exclude specific routes from OpenTelemetry tracing and prevent them from being exported:

import { IgnoreApmTrace } from "@middleware.io/nestjs-apm";

@Controller("users")
export class UsersController {
  // This endpoint will not create or export any OpenTelemetry traces
  @IgnoreApmTrace()
  @Get("health")
  healthCheck() {
    return "OK";
  }

  // This endpoint will still be traced normally
  @Get(":id")
  getUser(@Param("id") id: string) {
    return { id, name: "John Doe" };
  }
}

The @IgnoreApmTrace() decorator can be applied to individual methods or entire controllers:

// Ignore tracing for the entire controller
@IgnoreApmTrace()
@Controller("internal")
export class InternalController {
  @Get("status")
  getStatus() {
    return "Internal status";
  }

  @Get("metrics")
  getMetrics() {
    return "Internal metrics";
  }
}

Alternative: Manual Route Registration

You can also manually register routes to be ignored using the registerIgnoredRoutes function:

import { registerIgnoredRoutes } from "@middleware.io/nestjs-apm";

// In your application initialization
registerIgnoredRoutes([
  '/health',
  '/metrics', 
  '/status',
  '/users/:id/health', // Routes with parameters
  '/internal/*'        // Wildcard patterns
]);

This approach is useful when you want to:

  • Configure ignored routes in one central location
  • Ignore routes that don't use decorators
  • Set up ignored routes during application bootstrap

⚠️ Performance Recommendation

For production applications, we recommend using registerIgnoredRoutes() instead of @IgnoreApmTrace() for better performance.

Why registerIgnoredRoutes() is more efficient:

  • Prevents span creation entirely at the HTTP instrumentation level
  • Lower CPU overhead - no reflection or span manipulation needed
  • Better for high-traffic routes like health checks and metrics endpoints
  • Earlier filtering - operates before NestJS request processing

When to use @IgnoreApmTrace():

  • For fine-grained, method-level control
  • When ignored routes are not high-traffic
  • For development or low-traffic scenarios
// ✅ Recommended for production (better performance)
registerIgnoredRoutes(['/health', '/metrics', '/status']);

// ⚠️ Use sparingly for high-traffic routes
@IgnoreApmTrace()
@Get('health')
healthCheck() { ... }

Advanced Instrumentation

Custom Attributes

Add custom attributes to spans:

import { WithAttributes } from "@middleware.io/nestjs-apm";

@Controller("orders")
export class OrdersController {
  @WithAttributes({ "business.type": "order", "business.tier": "premium" })
  @Post()
  createOrder() {
    // Your code here
  }
}

Custom Spans

Create custom spans with specific names and attributes:

import { CreateSpan } from "@middleware.io/nestjs-apm";

@Injectable()
export class UserService {
  @CreateSpan("user.registration", { "user.type": "new" })
  async registerUser(userData: any) {
    // Your code here
  }
}

Parameter Recording

Automatically record method parameters as span attributes:

import { RecordParams } from "@middleware.io/nestjs-apm";

@Controller("users")
export class UsersController {
  @RecordParams(["userId", "action"])
  @Post(":userId/action")
  performAction(userId: string, action: string) {
    // Parameters will be recorded as span attributes
  }
}

Logging Integration

The module automatically records all NestJS logger output to OpenTelemetry. Just use the standard NestJS logger:

import { Logger, Injectable } from "@nestjs/common";

@Injectable()
export class UserService {
  private readonly logger = new Logger(UserService.name);

  async createUser(userData: any) {
    try {
      this.logger.log("Creating new user", { userId: userData.id });
      // ... user creation logic
    } catch (error) {
      this.logger.error("Failed to create user", error);
      throw error;
    }
  }
}

You can combine multiple decorators for comprehensive instrumentation:

@Controller("payments")
export class PaymentsController {
  @CreateSpan("payment.process")
  @WithAttributes({ "payment.type": "credit-card" })
  @RecordParams(["amount", "currency"])
  async processPayment(amount: number, currency: string) {
    // Your code here
  }
}

Configuration

The MiddlewareApmModule accepts various configuration options to customize the APM behavior:

@Module({
  imports: [
    MiddlewareApmModule.forRoot({
      projectName: "Your application name",
      serviceName: "Your service name",
      
      // Optional configuration options
      enableFsInstrumentation: false,  // Enable filesystem instrumentation (disabled by default for performance)
      consoleLog: false,               // Capture console.log outputs
      consoleError: true,              // Capture console.error outputs
      enableSelfInstrumentation: false, // Enable self-instrumentation
      consoleExporter: false,          // Export to console instead of OTLP
      disabledInstrumentations: "",    // Comma-separated list of instrumentations to disable
      customResourceAttributes: {},    // Custom resource attributes
      // ... other options
    }),
  ],
})
export class AppModule {}

Environment Variables

You can also configure the module using environment variables:

Environment VariableConfig OptionDescriptionDefault
MW_FS_INSTRUMENTATIONenableFsInstrumentationEnable filesystem instrumentationfalse
MW_SELF_INSTRUMENTATIONenableSelfInstrumentationEnable self-instrumentationfalse
MW_CONSOLE_EXPORTERconsoleExporterExport to console instead of OTLPfalse
MW_APM_TRACES_ENABLEDpauseTracesEnable/disable trace collectiontrue
MW_APM_METRICS_ENABLEDpauseMetricsEnable/disable metrics collectiontrue
MW_API_KEYaccessTokenMiddleware API key-
MW_SERVICE_NAMEserviceNameService name-
MW_PROJECT_NAMEprojectNameProject name-
MW_TARGETtargetOTLP endpoint URLhttp://localhost:9319

Filesystem Instrumentation

⚠️ Performance Warning: Filesystem instrumentation is disabled by default as it can have a severe impact on application performance, especially in I/O-intensive applications.

To enable filesystem instrumentation:

Via configuration object:

MiddlewareApmModule.forRoot({
  // ... other config
  enableFsInstrumentation: true
})

Via environment variable:

export MW_FS_INSTRUMENTATION=true

Only enable this if you specifically need to trace filesystem operations and are aware of the potential performance implications.