{"meta":{"title":"OpenTelemetry instrumentation for Copilot SDK","intro":"This guide shows how to add OpenTelemetry tracing to your Copilot SDK applications.","product":"GitHub Copilot","breadcrumbs":[{"href":"/en/copilot","title":"GitHub Copilot"},{"href":"/en/copilot/how-tos","title":"How-tos"},{"href":"/en/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/en/copilot/how-tos/copilot-sdk/observability","title":"Observability"},{"href":"/en/copilot/how-tos/copilot-sdk/observability/opentelemetry","title":"Opentelemetry"}],"documentType":"article"},"body":"# OpenTelemetry instrumentation for Copilot SDK\n\nThis guide shows how to add OpenTelemetry tracing to your Copilot SDK applications.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n## Built-in telemetry support\n\nThe SDK has built-in support for configuring OpenTelemetry on the CLI process and propagating W3C Trace Context between the SDK and CLI. Provide a `TelemetryConfig` when creating the client to opt in:\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n<!-- docs-validate: skip -->\n\n```typescript\nimport { CopilotClient } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient({\n  telemetry: {\n    otlpEndpoint: \"http://localhost:4318\",\n  },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```python\nfrom copilot import CopilotClient\n\nclient = CopilotClient(\n    telemetry={\n        \"otlp_endpoint\": \"http://localhost:4318\",\n    },\n)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```golang\nclient := copilot.NewClient(&copilot.ClientOptions{\n    Telemetry: &copilot.TelemetryConfig{\n        OTLPEndpoint: \"http://localhost:4318\",\n    },\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n<!-- docs-validate: skip -->\n\n```csharp\nvar client = new CopilotClient(new CopilotClientOptions\n{\n    Telemetry = new TelemetryConfig\n    {\n        OtlpEndpoint = \"http://localhost:4318\",\n    },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\n\nvar client = new CopilotClient(new CopilotClientOptions()\n    .setTelemetry(new TelemetryConfig()\n        .setOtlpEndpoint(\"http://localhost:4318\"))\n);\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n<!-- docs-validate: skip -->\n\n```rust\nuse github_copilot_sdk::{Client, ClientOptions, TelemetryConfig};\n\nlet client = Client::start(ClientOptions::new()\n    .with_telemetry(TelemetryConfig::new()\n        .with_otlp_endpoint(\"http://localhost:4318\"))\n).await?;\n```\n\n</div>\n\n</div>\n\n### TelemetryConfig options\n\n| Option          | Node.js          | Python            | Go               | .NET             | Java             | Rust              | Description                                                            |\n| --------------- | ---------------- | ----------------- | ---------------- | ---------------- | ---------------- | ----------------- | ---------------------------------------------------------------------- |\n| OTLP endpoint   | `otlpEndpoint`   | `otlp_endpoint`   | `OTLPEndpoint`   | `OtlpEndpoint`   | `otlpEndpoint`   | `otlp_endpoint`   | OTLP HTTP endpoint URL                                                 |\n| OTLP protocol   | `otlpProtocol`   | `otlp_protocol`   | `OTLPProtocol`   | `OtlpProtocol`   | `otlpProtocol`   | `otlp_protocol`   | OTLP HTTP protocol for all signals: `\"http/json\"` or `\"http/protobuf\"` |\n| File path       | `filePath`       | `file_path`       | `FilePath`       | `FilePath`       | `filePath`       | `file_path`       | File path for JSON-lines trace output                                  |\n| Exporter type   | `exporterType`   | `exporter_type`   | `ExporterType`   | `ExporterType`   | `exporterType`   | `exporter_type`   | `\"otlp-http\"` or `\"file\"`                                              |\n| Source name     | `sourceName`     | `source_name`     | `SourceName`     | `SourceName`     | `sourceName`     | `source_name`     | Instrumentation scope name                                             |\n| Capture content | `captureContent` | `capture_content` | `CaptureContent` | `CaptureContent` | `captureContent` | `capture_content` | Whether to capture message content                                     |\n\nThe OTLP protocol field configures the CLI's `\"otlp-http\"` exporter for all signals. Leave it unset to use the CLI default, or set it to `\"http/protobuf\"` to export protobuf over HTTP.\n\n### Trace context propagation\n\n> **Most users don't need this.** The `TelemetryConfig` above is all you need to collect traces from the CLI. The trace context propagation described in this section is an **advanced feature** for applications that create their own OpenTelemetry spans and want them to appear in the **same distributed trace** as the CLI's spans.\n\nThe SDK can propagate W3C Trace Context (`traceparent`/`tracestate`) on JSON-RPC payloads so that your application's spans and the CLI's spans are linked in one distributed trace. This is useful when, for example, you want to see a \"handle tool call\" span in your app nested inside the CLI's \"execute tool\" span, or show the SDK call as a child of your request-handling span.\n\nFor cost attribution alongside traces, subscribe to `assistant.usage` events and inspect `apiEndpoint` (`AssistantUsageApiEndpoint`) to see whether a turn used Chat Completions, Responses, or Anthropic Messages; see [Streaming session events](/en/copilot/how-tos/copilot-sdk/features/streaming-events).\n\n#### SDK → CLI (outbound)\n\nFor **Node.js**, provide an `onGetTraceContext` callback on the client options. This is only needed if your application already uses `@opentelemetry/api` and you want to link your spans with the CLI's spans. The SDK calls this callback before `session.create`, `session.resume`, and `session.send` RPCs:\n\n<!-- docs-validate: skip -->\n\n```typescript\nimport { CopilotClient } from \"@github/copilot-sdk\";\nimport { propagation, context } from \"@opentelemetry/api\";\n\nconst client = new CopilotClient({\n  telemetry: { otlpEndpoint: \"http://localhost:4318\" },\n  onGetTraceContext: () => {\n    const carrier: Record<string, string> = {};\n    propagation.inject(context.active(), carrier);\n    return carrier; // { traceparent: \"00-...\", tracestate: \"...\" }\n  },\n});\n```\n\nFor **Python**, **Go**, and **.NET**, trace context injection is automatic when the respective OpenTelemetry/Activity API is configured—no callback is needed.\n\n#### CLI → SDK (inbound)\n\nWhen the CLI invokes a tool handler, the `traceparent` and `tracestate` from the CLI's span are available in all languages:\n\n* **Go**: The `ToolInvocation.TraceContext` field is a `context.Context` with the trace already restored—use it directly as the parent for your spans.\n* **Python**: Trace context is automatically restored around the handler via `trace_context()`—child spans are parented to the CLI's span automatically.\n* **.NET**: Trace context is automatically restored via `RestoreTraceContext()`—child `Activity` instances are parented to the CLI's span automatically.\n* **Node.js**: Since the SDK has no OpenTelemetry dependency, `traceparent` and `tracestate` are passed as raw strings on the `ToolInvocation` object. Restore the context manually if needed:\n\n<!-- docs-validate: skip -->\n\n```typescript\nimport { defineTool } from \"@github/copilot-sdk\";\nimport { propagation, context, trace } from \"@opentelemetry/api\";\n\nconst myTool = defineTool(\"my-tool\", {\n  description: \"Do work\",\n  handler: async (args, invocation) => {\n    // Restore the CLI's trace context as the active context\n    const carrier = {\n      traceparent: invocation.traceparent,\n      tracestate: invocation.tracestate,\n    };\n    const parentCtx = propagation.extract(context.active(), carrier);\n\n    // Create a child span under the CLI's span\n    const tracer = trace.getTracer(\"my-app\");\n    return context.with(parentCtx, () =>\n      tracer.startActiveSpan(\"my-tool\", async (span) => {\n        try {\n          const result = await doWork(args);\n          return result;\n        } finally {\n          span.end();\n        }\n      })\n    );\n  },\n});\n\n// Tool handlers are registered when the session is created.\nconst session = await client.createSession({ tools: [myTool] });\n```\n\n### Per-language dependencies\n\n| Language | Dependency                           | Notes                                                                                                                                |\n| -------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |\n| Node.js  | —                                    | No dependency; provide `onGetTraceContext` callback for outbound propagation                                                         |\n| Python   | `opentelemetry-api`                  | Install with `pip install copilot-sdk[telemetry]`                                                                                    |\n| Go       | `go.opentelemetry.io/otel`           | Required dependency                                                                                                                  |\n| .NET     | —                                    | Uses built-in `System.Diagnostics.Activity`                                                                                          |\n| Java     | `io.opentelemetry:opentelemetry-api` | Add this dependency for SDK-based setup; trace context injection is automatic when the OpenTelemetry Java agent or SDK is configured |\n\n## References\n\n* [OpenTelemetry GenAI Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/)\n* [OpenTelemetry MCP Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/)\n* [OpenTelemetry Python SDK](https://opentelemetry.io/docs/instrumentation/python/)\n* [Copilot SDK Documentation](https://github.com/github/copilot-sdk)"}