src — telemetry

Module: src-telemetry Cohesion: 0.80 Members: 0

src — telemetry

The src/telemetry/otel-tracer.ts module provides a lightweight, dependency-free implementation of an OpenTelemetry (OTel) tracer. Its primary purpose is to capture and export trace spans to an OTLP-compatible endpoint using HTTP JSON, without requiring the full @opentelemetry/* SDKs.

This module is designed for scenarios where:

Core Concepts

The module implements a subset of OpenTelemetry tracing concepts:

OtelTracer Class

The OtelTracer class is the central component of this module, responsible for creating, buffering, and exporting spans.

Initialization and Configuration

An OtelTracer instance is configured via the OtelTracerConfig interface, environment variables, or CLI flags.

interface OtelTracerConfig {
  /** OTLP HTTP endpoint (e.g., http:class="hl-cmt">//localhost:4318/v1/traces) */
  endpoint?: string;
  /** Service name reported in telemetry */
  serviceName?: string;
  /** Enable/disable the tracer */
  enabled?: boolean;
  /** Flush interval in milliseconds (default: 30000) */
  flushIntervalMs?: number;
  /** Maximum buffer size before auto-flush (default: 100) */
  maxBufferSize?: number;
}

The constructor prioritizes configuration in the following order: config object > OTEL_ENDPOINT environment variable > default values.

import { OtelTracer, getOtelTracer } from './telemetry/otel-tracer.ts';

class="hl-cmt">// Via constructor config
const tracer = new OtelTracer({
  endpoint: 'http:class="hl-cmt">//localhost:4318/v1/traces',
  serviceName: 'my-app',
  flushIntervalMs: 10000,
});

class="hl-cmt">// Via singleton (recommended for app-wide use)
const appTracer = getOtelTracer({
  endpoint: process.env.OTEL_ENDPOINT || 'http:class="hl-cmt">//localhost:4318/v1/traces',
  serviceName: 'codebuddy',
});

If an endpoint is provided and the tracer is enabled, a setInterval timer is started to periodically call flush() at the specified flushIntervalMs. This timer is unref()'d to prevent it from keeping the Node.js process alive.

Span Lifecycle

The OtelTracer manages the creation, completion, and buffering of spans.

  1. startSpan(name, attributes?):

  1. endSpan(span, status?):
import { getOtelTracer } from './telemetry/otel-tracer.ts';

const tracer = getOtelTracer();

async function performOperation() {
  const span = tracer.startSpan('my.operation', { 'operation.type': 'async' });
  try {
    class="hl-cmt">// Simulate work
    await new Promise(resolve => setTimeout(resolve, 100));
    tracer.endSpan(span, { code: 1 }); class="hl-cmt">// OK
  } catch (error) {
    tracer.endSpan(span, { code: 2, message: String(error) }); class="hl-cmt">// ERROR
    throw error;
  }
}

Data Export (flush)

The flush() method is responsible for sending buffered spans to the configured OTLP endpoint.

sequenceDiagram
    participant App as Application Code
    participant Tracer as OtelTracer Instance
    participant Buffer as Internal Buffer
    participant OTLP as OTLP Endpoint

    App->>Tracer: startSpan("operation")
    Tracer->>App: OtelSpan object
    App->>Tracer: endSpan(OtelSpan, status)
    Tracer->>Buffer: Add OtelSpan
    alt Buffer full OR Flush Interval
        Tracer->>Tracer: flush()
        Tracer->>Buffer: Retrieve spans
        Tracer->>OTLP: POST /v1/traces (JSON payload)
        OTLP-->>Tracer: HTTP 200 OK
        Tracer->>Buffer: Clear sent spans
    else Export Failed
        OTLP--xTracer: HTTP Error / Network Error
        Tracer->>Buffer: Re-add spans (with limits)
    end
    App->>Tracer: dispose()
    Tracer->>Buffer: Flush remaining spans
    Tracer->>OTLP: POST /v1/traces

Convenience Tracing Methods

The OtelTracer provides specialized methods for common tracing patterns, which create, populate, and immediately buffer a span:

These methods simplify tracing by handling startSpan, attribute setting, endTimeUnixNano, and endSpan (buffering) in a single call.

Trace Management

Properties

Disposal

Helper Functions

The module includes several internal helper functions:

Singleton Access

To ensure consistent tracing across an application, the module provides a singleton pattern:

Example Usage

import { getOtelTracer, OtelSpanStatus } from './telemetry/otel-tracer.ts';

class="hl-cmt">// Get the singleton tracer instance
const tracer = getOtelTracer({
  endpoint: 'http:class="hl-cmt">//localhost:4318/v1/traces',
  serviceName: 'my-codebuddy-app',
});

async function processRequest(requestId: string) {
  class="hl-cmt">// Start a new trace for this request
  tracer.newTrace();

  class="hl-cmt">// Trace a high-level operation
  const requestSpan = tracer.startSpan('request.process', { 'request.id': requestId });

  try {
    class="hl-cmt">// Trace an LLM API call using the convenience method
    tracer.traceApiCall('gpt-4', 1500, 2500); class="hl-cmt">// 1500 tokens, 2500ms duration

    class="hl-cmt">// Trace a tool execution
    const toolSuccess = Math.random() > 0.1; class="hl-cmt">// 90% success rate
    tracer.traceToolExecution('search_tool', 500, toolSuccess);

    class="hl-cmt">// Trace a conversation turn
    tracer.traceConversation('session-abc', 5);

    class="hl-cmt">// End the main request span
    tracer.endSpan(requestSpan, { code: 1 }); class="hl-cmt">// OK
  } catch (error) {
    tracer.endSpan(requestSpan, { code: 2, message: String(error) }); class="hl-cmt">// ERROR
  }
}

class="hl-cmt">// Example call
processRequest('req-123').catch(console.error);

class="hl-cmt">// In a shutdown hook or before process exit, ensure all spans are flushed
process.on('beforeExit', async () => {
  console.log(`Flushing ${tracer.pendingSpans} pending spans...`);
  await tracer.dispose();
  console.log('Tracer disposed.');
});