Skip to content

@connectum/otel

Full OpenTelemetry instrumentation for Connectum services. Provides ConnectRPC interceptors for server and client tracing/metrics, a correlated logger, deep tracing helpers (traced, traceAll), and OTLP exporter management. Follows OpenTelemetry semantic conventions for RPC.

Layer: 0 (Independent Core)

Related Guides

Full API Reference

Complete TypeScript API documentation: API Reference

Installation

bash
pnpm add @connectum/otel

Requires: Node.js 18+

Quick Start

typescript
import { createServer } from '@connectum/core';
import { createOtelInterceptor, initProvider } from '@connectum/otel';

// Initialize provider (optional -- lazy-initialized from env by default)
initProvider({ serviceName: 'my-service' });

const server = createServer({
  services: [routes],
  interceptors: [
    createOtelInterceptor({ serverPort: 5000 }),
  ],
});

await server.start();

API Reference

Server Interceptor

createOtelInterceptor(options?)

Creates a ConnectRPC interceptor that instruments incoming RPC calls with OpenTelemetry tracing and metrics.

typescript
function createOtelInterceptor(options?: OtelInterceptorOptions): Interceptor;
OptionTypeDefaultDescription
withoutTracingbooleanfalseDisable span creation (metrics only)
withoutMetricsbooleanfalseDisable metric recording (tracing only)
filterOtelFilter--Filter callback to skip specific RPCs
attributeFilterOtelAttributeFilter--Filter to exclude specific attributes
recordMessagesbooleanfalseInclude message content in span events (may contain sensitive data)
trustRemotebooleanfalseUse extracted remote context as parent span
serverAddressstringos.hostname()Override server.address attribute
serverPortnumber--Opt-in server.port attribute
typescript
const interceptor = createOtelInterceptor({
  serverPort: 5000,
  filter: ({ service }) => !service.includes('Health'),
  trustRemote: false, // creates root spans with links to remote context
});

Client Interceptor

createOtelClientInterceptor(options)

Creates a ConnectRPC interceptor for outgoing client RPC calls.

typescript
function createOtelClientInterceptor(options: OtelClientInterceptorOptions): Interceptor;
OptionTypeDefaultDescription
serverAddressstring(required)Target server address
serverPortnumber--Target server port
withoutTracingbooleanfalseDisable span creation
withoutMetricsbooleanfalseDisable metric recording
filterOtelFilter--Filter callback
attributeFilterOtelAttributeFilter--Attribute filter
recordMessagesbooleanfalseRecord message content

Streaming RPC Instrumentation

Both server and client interceptors automatically instrument streaming RPCs (client streaming, server streaming, and bidirectional streaming).

Span lifecycle: For streaming calls, the span is not closed when the handler returns. Instead, the span lifecycle is deferred to stream completion:

  • Span starts when the RPC begins
  • Individual rpc.message events are recorded for each sent/received message (when recordMessages is enabled)
  • Span ends when the stream is fully consumed, errors, or is broken

This ensures accurate duration measurement and complete message tracking for long-lived streams.

Streaming Attributes

Additional semantic convention attributes for streaming messages:

AttributeValueDescription
rpc.message.idsequential numberMessage sequence number within the stream
rpc.message.type"SENT" / "RECEIVED"Message direction
rpc.message.uncompressed_sizebytes (estimated)Estimated message size
network.transport"tcp"Network transport protocol

Deep Tracing Helpers

traced(fn, options?)

Wraps a single function in an OpenTelemetry span. Preserves the original type signature. Supports sync and async functions.

typescript
function traced<T extends (...args: any[]) => any>(fn: T, options?: TracedOptions): T;
typescript
import { traced } from '@connectum/otel';

const findUser = traced(async (id: string) => {
  return await db.users.findById(id);
}, {
  name: 'UserService.findUser',
  recordArgs: true,
  argsFilter: (args) => args.map(a => typeof a === 'string' ? a : '[redacted]'),
});
OptionTypeDefaultDescription
namestringfn.name or "anonymous"Span name
recordArgsboolean | string[]falseRecord function arguments
argsFilterArgsFilter--Transform/mask recorded args
attributesRecord<string, string | number | boolean>--Custom span attributes

traceAll(obj, options?)

Proxy-based wrapper that instruments all methods of an object. Returns a proxy that creates spans for each method call.

typescript
function traceAll<T extends object>(obj: T, options?: TraceAllOptions): T;
typescript
import { traceAll } from '@connectum/otel';

const userRepo = traceAll(new UserRepository(), {
  prefix: 'UserRepository',
  exclude: ['toString'],
  recordArgs: false,
});
OptionTypeDefaultDescription
prefixstringconstructor.name or "Object"Span name prefix
includestring[]--Whitelist of methods to wrap
excludestring[]--Blacklist of methods to exclude
recordArgsboolean | string[]falseRecord method arguments
argsFilterMethodArgsFilter--Transform/mask recorded args

Logger

getLogger(name?, options?)

Returns a correlated logger that automatically enriches log records with the active trace_id and span_id.

typescript
function getLogger(name?: string, options?: LoggerOptions): Logger;
typescript
import { getLogger } from '@connectum/otel';

const log = getLogger('my-service');

log.info('User created', { userId: '123' });
log.warn('Rate limit approaching');
log.error('Database connection failed', { db: 'primary' });
log.debug('Cache hit');
typescript
interface Logger {
  info(message: string, attributes?: AnyValueMap): void;
  warn(message: string, attributes?: AnyValueMap): void;
  error(message: string, attributes?: AnyValueMap): void;
  debug(message: string, attributes?: AnyValueMap): void;
  emit(record: LogRecord): void;
}

Provider Management

initProvider(options?)

Explicitly initialize the OpenTelemetry provider. Must be called before any telemetry is emitted if custom configuration is needed.

typescript
function initProvider(options?: ProviderOptions): void;
OptionTypeDefaultDescription
serviceNamestringOTEL_SERVICE_NAME or npm_package_nameOverride service name
serviceVersionstringnpm_package_versionOverride service version
settingsPartial<OTLPSettings>env-basedOverride OTLP settings

getProvider()

Returns the current provider instance. Lazily creates one with default (environment-based) configuration if not yet initialized.

typescript
function getProvider(): OtelProvider;

shutdownProvider()

Gracefully shuts down all OTLP providers and releases resources.

typescript
async function shutdownProvider(): Promise<void>;

Standalone Instances

typescript
import { getTracer, getMeter } from '@connectum/otel';

const tracer = getTracer();  // Lazy-initialized Tracer
const meter = getMeter();    // Lazy-initialized Meter

RPC Metrics

typescript
import { createRpcServerMetrics, createRpcClientMetrics } from '@connectum/otel';

const serverMetrics = createRpcServerMetrics(meter);
// serverMetrics.callDuration  -- rpc.server.call.duration (seconds)
// serverMetrics.requestSize   -- rpc.server.request.size (bytes)
// serverMetrics.responseSize  -- rpc.server.response.size (bytes)

const clientMetrics = createRpcClientMetrics(meter);
// clientMetrics.callDuration  -- rpc.client.call.duration (seconds)
// clientMetrics.requestSize   -- rpc.client.request.size (bytes)
// clientMetrics.responseSize  -- rpc.client.response.size (bytes)

Configuration

Environment Variables

VariableValuesDefaultDescription
OTEL_SERVICE_NAMEstringnpm_package_nameService name for all telemetry
OTEL_TRACES_EXPORTERconsole | otlp/http | otlp/grpc | none--Trace exporter type
OTEL_METRICS_EXPORTERconsole | otlp/http | otlp/grpc | none--Metric exporter type
OTEL_LOGS_EXPORTERconsole | otlp/http | otlp/grpc | none--Log exporter type
OTEL_EXPORTER_OTLP_ENDPOINTURL--Collector endpoint (e.g. http://localhost:4318)
OTEL_BSP_MAX_EXPORT_BATCH_SIZEnumber100Max spans per export batch
OTEL_BSP_MAX_QUEUE_SIZEnumber1000Max queue size (drops when full)
OTEL_BSP_SCHEDULE_DELAYnumber (ms)1000Auto-export interval
OTEL_BSP_EXPORT_TIMEOUTnumber (ms)10000Export operation timeout

ExporterType

typescript
const ExporterType = {
  CONSOLE: 'console',
  OTLP_HTTP: 'otlp/http',
  OTLP_GRPC: 'otlp/grpc',
  NONE: 'none',
} as const;

Semantic Conventions

Exported attribute constants for manual instrumentation:

typescript
import {
  ATTR_RPC_SYSTEM,
  ATTR_RPC_SERVICE,
  ATTR_RPC_METHOD,
  ATTR_RPC_CONNECT_RPC_STATUS_CODE,
  ATTR_SERVER_ADDRESS,
  ATTR_SERVER_PORT,
  ATTR_ERROR_TYPE,
  ATTR_RPC_MESSAGE_ID,
  ATTR_RPC_MESSAGE_TYPE,
  ATTR_RPC_MESSAGE_UNCOMPRESSED_SIZE,
  ATTR_NETWORK_TRANSPORT,
  RPC_SYSTEM_CONNECT_RPC,
  RPC_MESSAGE_EVENT,
} from '@connectum/otel';

Exports Summary

ExportSubpathDescription
createOtelInterceptor. / ./interceptorServer-side RPC interceptor
createOtelClientInterceptor. / ./client-interceptorClient-side RPC interceptor
traced. / ./tracedFunction-level tracing wrapper
traceAll. / ./traceAllObject-level tracing proxy
getLogger. / ./loggerCorrelated logger
getTracer, getMeter. / ./tracer, ./meterStandalone OTel instances
initProvider, getProvider, shutdownProvider. / ./providerProvider lifecycle
createRpcServerMetrics, createRpcClientMetrics. / ./metricsRPC metric factories
ATTR_*, ConnectErrorCode, etc.. / ./attributesSemantic conventions
ExporterType, getOTLPSettings, etc.. / ./configConfiguration utilities