How to Set Up OpenTelemetry Observability and Tracing for Copilot SDK: A Complete Guide
The Copilot SDK enables OpenTelemetry observability by accepting a TelemetryConfig object that it translates into environment variables for the spawned Copilot runtime, with optional TraceContextProvider support for distributed tracing.
The Copilot SDK provides a flexible, environment-variable-based approach to OpenTelemetry (OTEL) observability. Rather than bundling OpenTelemetry libraries directly, the SDK externalizes telemetry configuration—allowing you to inject OTLP endpoints, exporters, and trace context through the telemetry option in CopilotClientOptions.
Core Architecture: How Telemetry Flows Through the SDK
The SDK's telemetry system rests on three foundational types defined in nodejs/src/types.ts:
TelemetryConfig— configuration object describing exporters, endpoints, and capture settings (source)CopilotClientOptions.telemetry— optional field passed to theCopilotClientconstructor (source)TraceContextProvider— callback interface for propagating external W3C trace context (source)
When you call client.start(), the SDK's buildRuntimeEnv() method in nodejs/src/client.ts (source) transforms your TelemetryConfig into the following environment variables:
| Environment Variable | Source in TelemetryConfig |
Purpose |
|---|---|---|
COPILOT_OTEL_ENABLED |
always "true" |
Activates the runtime's OTEL exporter |
OTEL_EXPORTER_OTLP_ENDPOINT |
otlpEndpoint |
OTLP collector URL |
OTEL_EXPORTER_OTLP_PROTOCOL |
otlpProtocol |
"http/json" or "http/protobuf" |
COPILOT_OTEL_FILE_EXPORTER_PATH |
filePath |
JSON-lines output for local debugging |
COPILOT_OTEL_EXPORTER_TYPE |
exporterType |
"otlp-http" or "file" |
COPILOT_OTEL_SOURCE_NAME |
sourceName |
Instrumentation scope name |
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT |
captureContent |
Include prompt/response payloads |
Setting Up OTLP HTTP Exporter
The most common production configuration streams traces to an OTLP collector. Pass a TelemetryConfig with exporterType: "otlp-http" when constructing your client:
import { CopilotClient, RuntimeConnection } from "copilot-sdk";
const client = new CopilotClient({
telemetry: {
otlpEndpoint: "http://localhost:4318",
otlpProtocol: "http/json",
exporterType: "otlp-http",
sourceName: "my-backend-service",
captureContent: true,
},
connection: RuntimeConnection.forTcp({ port: 0 })
});
await client.start();
The buildRuntimeEnv() method populates OTEL_EXPORTER_OTLP_ENDPOINT and related variables before spawning the Copilot CLI process. The runtime reads these on startup and configures its internal exporter accordingly.
Using File-Based Exporter for Local Development
For debugging without a collector, write traces to a JSON-lines file:
import { CopilotClient } from "copilot-sdk";
const client = new CopilotClient({
telemetry: {
filePath: "./copilot-traces.jsonl",
exporterType: "file",
captureContent: false,
},
});
await client.start();
This sets COPILOT_OTEL_FILE_EXPORTER_PATH (source), causing the runtime to append traces as newline-delimited JSON objects.
Implementing Distributed Tracing with TraceContextProvider
To make Copilot runtime spans children of your existing traces, provide a TraceContextProvider via onGetTraceContext:
import { propagation, context } from "@opentelemetry/api";
import { CopilotClient } from "copilot-sdk";
const client = new CopilotClient({
telemetry: {
otlpEndpoint: "http://localhost:4318",
otlpProtocol: "http/json",
exporterType: "otlp-http",
},
onGetTraceContext: () => {
const carrier: Record<string, string> = {};
propagation.inject(context.active(), carrier);
return carrier; // { traceparent, tracestate }
},
});
await client.start();
The SDK calls this provider before each RPC (session.create, session.send, etc.) via the getTraceContext helper in nodejs/src/telemetry.ts (source), injecting the returned headers into the request.
Complete End-to-End Example
Combine OTLP export with custom span propagation:
import { trace, context, propagation } from "@opentelemetry/api";
import { CopilotClient, RuntimeConnection } from "copilot-sdk";
const tracer = trace.getTracer("my-app");
const client = new CopilotClient({
telemetry: {
otlpEndpoint: "http://localhost:4318",
otlpProtocol: "http/json",
exporterType: "otlp-http",
sourceName: "my-app",
captureContent: true,
},
onGetTraceContext: () => {
const carrier: Record<string, string> = {};
propagation.inject(context.active(), carrier);
return carrier;
},
connection: RuntimeConnection.forTcp({ port: 0 }),
});
await client.start();
await tracer.startActiveSpan("copilot-session", async (span) => {
const session = await client.session.create();
const response = await session.send("Refactor this function to use async/await");
});
All Copilot CLI spans become descendants of your "copilot-session" span, with full trace continuity to your OTLP collector.
Important Constraints and Limitations
In-process connections lack per-client telemetry. The RuntimeConnection.forInProcess() transport inherits the host process's environment unconditionally. If you use this connection type, set OTEL environment variables directly before constructing the client—the telemetry option has no effect.
Configuration is immutable after construction. Changes to TelemetryConfig after calling client.start() do not propagate; the SDK reads these values once during buildRuntimeEnv().
Zero OpenTelemetry dependencies. The SDK does not import @opentelemetry/* packages, leaving you free to use any API version in your application code.
Summary
- The Copilot SDK implements OpenTelemetry observability through environment variable injection, not bundled libraries
- Configure via
TelemetryConfiginCopilotClientOptions.telemetryto set OTLP endpoints, protocols, and exporters - The
buildRuntimeEnv()method innodejs/src/client.tstranslates configuration intoCOPILOT_OTEL_*andOTEL_EXPORTER_*variables - Use
TraceContextProvider(onGetTraceContext) to propagate external trace context for distributed tracing - Choose OTLP HTTP for production collectors or file export for local debugging
- Avoid
forInProcessconnections when telemetry isolation is required
Frequently Asked Questions
Does the Copilot SDK include OpenTelemetry libraries?
No. The SDK deliberately excludes @opentelemetry dependencies. It writes configuration to environment variables that the spawned Copilot runtime reads, keeping your dependency tree clean and version-flexible.
What happens if I don't provide a telemetry option?
The Copilot runtime runs without OpenTelemetry export. No traces are collected, and no performance overhead from telemetry processing occurs.
How do I correlate Copilot spans with my existing distributed traces?
Implement onGetTraceContext with a function that calls propagation.inject(context.active(), carrier). The SDK invokes this before each RPC, injecting traceparent and tracestate so Copilot spans attach to your current trace.
Can I change telemetry settings after creating the client?
No. The SDK evaluates TelemetryConfig once during start() via buildRuntimeEnv(). To modify settings, construct a new CopilotClient instance with updated configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →