Skip to content

Commit 7fce3c9

Browse files
loganrosenCopilot
andcommitted
Add OTLP protocol telemetry options
Expose OTLP HTTP protocol selection through each SDK telemetry config and map the values to the standard OpenTelemetry protocol environment variables when spawning the Copilot CLI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 736bd6e commit 7fce3c9

28 files changed

Lines changed: 569 additions & 16 deletions

docs/getting-started.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2227,11 +2227,16 @@ Dependency: `io.opentelemetry:opentelemetry-api`
22272227
| Option | Node.js | Python | Go | Rust | Java | .NET | Description |
22282228
|---|---|---|---|---|---|---|---|
22292229
| OTLP endpoint | `otlpEndpoint` | `otlp_endpoint` | `OTLPEndpoint` | `otlp_endpoint` | `otlpEndpoint` | `OtlpEndpoint` | OTLP HTTP endpoint URL |
2230+
| OTLP protocol | `otlpProtocol` | `otlp_protocol` | `OTLPProtocol` | `otlp_protocol` | `otlpProtocol` | `OtlpProtocol` | OTLP HTTP protocol for all signals: `"http/json"` or `"http/protobuf"` |
2231+
| OTLP traces protocol | `otlpTracesProtocol` | `otlp_traces_protocol` | `OTLPTracesProtocol` | `otlp_traces_protocol` | `otlpTracesProtocol` | `OtlpTracesProtocol` | OTLP HTTP protocol override for traces |
2232+
| OTLP metrics protocol | `otlpMetricsProtocol` | `otlp_metrics_protocol` | `OTLPMetricsProtocol` | `otlp_metrics_protocol` | `otlpMetricsProtocol` | `OtlpMetricsProtocol` | OTLP HTTP protocol override for metrics |
22302233
| File path | `filePath` | `file_path` | `FilePath` | `file_path` | `filePath` | `FilePath` | File path for JSON-lines trace output |
22312234
| Exporter type | `exporterType` | `exporter_type` | `ExporterType` | `exporter_type` | `exporterType` | `ExporterType` | `"otlp-http"` or `"file"` |
22322235
| Source name | `sourceName` | `source_name` | `SourceName` | `source_name` | `sourceName` | `SourceName` | Instrumentation scope name |
22332236
| Capture content | `captureContent` | `capture_content` | `CaptureContent` | `capture_content` | `captureContent` | `CaptureContent` | Whether to capture message content |
22342237

2238+
The OTLP protocol fields configure the CLI's `"otlp-http"` exporter. Leave them unset to use the CLI default (`"http/json"`), set the general protocol for both traces and metrics, or use the traces/metrics fields to override one signal.
2239+
22352240
### File export
22362241

22372242
To write traces to a local file instead of an OTLP endpoint:

docs/observability/opentelemetry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,16 @@ let client = Client::start(ClientOptions::new()
104104
| Option | Node.js | Python | Go | .NET | Java | Rust | Description |
105105
|---|---|---|---|---|---|---|---|
106106
| OTLP endpoint | `otlpEndpoint` | `otlp_endpoint` | `OTLPEndpoint` | `OtlpEndpoint` | `otlpEndpoint` | `otlp_endpoint` | OTLP HTTP endpoint URL |
107+
| OTLP protocol | `otlpProtocol` | `otlp_protocol` | `OTLPProtocol` | `OtlpProtocol` | `otlpProtocol` | `otlp_protocol` | OTLP HTTP protocol for all signals: `"http/json"` or `"http/protobuf"` |
108+
| OTLP traces protocol | `otlpTracesProtocol` | `otlp_traces_protocol` | `OTLPTracesProtocol` | `OtlpTracesProtocol` | `otlpTracesProtocol` | `otlp_traces_protocol` | OTLP HTTP protocol override for traces |
109+
| OTLP metrics protocol | `otlpMetricsProtocol` | `otlp_metrics_protocol` | `OTLPMetricsProtocol` | `OtlpMetricsProtocol` | `otlpMetricsProtocol` | `otlp_metrics_protocol` | OTLP HTTP protocol override for metrics |
107110
| File path | `filePath` | `file_path` | `FilePath` | `FilePath` | `filePath` | `file_path` | File path for JSON-lines trace output |
108111
| Exporter type | `exporterType` | `exporter_type` | `ExporterType` | `ExporterType` | `exporterType` | `exporter_type` | `"otlp-http"` or `"file"` |
109112
| Source name | `sourceName` | `source_name` | `SourceName` | `SourceName` | `sourceName` | `source_name` | Instrumentation scope name |
110113
| Capture content | `captureContent` | `capture_content` | `CaptureContent` | `CaptureContent` | `captureContent` | `capture_content` | Whether to capture message content |
111114

115+
The OTLP protocol fields configure the CLI's `"otlp-http"` exporter. Leave them unset to use the CLI default (`"http/json"`), set the general protocol for both traces and metrics, or use the traces/metrics fields to override one signal.
116+
112117
### Trace context propagation
113118

114119
> **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.

dotnet/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,9 @@ var client = new CopilotClient(new CopilotClientOptions
738738
**TelemetryConfig properties:**
739739

740740
- `OtlpEndpoint` - OTLP HTTP endpoint URL
741+
- `OtlpProtocol` - OTLP HTTP protocol for all signals (`"http/json"` or `"http/protobuf"`)
742+
- `OtlpTracesProtocol` - OTLP HTTP protocol override for traces
743+
- `OtlpMetricsProtocol` - OTLP HTTP protocol override for metrics
741744
- `FilePath` - File path for JSON-lines trace output
742745
- `ExporterType` - `"otlp-http"` or `"file"`
743746
- `SourceName` - Instrumentation scope name

dotnet/src/Client.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,6 +1805,9 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex)
18051805
{
18061806
startInfo.Environment["COPILOT_OTEL_ENABLED"] = "true";
18071807
if (telemetry.OtlpEndpoint is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_ENDPOINT"] = telemetry.OtlpEndpoint;
1808+
if (telemetry.OtlpProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_PROTOCOL"] = telemetry.OtlpProtocol;
1809+
if (telemetry.OtlpTracesProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] = telemetry.OtlpTracesProtocol;
1810+
if (telemetry.OtlpMetricsProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] = telemetry.OtlpMetricsProtocol;
18081811
if (telemetry.FilePath is not null) startInfo.Environment["COPILOT_OTEL_FILE_EXPORTER_PATH"] = telemetry.FilePath;
18091812
if (telemetry.ExporterType is not null) startInfo.Environment["COPILOT_OTEL_EXPORTER_TYPE"] = telemetry.ExporterType;
18101813
if (telemetry.SourceName is not null) startInfo.Environment["COPILOT_OTEL_SOURCE_NAME"] = telemetry.SourceName;

dotnet/src/Types.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,30 @@ public sealed class TelemetryConfig
413413
/// </remarks>
414414
public string? OtlpEndpoint { get; set; }
415415

416+
/// <summary>
417+
/// OTLP HTTP protocol for all signals (<c>"http/json"</c> or <c>"http/protobuf"</c>).
418+
/// </summary>
419+
/// <remarks>
420+
/// Maps to the <c>OTEL_EXPORTER_OTLP_PROTOCOL</c> environment variable.
421+
/// </remarks>
422+
public string? OtlpProtocol { get; set; }
423+
424+
/// <summary>
425+
/// OTLP HTTP protocol for traces (<c>"http/json"</c> or <c>"http/protobuf"</c>).
426+
/// </summary>
427+
/// <remarks>
428+
/// Maps to the <c>OTEL_EXPORTER_OTLP_TRACES_PROTOCOL</c> environment variable.
429+
/// </remarks>
430+
public string? OtlpTracesProtocol { get; set; }
431+
432+
/// <summary>
433+
/// OTLP HTTP protocol for metrics (<c>"http/json"</c> or <c>"http/protobuf"</c>).
434+
/// </summary>
435+
/// <remarks>
436+
/// Maps to the <c>OTEL_EXPORTER_OTLP_METRICS_PROTOCOL</c> environment variable.
437+
/// </remarks>
438+
public string? OtlpMetricsProtocol { get; set; }
439+
416440
/// <summary>
417441
/// File path for the file exporter.
418442
/// </summary>

dotnet/test/E2E/ClientOptionsE2ETests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli()
7878
Telemetry = new TelemetryConfig
7979
{
8080
OtlpEndpoint = "http://127.0.0.1:4318",
81+
OtlpProtocol = "http/protobuf",
82+
OtlpTracesProtocol = "http/json",
83+
OtlpMetricsProtocol = "http/protobuf",
8184
FilePath = telemetryPath,
8285
ExporterType = "file",
8386
SourceName = "dotnet-sdk-e2e",
@@ -104,6 +107,9 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli()
104107
Assert.Equal("process-option-token", capturedEnv.GetProperty("COPILOT_SDK_AUTH_TOKEN").GetString());
105108
Assert.Equal("true", capturedEnv.GetProperty("COPILOT_OTEL_ENABLED").GetString());
106109
Assert.Equal("http://127.0.0.1:4318", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_ENDPOINT").GetString());
110+
Assert.Equal("http/protobuf", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_PROTOCOL").GetString());
111+
Assert.Equal("http/json", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL").GetString());
112+
Assert.Equal("http/protobuf", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL").GetString());
107113
Assert.Equal(telemetryPath, capturedEnv.GetProperty("COPILOT_OTEL_FILE_EXPORTER_PATH").GetString());
108114
Assert.Equal("file", capturedEnv.GetProperty("COPILOT_OTEL_EXPORTER_TYPE").GetString());
109115
Assert.Equal("dotnet-sdk-e2e", capturedEnv.GetProperty("COPILOT_OTEL_SOURCE_NAME").GetString());
@@ -124,6 +130,32 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli()
124130
await session.DisposeAsync();
125131
}
126132

133+
[Fact]
134+
public async Task Should_Only_Set_Configured_Otlp_Protocol_Env_Vars()
135+
{
136+
var (cliPath, capturePath) = await CreateFakeCliCaptureAsync();
137+
138+
await using var client = Ctx.CreateClient(options: new CopilotClientOptions
139+
{
140+
Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]),
141+
GitHubToken = "process-option-token",
142+
Telemetry = new TelemetryConfig
143+
{
144+
OtlpTracesProtocol = "http/protobuf",
145+
},
146+
UseLoggedInUser = false,
147+
});
148+
149+
await client.StartAsync();
150+
151+
using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath));
152+
var capturedEnv = capture.RootElement.GetProperty("env");
153+
Assert.Equal("true", capturedEnv.GetProperty("COPILOT_OTEL_ENABLED").GetString());
154+
Assert.Equal("http/protobuf", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL").GetString());
155+
Assert.False(capturedEnv.TryGetProperty("OTEL_EXPORTER_OTLP_PROTOCOL", out _));
156+
Assert.False(capturedEnv.TryGetProperty("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", out _));
157+
}
158+
127159
[Fact]
128160
public async Task Should_Forward_EnableSessionTelemetry_In_Wire_Request()
129161
{
@@ -642,6 +674,9 @@ function saveCapture() {
642674
COPILOT_SDK_AUTH_TOKEN: process.env.COPILOT_SDK_AUTH_TOKEN,
643675
COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED,
644676
OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
677+
OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL,
678+
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
679+
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
645680
COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH,
646681
COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE,
647682
COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME,

dotnet/test/Unit/TelemetryTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public void TelemetryConfig_DefaultValues_AreNull()
1616
var config = new TelemetryConfig();
1717

1818
Assert.Null(config.OtlpEndpoint);
19+
Assert.Null(config.OtlpProtocol);
20+
Assert.Null(config.OtlpTracesProtocol);
21+
Assert.Null(config.OtlpMetricsProtocol);
1922
Assert.Null(config.FilePath);
2023
Assert.Null(config.ExporterType);
2124
Assert.Null(config.SourceName);
@@ -28,13 +31,19 @@ public void TelemetryConfig_CanSetAllProperties()
2831
var config = new TelemetryConfig
2932
{
3033
OtlpEndpoint = "http://localhost:4318",
34+
OtlpProtocol = "http/protobuf",
35+
OtlpTracesProtocol = "http/json",
36+
OtlpMetricsProtocol = "http/protobuf",
3137
FilePath = "/tmp/traces.json",
3238
ExporterType = "otlp-http",
3339
SourceName = "my-app",
3440
CaptureContent = true
3541
};
3642

3743
Assert.Equal("http://localhost:4318", config.OtlpEndpoint);
44+
Assert.Equal("http/protobuf", config.OtlpProtocol);
45+
Assert.Equal("http/json", config.OtlpTracesProtocol);
46+
Assert.Equal("http/protobuf", config.OtlpMetricsProtocol);
3847
Assert.Equal("/tmp/traces.json", config.FilePath);
3948
Assert.Equal("otlp-http", config.ExporterType);
4049
Assert.Equal("my-app", config.SourceName);

go/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,9 @@ client, err := copilot.NewClient(copilot.ClientOptions{
573573
**TelemetryConfig fields:**
574574

575575
- `OTLPEndpoint` (string): OTLP HTTP endpoint URL
576+
- `OTLPProtocol` (string): OTLP HTTP protocol for all signals (`"http/json"` or `"http/protobuf"`)
577+
- `OTLPTracesProtocol` (string): OTLP HTTP protocol override for traces
578+
- `OTLPMetricsProtocol` (string): OTLP HTTP protocol override for metrics
576579
- `FilePath` (string): File path for JSON-lines trace output
577580
- `ExporterType` (string): `"otlp-http"` or `"file"`
578581
- `SourceName` (string): Instrumentation scope name

go/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1695,6 +1695,15 @@ func (c *Client) startCLIServer(ctx context.Context) error {
16951695
if t.OTLPEndpoint != "" {
16961696
c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_ENDPOINT", t.OTLPEndpoint)
16971697
}
1698+
if t.OTLPProtocol != "" {
1699+
c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_PROTOCOL", t.OTLPProtocol)
1700+
}
1701+
if t.OTLPTracesProtocol != "" {
1702+
c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", t.OTLPTracesProtocol)
1703+
}
1704+
if t.OTLPMetricsProtocol != "" {
1705+
c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", t.OTLPMetricsProtocol)
1706+
}
16981707
if t.FilePath != "" {
16991708
c.process.Env = setEnvValue(c.process.Env, "COPILOT_OTEL_FILE_EXPORTER_PATH", t.FilePath)
17001709
}

go/internal/e2e/client_options_e2e_test.go

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,14 @@ func TestClientOptionsE2E(t *testing.T) {
109109
opts.LogLevel = "debug"
110110
opts.SessionIdleTimeoutSeconds = 17
111111
opts.Telemetry = &copilot.TelemetryConfig{
112-
OTLPEndpoint: "http://127.0.0.1:4318",
113-
FilePath: telemetryPath,
114-
ExporterType: "file",
115-
SourceName: "go-sdk-e2e",
116-
CaptureContent: copilot.Bool(true),
112+
OTLPEndpoint: "http://127.0.0.1:4318",
113+
OTLPProtocol: "http/protobuf",
114+
OTLPTracesProtocol: "http/json",
115+
OTLPMetricsProtocol: "http/protobuf",
116+
FilePath: telemetryPath,
117+
ExporterType: "file",
118+
SourceName: "go-sdk-e2e",
119+
CaptureContent: copilot.Bool(true),
117120
}
118121
opts.UseLoggedInUser = copilot.Bool(false)
119122
})
@@ -147,6 +150,9 @@ func TestClientOptionsE2E(t *testing.T) {
147150
"COPILOT_SDK_AUTH_TOKEN": "process-option-token",
148151
"COPILOT_OTEL_ENABLED": "true",
149152
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://127.0.0.1:4318",
153+
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
154+
"OTEL_EXPORTER_OTLP_TRACES_PROTOCOL": "http/json",
155+
"OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": "http/protobuf",
150156
"COPILOT_OTEL_FILE_EXPORTER_PATH": telemetryPath,
151157
"COPILOT_OTEL_EXPORTER_TYPE": "file",
152158
"COPILOT_OTEL_SOURCE_NAME": "go-sdk-e2e",
@@ -195,6 +201,46 @@ func TestClientOptionsE2E(t *testing.T) {
195201
t.Errorf("Expected session.create.params.includeSubAgentStreamingEvents=false, got %v", params["includeSubAgentStreamingEvents"])
196202
}
197203
})
204+
205+
t.Run("should only set configured OTLP protocol env vars", func(t *testing.T) {
206+
ctx := testharness.NewTestContext(t)
207+
208+
cliPath := filepath.Join(ctx.WorkDir, "fake-cli-"+randomHex(t)+".js")
209+
capturePath := filepath.Join(ctx.WorkDir, "fake-cli-capture-"+randomHex(t)+".json")
210+
if err := os.WriteFile(cliPath, []byte(fakeStdioCliScript), 0644); err != nil {
211+
t.Fatalf("Failed to write fake CLI script: %v", err)
212+
}
213+
214+
client := ctx.NewClient(func(opts *copilot.ClientOptions) {
215+
opts.Connection = copilot.StdioConnection{
216+
Path: cliPath,
217+
Args: []string{"--capture-file", capturePath},
218+
}
219+
opts.GitHubToken = "process-option-token"
220+
opts.Telemetry = &copilot.TelemetryConfig{
221+
OTLPTracesProtocol: "http/protobuf",
222+
}
223+
opts.UseLoggedInUser = copilot.Bool(false)
224+
})
225+
t.Cleanup(func() { client.ForceStop() })
226+
227+
if err := client.Start(t.Context()); err != nil {
228+
t.Fatalf("Start failed: %v", err)
229+
}
230+
231+
capture := readCapture(t, capturePath)
232+
if got := capture.Env["COPILOT_OTEL_ENABLED"]; got != "true" {
233+
t.Errorf("Expected COPILOT_OTEL_ENABLED=true, got %q", got)
234+
}
235+
if got := capture.Env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"]; got != "http/protobuf" {
236+
t.Errorf("Expected OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf, got %q", got)
237+
}
238+
for _, key := range []string{"OTEL_EXPORTER_OTLP_PROTOCOL", "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"} {
239+
if got, ok := capture.Env[key]; ok {
240+
t.Errorf("Expected %s to be unset, got %q", key, got)
241+
}
242+
}
243+
})
198244
}
199245

200246
// ---------------------------------------------------------------------------
@@ -372,6 +418,9 @@ function saveCapture() {
372418
COPILOT_SDK_AUTH_TOKEN: process.env.COPILOT_SDK_AUTH_TOKEN,
373419
COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED,
374420
OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
421+
OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL,
422+
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
423+
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
375424
COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH,
376425
COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE,
377426
COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME,

0 commit comments

Comments
 (0)