CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-opentelemetry--instrumentation-http

OpenTelemetry instrumentation for Node.js HTTP and HTTPS modules enabling automatic telemetry collection for client and server operations

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

hook-functions.mddocs/

Hook Functions

Hook functions provide custom logic for filtering requests and adding attributes during HTTP instrumentation lifecycle events. These functions allow fine-grained control over which requests are instrumented and what telemetry data is collected.

Capabilities

Request Filtering Functions

Functions used to determine whether incoming or outgoing requests should be instrumented.

/**
 * Function to determine if an incoming request should be ignored by instrumentation
 * @param request - The incoming HTTP request
 * @returns true to ignore the request, false to instrument it
 */
interface IgnoreIncomingRequestFunction {
  (request: IncomingMessage): boolean;
}

/**
 * Function to determine if an outgoing request should be ignored by instrumentation
 * @param request - The outgoing HTTP request options
 * @returns true to ignore the request, false to instrument it
 */
interface IgnoreOutgoingRequestFunction {
  (request: RequestOptions): boolean;
}

Usage Examples:

import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import type { IncomingMessage, RequestOptions } from "http";

const instrumentation = new HttpInstrumentation({
  // Ignore specific incoming request patterns
  ignoreIncomingRequestHook: (request: IncomingMessage) => {
    const url = request.url || '';
    const userAgent = request.headers['user-agent'] || '';
    
    // Ignore health checks, metrics endpoints, and bot traffic
    return url.startsWith('/health') ||
           url.startsWith('/metrics') ||
           url.startsWith('/_internal') ||
           userAgent.includes('bot') ||
           userAgent.includes('crawler');
  },
  
  // Ignore specific outgoing request patterns
  ignoreOutgoingRequestHook: (request: RequestOptions) => {
    const hostname = request.hostname || request.host || '';
    const path = request.path || '';
    
    // Ignore internal services and health checks
    return hostname.includes('internal.company.com') ||
           hostname === 'localhost' ||
           hostname === '127.0.0.1' ||
           path.includes('/health');
  }
});

Custom Attribute Functions

Functions used to add custom attributes to spans at different points in the request lifecycle.

/**
 * Function for adding custom attributes to spans after both request and response are processed
 * @param span - The current span
 * @param request - The HTTP request (ClientRequest for outgoing, IncomingMessage for incoming)
 * @param response - The HTTP response (IncomingMessage for outgoing, ServerResponse for incoming)
 */
interface HttpCustomAttributeFunction {
  (
    span: Span,
    request: ClientRequest | IncomingMessage,
    response: IncomingMessage | ServerResponse
  ): void;
}

/**
 * Function for adding custom attributes to spans during request processing
 * @param span - The current span
 * @param request - The HTTP request (ClientRequest for outgoing, IncomingMessage for incoming)
 */
interface HttpRequestCustomAttributeFunction {
  (span: Span, request: ClientRequest | IncomingMessage): void;
}

/**
 * Function for adding custom attributes to spans during response processing
 * @param span - The current span
 * @param response - The HTTP response (IncomingMessage for outgoing, ServerResponse for incoming)
 */
interface HttpResponseCustomAttributeFunction {
  (span: Span, response: IncomingMessage | ServerResponse): void;
}

Usage Examples:

import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import type { Span } from "@opentelemetry/api";
import type { ClientRequest, IncomingMessage, ServerResponse } from "http";

const instrumentation = new HttpInstrumentation({
  // Add custom attributes during request processing
  requestHook: (span: Span, request: ClientRequest | IncomingMessage) => {
    // Common attributes for both incoming and outgoing requests
    const userAgent = request.headers['user-agent'];
    const contentType = request.headers['content-type'];
    const requestId = request.headers['x-request-id'];
    
    if (userAgent) {
      span.setAttribute('http.user_agent.original', userAgent);
      span.setAttribute('http.user_agent.is_mobile', userAgent.includes('Mobile'));
    }
    
    if (contentType) {
      span.setAttribute('http.request.content_type', contentType);
    }
    
    if (requestId) {
      span.setAttribute('http.request.id', requestId);
    }
    
    // Differentiate between incoming and outgoing requests
    if ('method' in request) {
      // This is an IncomingMessage (incoming request)
      span.setAttribute('http.direction', 'incoming');
      span.setAttribute('http.client.ip', request.socket.remoteAddress || '');
    } else {
      // This is a ClientRequest (outgoing request)
      span.setAttribute('http.direction', 'outgoing');
    }
  },
  
  // Add custom attributes during response processing
  responseHook: (span: Span, response: IncomingMessage | ServerResponse) => {
    const contentLength = response.headers['content-length'];
    const cacheControl = response.headers['cache-control'];
    const server = response.headers['server'];
    
    if (contentLength) {
      span.setAttribute('http.response.body.size', parseInt(contentLength));
    }
    
    if (cacheControl) {
      span.setAttribute('http.response.cache_control', cacheControl);
    }
    
    if (server) {
      span.setAttribute('http.response.server', server);
    }
    
    // Add custom performance categorization
    if ('statusCode' in response && response.statusCode) {
      const category = response.statusCode < 300 ? 'success' :
                      response.statusCode < 400 ? 'redirect' :
                      response.statusCode < 500 ? 'client_error' : 'server_error';
      span.setAttribute('http.response.category', category);
    }
  },
  
  // Add comprehensive attributes after complete request/response cycle
  applyCustomAttributesOnSpan: (
    span: Span,
    request: ClientRequest | IncomingMessage,
    response: IncomingMessage | ServerResponse
  ) => {
    // Calculate request processing time
    const endTime = Date.now();
    const startTime = span.startTime[0] * 1000 + span.startTime[1] / 1_000_000;
    const duration = endTime - startTime;
    
    // Add performance categories
    span.setAttribute('http.duration.category', 
      duration < 50 ? 'very_fast' :
      duration < 200 ? 'fast' :
      duration < 1000 ? 'moderate' :
      duration < 5000 ? 'slow' : 'very_slow'
    );
    
    // Add size categories for responses
    const contentLength = response.headers['content-length'];
    if (contentLength) {
      const size = parseInt(contentLength);
      span.setAttribute('http.response.size.category',
        size < 1024 ? 'small' :
        size < 102400 ? 'medium' :
        size < 1048576 ? 'large' : 'very_large'
      );
    }
  }
});

Span Start Attribute Functions

Functions used to add custom attributes before spans are created, allowing attributes to be set at span initialization.

/**
 * Function for adding custom attributes before an incoming request span is started
 * @param request - The incoming HTTP request
 * @returns Object containing attributes to add to the span
 */
interface StartIncomingSpanCustomAttributeFunction {
  (request: IncomingMessage): Attributes;
}

/**
 * Function for adding custom attributes before an outgoing request span is started
 * @param request - The outgoing HTTP request options
 * @returns Object containing attributes to add to the span
 */
interface StartOutgoingSpanCustomAttributeFunction {
  (request: RequestOptions): Attributes;
}

Usage Examples:

import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import type { Attributes } from "@opentelemetry/api";
import type { IncomingMessage, RequestOptions } from "http";

const instrumentation = new HttpInstrumentation({
  // Add attributes at the start of incoming request spans
  startIncomingSpanHook: (request: IncomingMessage): Attributes => {
    const attributes: Attributes = {};
    
    // Extract tenant/customer information
    const tenantId = request.headers['x-tenant-id'];
    const customerId = request.headers['x-customer-id'];
    const apiVersion = request.headers['api-version'];
    
    if (tenantId) {
      attributes['tenant.id'] = tenantId;
    }
    
    if (customerId) {
      attributes['customer.id'] = customerId;
    }
    
    if (apiVersion) {
      attributes['api.version'] = apiVersion;
    }
    
    // Add request classification
    const url = request.url || '';
    if (url.startsWith('/api/v1/')) {
      attributes['api.type'] = 'rest';
      attributes['api.version'] = 'v1';
    } else if (url.startsWith('/graphql')) {
      attributes['api.type'] = 'graphql';
    } else if (url.startsWith('/webhook')) {
      attributes['api.type'] = 'webhook';
    }
    
    return attributes;
  },
  
  // Add attributes at the start of outgoing request spans
  startOutgoingSpanHook: (request: RequestOptions): Attributes => {
    const attributes: Attributes = {};
    const hostname = request.hostname || request.host || '';
    const path = request.path || '';
    
    // Service identification
    if (hostname.includes('api.stripe.com')) {
      attributes['service.name'] = 'stripe';
      attributes['service.type'] = 'payment';
    } else if (hostname.includes('api.github.com')) {
      attributes['service.name'] = 'github';
      attributes['service.type'] = 'code_repository';
    } else if (hostname.includes('amazonaws.com')) {
      attributes['service.name'] = 'aws';
      attributes['service.type'] = 'cloud_service';
    }
    
    // Operation classification
    const method = request.method?.toUpperCase() || 'GET';
    if (method === 'GET') {
      attributes['operation.type'] = 'read';
    } else if (method === 'POST' || method === 'PUT') {
      attributes['operation.type'] = 'write';
    } else if (method === 'DELETE') {
      attributes['operation.type'] = 'delete';
    }
    
    // Add timeout information if specified
    if (request.timeout) {
      attributes['http.client.timeout'] = request.timeout;
    }
    
    return attributes;
  }
});

Advanced Hook Patterns

Conditional Attribute Addition

const instrumentation = new HttpInstrumentation({
  requestHook: (span, request) => {
    // Only add attributes for certain content types
    const contentType = request.headers['content-type'] || '';
    
    if (contentType.includes('application/json')) {
      span.setAttribute('request.format', 'json');
      
      // Add content length for JSON requests
      const contentLength = request.headers['content-length'];
      if (contentLength) {
        span.setAttribute('request.json.size', parseInt(contentLength));
      }
    } else if (contentType.includes('multipart/form-data')) {
      span.setAttribute('request.format', 'multipart');
    } else if (contentType.includes('application/x-www-form-urlencoded')) {
      span.setAttribute('request.format', 'form');
    }
  }
});

Error Context Enhancement

const instrumentation = new HttpInstrumentation({
  applyCustomAttributesOnSpan: (span, request, response) => {
    // Enhanced error context for failed requests
    if ('statusCode' in response && response.statusCode && response.statusCode >= 400) {
      span.setAttribute('error.type', 'http_error');
      span.setAttribute('error.status_code', response.statusCode);
      
      // Add more context for specific error ranges
      if (response.statusCode >= 500) {
        span.setAttribute('error.category', 'server_error');
        span.setAttribute('error.severity', 'high');
      } else if (response.statusCode >= 400) {
        span.setAttribute('error.category', 'client_error');
        span.setAttribute('error.severity', 'medium');
      }
      
      // Add request details for debugging
      if ('url' in request) {
        span.setAttribute('error.request.url', request.url || '');
      }
      if ('method' in request) {
        span.setAttribute('error.request.method', request.method || '');
      }
    }
  }
});

Install with Tessl CLI

npx tessl i tessl/npm-opentelemetry--instrumentation-http

docs

configuration.md

hook-functions.md

http-instrumentation.md

index.md

tile.json