Tracing
Connectum provides distributed tracing through OpenTelemetry interceptors for both server-side and client-side RPC calls, plus deep tracing utilities for your business logic.
Server Interceptor
createOtelInterceptor() automatically creates spans for all incoming RPC calls with OpenTelemetry semantic conventions:
typescript
import { createOtelInterceptor } from '@connectum/otel';
const interceptor = createOtelInterceptor({
filter: ({ service }) => !service.includes('grpc.health'),
serverPort: 5000,
recordMessages: false,
trustRemote: false,
});Options
| Option | Type | Default | Description |
|---|---|---|---|
filter | OtelFilter | -- | Skip specific requests from tracing |
serverAddress | string | os.hostname() | Override server address attribute |
serverPort | number | -- | Add server port attribute |
recordMessages | boolean | false | Include request/response bodies in spans |
trustRemote | boolean | false | Use remote context as parent span |
withoutTracing | boolean | false | Disable tracing (metrics only) |
withoutMetrics | boolean | false | Disable metrics (tracing only) |
attributeFilter | OtelAttributeFilter | -- | Exclude specific span attributes |
Client Interceptor
For outgoing RPC calls, use createOtelClientInterceptor() to propagate trace context:
typescript
import { createConnectTransport } from '@connectrpc/connect-node';
import { createClient } from '@connectrpc/connect';
import { createOtelClientInterceptor } from '@connectum/otel';
import { UserService } from '#gen/user_pb.js';
const transport = createConnectTransport({
baseUrl: 'http://user-service:5001',
interceptors: [
createOtelClientInterceptor({
serverAddress: 'user-service', // Required
serverPort: 5001,
}),
],
});
const client = createClient(UserService, transport);The client interceptor:
- Injects trace context into outgoing requests via
propagation.inject() - Creates
SpanKind.CLIENTspans - Records
rpc.client.*metrics
Deep Tracing with traced()
Wrap individual functions to create spans for your business logic:
typescript
import { traced } from '@connectum/otel';
const findUser = traced(async (id: string) => {
return await db.users.findById(id);
}, {
name: 'UserRepository.findUser',
recordArgs: true, // Record function arguments as span attributes
});
// Each call creates a span: "UserRepository.findUser"
const user = await findUser('123');Deep Tracing with traceAll()
Wrap all methods of an object via Proxy:
typescript
import { traceAll } from '@connectum/otel';
class OrderRepository {
async findById(id: string) {
return await db.orders.findById(id);
}
async create(data: OrderData) {
return await db.orders.create(data);
}
async updateStatus(id: string, status: string) {
return await db.orders.update(id, { status });
}
}
// Auto-instrument all methods (does NOT mutate the original)
const repository = traceAll(new OrderRepository(), {
prefix: 'OrderRepository',
exclude: ['internalHelper'],
recordArgs: true,
});
// Calls automatically create spans:
// "OrderRepository.findById", "OrderRepository.create", etc.
await repository.findById('order-123');Performance
traceAll() uses ES6 Proxy and creates method wrappers lazily on first access. It prevents double-wrapping automatically.
Distributed Tracing Across Services
A complete example showing trace context propagation between two services:
typescript
// ---- Service A (Gateway) ----
import { createServer } from '@connectum/core';
import { createConnectTransport } from '@connectrpc/connect-node';
import { createClient } from '@connectrpc/connect';
import { createOtelInterceptor, createOtelClientInterceptor } from '@connectum/otel';
import { UserService } from '#gen/user_pb.js';
import gatewayRoutes from '#gen/routes.js';
// Server: trace incoming requests
const server = createServer({
services: [gatewayRoutes],
port: 5000,
interceptors: [
createOtelInterceptor({
serverPort: 5000,
filter: ({ service }) => !service.includes('grpc.health'),
}),
],
});
// Client: propagate trace context to Service B
const transport = createConnectTransport({
baseUrl: 'http://user-service:5001',
interceptors: [
createOtelClientInterceptor({
serverAddress: 'user-service',
serverPort: 5001,
}),
],
});
const userClient = createClient(UserService, transport);
// Trace context flows automatically:
// Service A (server span) -> Service A (client span) -> Service B (server span)Related
- Observability Overview -- back to overview
- Service Communication -- inter-service calls, transport configuration
- Client Interceptors -- OTel client interceptor, resilience
- Metrics -- counters, histograms, automatic RPC metrics
- Logging -- structured logging with trace correlation
- @connectum/otel -- Package Guide
- @connectum/otel API -- Full API Reference
