From 7fce3c99ffc3dea9c021ddec0ca5055184f38bbd Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Fri, 12 Jun 2026 18:02:33 -0400 Subject: [PATCH 1/4] 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> --- docs/getting-started.md | 5 + docs/observability/opentelemetry.md | 5 + dotnet/README.md | 3 + dotnet/src/Client.cs | 3 + dotnet/src/Types.cs | 24 +++++ dotnet/test/E2E/ClientOptionsE2ETests.cs | 35 +++++++ dotnet/test/Unit/TelemetryTests.cs | 9 ++ go/README.md | 3 + go/client.go | 9 ++ go/internal/e2e/client_options_e2e_test.go | 59 ++++++++++- go/internal/e2e/telemetry_e2e_test.go | 31 +++++- go/types.go | 12 +++ .../com/github/copilot/CliServerManager.java | 9 ++ .../github/copilot/rpc/TelemetryConfig.java | 72 ++++++++++++++ .../github/copilot/CliServerManagerTest.java | 6 +- .../github/copilot/TelemetryConfigTest.java | 33 ++++++- nodejs/README.md | 3 + nodejs/src/client.ts | 6 ++ nodejs/src/types.ts | 6 ++ nodejs/test/e2e/client_options.e2e.test.ts | 52 ++++++++++ nodejs/test/telemetry.test.ts | 16 +++ python/README.md | 3 + python/copilot/client.py | 12 +++ python/e2e/test_client_options_e2e.py | 44 +++++++++ python/e2e/test_telemetry_e2e.py | 9 ++ python/test_telemetry.py | 12 +++ rust/README.md | 5 +- rust/src/lib.rs | 99 +++++++++++++++++++ 28 files changed, 569 insertions(+), 16 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index f4e92f6fe0..33ccf4caaf 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2227,11 +2227,16 @@ Dependency: `io.opentelemetry:opentelemetry-api` | Option | Node.js | Python | Go | Rust | Java | .NET | Description | |---|---|---|---|---|---|---|---| | OTLP endpoint | `otlpEndpoint` | `otlp_endpoint` | `OTLPEndpoint` | `otlp_endpoint` | `otlpEndpoint` | `OtlpEndpoint` | OTLP HTTP endpoint URL | +| OTLP protocol | `otlpProtocol` | `otlp_protocol` | `OTLPProtocol` | `otlp_protocol` | `otlpProtocol` | `OtlpProtocol` | OTLP HTTP protocol for all signals: `"http/json"` or `"http/protobuf"` | +| OTLP traces protocol | `otlpTracesProtocol` | `otlp_traces_protocol` | `OTLPTracesProtocol` | `otlp_traces_protocol` | `otlpTracesProtocol` | `OtlpTracesProtocol` | OTLP HTTP protocol override for traces | +| OTLP metrics protocol | `otlpMetricsProtocol` | `otlp_metrics_protocol` | `OTLPMetricsProtocol` | `otlp_metrics_protocol` | `otlpMetricsProtocol` | `OtlpMetricsProtocol` | OTLP HTTP protocol override for metrics | | File path | `filePath` | `file_path` | `FilePath` | `file_path` | `filePath` | `FilePath` | File path for JSON-lines trace output | | Exporter type | `exporterType` | `exporter_type` | `ExporterType` | `exporter_type` | `exporterType` | `ExporterType` | `"otlp-http"` or `"file"` | | Source name | `sourceName` | `source_name` | `SourceName` | `source_name` | `sourceName` | `SourceName` | Instrumentation scope name | | Capture content | `captureContent` | `capture_content` | `CaptureContent` | `capture_content` | `captureContent` | `CaptureContent` | Whether to capture message content | +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. + ### File export To write traces to a local file instead of an OTLP endpoint: diff --git a/docs/observability/opentelemetry.md b/docs/observability/opentelemetry.md index d89f68ca9a..af7ad4caa6 100644 --- a/docs/observability/opentelemetry.md +++ b/docs/observability/opentelemetry.md @@ -104,11 +104,16 @@ let client = Client::start(ClientOptions::new() | Option | Node.js | Python | Go | .NET | Java | Rust | Description | |---|---|---|---|---|---|---|---| | OTLP endpoint | `otlpEndpoint` | `otlp_endpoint` | `OTLPEndpoint` | `OtlpEndpoint` | `otlpEndpoint` | `otlp_endpoint` | OTLP HTTP endpoint URL | +| OTLP protocol | `otlpProtocol` | `otlp_protocol` | `OTLPProtocol` | `OtlpProtocol` | `otlpProtocol` | `otlp_protocol` | OTLP HTTP protocol for all signals: `"http/json"` or `"http/protobuf"` | +| OTLP traces protocol | `otlpTracesProtocol` | `otlp_traces_protocol` | `OTLPTracesProtocol` | `OtlpTracesProtocol` | `otlpTracesProtocol` | `otlp_traces_protocol` | OTLP HTTP protocol override for traces | +| OTLP metrics protocol | `otlpMetricsProtocol` | `otlp_metrics_protocol` | `OTLPMetricsProtocol` | `OtlpMetricsProtocol` | `otlpMetricsProtocol` | `otlp_metrics_protocol` | OTLP HTTP protocol override for metrics | | File path | `filePath` | `file_path` | `FilePath` | `FilePath` | `filePath` | `file_path` | File path for JSON-lines trace output | | Exporter type | `exporterType` | `exporter_type` | `ExporterType` | `ExporterType` | `exporterType` | `exporter_type` | `"otlp-http"` or `"file"` | | Source name | `sourceName` | `source_name` | `SourceName` | `SourceName` | `sourceName` | `source_name` | Instrumentation scope name | | Capture content | `captureContent` | `capture_content` | `CaptureContent` | `CaptureContent` | `captureContent` | `capture_content` | Whether to capture message content | +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. + ### Trace context propagation > **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. diff --git a/dotnet/README.md b/dotnet/README.md index c2829277d6..d51b9629c4 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -738,6 +738,9 @@ var client = new CopilotClient(new CopilotClientOptions **TelemetryConfig properties:** - `OtlpEndpoint` - OTLP HTTP endpoint URL +- `OtlpProtocol` - OTLP HTTP protocol for all signals (`"http/json"` or `"http/protobuf"`) +- `OtlpTracesProtocol` - OTLP HTTP protocol override for traces +- `OtlpMetricsProtocol` - OTLP HTTP protocol override for metrics - `FilePath` - File path for JSON-lines trace output - `ExporterType` - `"otlp-http"` or `"file"` - `SourceName` - Instrumentation scope name diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index e98513c533..45d08d1738 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1805,6 +1805,9 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) { startInfo.Environment["COPILOT_OTEL_ENABLED"] = "true"; if (telemetry.OtlpEndpoint is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_ENDPOINT"] = telemetry.OtlpEndpoint; + if (telemetry.OtlpProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_PROTOCOL"] = telemetry.OtlpProtocol; + if (telemetry.OtlpTracesProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] = telemetry.OtlpTracesProtocol; + if (telemetry.OtlpMetricsProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] = telemetry.OtlpMetricsProtocol; if (telemetry.FilePath is not null) startInfo.Environment["COPILOT_OTEL_FILE_EXPORTER_PATH"] = telemetry.FilePath; if (telemetry.ExporterType is not null) startInfo.Environment["COPILOT_OTEL_EXPORTER_TYPE"] = telemetry.ExporterType; if (telemetry.SourceName is not null) startInfo.Environment["COPILOT_OTEL_SOURCE_NAME"] = telemetry.SourceName; diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 7a2ad29518..c05ea93296 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -413,6 +413,30 @@ public sealed class TelemetryConfig /// public string? OtlpEndpoint { get; set; } + /// + /// OTLP HTTP protocol for all signals ("http/json" or "http/protobuf"). + /// + /// + /// Maps to the OTEL_EXPORTER_OTLP_PROTOCOL environment variable. + /// + public string? OtlpProtocol { get; set; } + + /// + /// OTLP HTTP protocol for traces ("http/json" or "http/protobuf"). + /// + /// + /// Maps to the OTEL_EXPORTER_OTLP_TRACES_PROTOCOL environment variable. + /// + public string? OtlpTracesProtocol { get; set; } + + /// + /// OTLP HTTP protocol for metrics ("http/json" or "http/protobuf"). + /// + /// + /// Maps to the OTEL_EXPORTER_OTLP_METRICS_PROTOCOL environment variable. + /// + public string? OtlpMetricsProtocol { get; set; } + /// /// File path for the file exporter. /// diff --git a/dotnet/test/E2E/ClientOptionsE2ETests.cs b/dotnet/test/E2E/ClientOptionsE2ETests.cs index 6360cb55a1..6e95654e37 100644 --- a/dotnet/test/E2E/ClientOptionsE2ETests.cs +++ b/dotnet/test/E2E/ClientOptionsE2ETests.cs @@ -78,6 +78,9 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli() Telemetry = new TelemetryConfig { OtlpEndpoint = "http://127.0.0.1:4318", + OtlpProtocol = "http/protobuf", + OtlpTracesProtocol = "http/json", + OtlpMetricsProtocol = "http/protobuf", FilePath = telemetryPath, ExporterType = "file", SourceName = "dotnet-sdk-e2e", @@ -104,6 +107,9 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli() Assert.Equal("process-option-token", capturedEnv.GetProperty("COPILOT_SDK_AUTH_TOKEN").GetString()); Assert.Equal("true", capturedEnv.GetProperty("COPILOT_OTEL_ENABLED").GetString()); Assert.Equal("http://127.0.0.1:4318", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_ENDPOINT").GetString()); + Assert.Equal("http/protobuf", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_PROTOCOL").GetString()); + Assert.Equal("http/json", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL").GetString()); + Assert.Equal("http/protobuf", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL").GetString()); Assert.Equal(telemetryPath, capturedEnv.GetProperty("COPILOT_OTEL_FILE_EXPORTER_PATH").GetString()); Assert.Equal("file", capturedEnv.GetProperty("COPILOT_OTEL_EXPORTER_TYPE").GetString()); 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() await session.DisposeAsync(); } + [Fact] + public async Task Should_Only_Set_Configured_Otlp_Protocol_Env_Vars() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + GitHubToken = "process-option-token", + Telemetry = new TelemetryConfig + { + OtlpTracesProtocol = "http/protobuf", + }, + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var capturedEnv = capture.RootElement.GetProperty("env"); + Assert.Equal("true", capturedEnv.GetProperty("COPILOT_OTEL_ENABLED").GetString()); + Assert.Equal("http/protobuf", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL").GetString()); + Assert.False(capturedEnv.TryGetProperty("OTEL_EXPORTER_OTLP_PROTOCOL", out _)); + Assert.False(capturedEnv.TryGetProperty("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", out _)); + } + [Fact] public async Task Should_Forward_EnableSessionTelemetry_In_Wire_Request() { @@ -642,6 +674,9 @@ function saveCapture() { COPILOT_SDK_AUTH_TOKEN: process.env.COPILOT_SDK_AUTH_TOKEN, COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL, + OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, + OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH, COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE, COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME, diff --git a/dotnet/test/Unit/TelemetryTests.cs b/dotnet/test/Unit/TelemetryTests.cs index 9229285b9b..e275184a0c 100644 --- a/dotnet/test/Unit/TelemetryTests.cs +++ b/dotnet/test/Unit/TelemetryTests.cs @@ -16,6 +16,9 @@ public void TelemetryConfig_DefaultValues_AreNull() var config = new TelemetryConfig(); Assert.Null(config.OtlpEndpoint); + Assert.Null(config.OtlpProtocol); + Assert.Null(config.OtlpTracesProtocol); + Assert.Null(config.OtlpMetricsProtocol); Assert.Null(config.FilePath); Assert.Null(config.ExporterType); Assert.Null(config.SourceName); @@ -28,6 +31,9 @@ public void TelemetryConfig_CanSetAllProperties() var config = new TelemetryConfig { OtlpEndpoint = "http://localhost:4318", + OtlpProtocol = "http/protobuf", + OtlpTracesProtocol = "http/json", + OtlpMetricsProtocol = "http/protobuf", FilePath = "/tmp/traces.json", ExporterType = "otlp-http", SourceName = "my-app", @@ -35,6 +41,9 @@ public void TelemetryConfig_CanSetAllProperties() }; Assert.Equal("http://localhost:4318", config.OtlpEndpoint); + Assert.Equal("http/protobuf", config.OtlpProtocol); + Assert.Equal("http/json", config.OtlpTracesProtocol); + Assert.Equal("http/protobuf", config.OtlpMetricsProtocol); Assert.Equal("/tmp/traces.json", config.FilePath); Assert.Equal("otlp-http", config.ExporterType); Assert.Equal("my-app", config.SourceName); diff --git a/go/README.md b/go/README.md index 4bdbc75f51..b9ce90d248 100644 --- a/go/README.md +++ b/go/README.md @@ -573,6 +573,9 @@ client, err := copilot.NewClient(copilot.ClientOptions{ **TelemetryConfig fields:** - `OTLPEndpoint` (string): OTLP HTTP endpoint URL +- `OTLPProtocol` (string): OTLP HTTP protocol for all signals (`"http/json"` or `"http/protobuf"`) +- `OTLPTracesProtocol` (string): OTLP HTTP protocol override for traces +- `OTLPMetricsProtocol` (string): OTLP HTTP protocol override for metrics - `FilePath` (string): File path for JSON-lines trace output - `ExporterType` (string): `"otlp-http"` or `"file"` - `SourceName` (string): Instrumentation scope name diff --git a/go/client.go b/go/client.go index cad460557d..f76431ce1e 100644 --- a/go/client.go +++ b/go/client.go @@ -1695,6 +1695,15 @@ func (c *Client) startCLIServer(ctx context.Context) error { if t.OTLPEndpoint != "" { c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_ENDPOINT", t.OTLPEndpoint) } + if t.OTLPProtocol != "" { + c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_PROTOCOL", t.OTLPProtocol) + } + if t.OTLPTracesProtocol != "" { + c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", t.OTLPTracesProtocol) + } + if t.OTLPMetricsProtocol != "" { + c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", t.OTLPMetricsProtocol) + } if t.FilePath != "" { c.process.Env = setEnvValue(c.process.Env, "COPILOT_OTEL_FILE_EXPORTER_PATH", t.FilePath) } diff --git a/go/internal/e2e/client_options_e2e_test.go b/go/internal/e2e/client_options_e2e_test.go index 4f8b74f2be..c291e46ef8 100644 --- a/go/internal/e2e/client_options_e2e_test.go +++ b/go/internal/e2e/client_options_e2e_test.go @@ -109,11 +109,14 @@ func TestClientOptionsE2E(t *testing.T) { opts.LogLevel = "debug" opts.SessionIdleTimeoutSeconds = 17 opts.Telemetry = &copilot.TelemetryConfig{ - OTLPEndpoint: "http://127.0.0.1:4318", - FilePath: telemetryPath, - ExporterType: "file", - SourceName: "go-sdk-e2e", - CaptureContent: copilot.Bool(true), + OTLPEndpoint: "http://127.0.0.1:4318", + OTLPProtocol: "http/protobuf", + OTLPTracesProtocol: "http/json", + OTLPMetricsProtocol: "http/protobuf", + FilePath: telemetryPath, + ExporterType: "file", + SourceName: "go-sdk-e2e", + CaptureContent: copilot.Bool(true), } opts.UseLoggedInUser = copilot.Bool(false) }) @@ -147,6 +150,9 @@ func TestClientOptionsE2E(t *testing.T) { "COPILOT_SDK_AUTH_TOKEN": "process-option-token", "COPILOT_OTEL_ENABLED": "true", "OTEL_EXPORTER_OTLP_ENDPOINT": "http://127.0.0.1:4318", + "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf", + "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL": "http/json", + "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": "http/protobuf", "COPILOT_OTEL_FILE_EXPORTER_PATH": telemetryPath, "COPILOT_OTEL_EXPORTER_TYPE": "file", "COPILOT_OTEL_SOURCE_NAME": "go-sdk-e2e", @@ -195,6 +201,46 @@ func TestClientOptionsE2E(t *testing.T) { t.Errorf("Expected session.create.params.includeSubAgentStreamingEvents=false, got %v", params["includeSubAgentStreamingEvents"]) } }) + + t.Run("should only set configured OTLP protocol env vars", func(t *testing.T) { + ctx := testharness.NewTestContext(t) + + cliPath := filepath.Join(ctx.WorkDir, "fake-cli-"+randomHex(t)+".js") + capturePath := filepath.Join(ctx.WorkDir, "fake-cli-capture-"+randomHex(t)+".json") + if err := os.WriteFile(cliPath, []byte(fakeStdioCliScript), 0644); err != nil { + t.Fatalf("Failed to write fake CLI script: %v", err) + } + + client := ctx.NewClient(func(opts *copilot.ClientOptions) { + opts.Connection = copilot.StdioConnection{ + Path: cliPath, + Args: []string{"--capture-file", capturePath}, + } + opts.GitHubToken = "process-option-token" + opts.Telemetry = &copilot.TelemetryConfig{ + OTLPTracesProtocol: "http/protobuf", + } + opts.UseLoggedInUser = copilot.Bool(false) + }) + t.Cleanup(func() { client.ForceStop() }) + + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + + capture := readCapture(t, capturePath) + if got := capture.Env["COPILOT_OTEL_ENABLED"]; got != "true" { + t.Errorf("Expected COPILOT_OTEL_ENABLED=true, got %q", got) + } + if got := capture.Env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"]; got != "http/protobuf" { + t.Errorf("Expected OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf, got %q", got) + } + for _, key := range []string{"OTEL_EXPORTER_OTLP_PROTOCOL", "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"} { + if got, ok := capture.Env[key]; ok { + t.Errorf("Expected %s to be unset, got %q", key, got) + } + } + }) } // --------------------------------------------------------------------------- @@ -372,6 +418,9 @@ function saveCapture() { COPILOT_SDK_AUTH_TOKEN: process.env.COPILOT_SDK_AUTH_TOKEN, COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL, + OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, + OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH, COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE, COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME, diff --git a/go/internal/e2e/telemetry_e2e_test.go b/go/internal/e2e/telemetry_e2e_test.go index 0710302812..998fd86807 100644 --- a/go/internal/e2e/telemetry_e2e_test.go +++ b/go/internal/e2e/telemetry_e2e_test.go @@ -307,6 +307,15 @@ func TestTelemetryConfigUnit(t *testing.T) { if cfg.OTLPEndpoint != "" { t.Errorf("Expected empty OTLPEndpoint, got %q", cfg.OTLPEndpoint) } + if cfg.OTLPProtocol != "" { + t.Errorf("Expected empty OTLPProtocol, got %q", cfg.OTLPProtocol) + } + if cfg.OTLPTracesProtocol != "" { + t.Errorf("Expected empty OTLPTracesProtocol, got %q", cfg.OTLPTracesProtocol) + } + if cfg.OTLPMetricsProtocol != "" { + t.Errorf("Expected empty OTLPMetricsProtocol, got %q", cfg.OTLPMetricsProtocol) + } if cfg.FilePath != "" { t.Errorf("Expected empty FilePath, got %q", cfg.FilePath) } @@ -324,15 +333,27 @@ func TestTelemetryConfigUnit(t *testing.T) { t.Run("can set all properties", func(t *testing.T) { // Mirrors: TelemetryConfig_CanSetAllProperties cfg := copilot.TelemetryConfig{ - OTLPEndpoint: "http://localhost:4318", - FilePath: "/tmp/traces.json", - ExporterType: "otlp-http", - SourceName: "my-app", - CaptureContent: copilot.Bool(true), + OTLPEndpoint: "http://localhost:4318", + OTLPProtocol: "http/protobuf", + OTLPTracesProtocol: "http/json", + OTLPMetricsProtocol: "http/protobuf", + FilePath: "/tmp/traces.json", + ExporterType: "otlp-http", + SourceName: "my-app", + CaptureContent: copilot.Bool(true), } if cfg.OTLPEndpoint != "http://localhost:4318" { t.Errorf("OTLPEndpoint mismatch: %q", cfg.OTLPEndpoint) } + if cfg.OTLPProtocol != "http/protobuf" { + t.Errorf("OTLPProtocol mismatch: %q", cfg.OTLPProtocol) + } + if cfg.OTLPTracesProtocol != "http/json" { + t.Errorf("OTLPTracesProtocol mismatch: %q", cfg.OTLPTracesProtocol) + } + if cfg.OTLPMetricsProtocol != "http/protobuf" { + t.Errorf("OTLPMetricsProtocol mismatch: %q", cfg.OTLPMetricsProtocol) + } if cfg.FilePath != "/tmp/traces.json" { t.Errorf("FilePath mismatch: %q", cfg.FilePath) } diff --git a/go/types.go b/go/types.go index 6245ce519e..ed538bbf22 100644 --- a/go/types.go +++ b/go/types.go @@ -159,6 +159,18 @@ type TelemetryConfig struct { // Sets OTEL_EXPORTER_OTLP_ENDPOINT. OTLPEndpoint string + // OTLPProtocol is the OTLP HTTP protocol for all signals. + // Sets OTEL_EXPORTER_OTLP_PROTOCOL. + OTLPProtocol string + + // OTLPTracesProtocol is the OTLP HTTP protocol for traces. + // Sets OTEL_EXPORTER_OTLP_TRACES_PROTOCOL. + OTLPTracesProtocol string + + // OTLPMetricsProtocol is the OTLP HTTP protocol for metrics. + // Sets OTEL_EXPORTER_OTLP_METRICS_PROTOCOL. + OTLPMetricsProtocol string + // FilePath is the file path for JSON-lines trace output. // Sets COPILOT_OTEL_FILE_EXPORTER_PATH. FilePath string diff --git a/java/src/main/java/com/github/copilot/CliServerManager.java b/java/src/main/java/com/github/copilot/CliServerManager.java index a6a08a8482..af3e01546b 100644 --- a/java/src/main/java/com/github/copilot/CliServerManager.java +++ b/java/src/main/java/com/github/copilot/CliServerManager.java @@ -150,6 +150,15 @@ ProcessInfo startCliServer() throws IOException, InterruptedException { if (telemetry.getOtlpEndpoint() != null) { pb.environment().put("OTEL_EXPORTER_OTLP_ENDPOINT", telemetry.getOtlpEndpoint()); } + if (telemetry.getOtlpProtocol() != null) { + pb.environment().put("OTEL_EXPORTER_OTLP_PROTOCOL", telemetry.getOtlpProtocol()); + } + if (telemetry.getOtlpTracesProtocol() != null) { + pb.environment().put("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", telemetry.getOtlpTracesProtocol()); + } + if (telemetry.getOtlpMetricsProtocol() != null) { + pb.environment().put("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", telemetry.getOtlpMetricsProtocol()); + } if (telemetry.getFilePath() != null) { pb.environment().put("COPILOT_OTEL_FILE_EXPORTER_PATH", telemetry.getFilePath()); } diff --git a/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java b/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java index c0b75f29d6..e73b2a5436 100644 --- a/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java @@ -30,6 +30,9 @@ public class TelemetryConfig { private String otlpEndpoint; + private String otlpProtocol; + private String otlpTracesProtocol; + private String otlpMetricsProtocol; private String filePath; private String exporterType; private String sourceName; @@ -58,6 +61,75 @@ public TelemetryConfig setOtlpEndpoint(String otlpEndpoint) { return this; } + /** + * Gets the OTLP HTTP protocol for all signals. + *

+ * Maps to the {@code OTEL_EXPORTER_OTLP_PROTOCOL} environment variable. + * + * @return the OTLP HTTP protocol, or {@code null} + */ + public String getOtlpProtocol() { + return otlpProtocol; + } + + /** + * Sets the OTLP HTTP protocol for all signals. + * + * @param otlpProtocol + * the protocol ({@code "http/json"} or {@code "http/protobuf"}) + * @return this config for method chaining + */ + public TelemetryConfig setOtlpProtocol(String otlpProtocol) { + this.otlpProtocol = otlpProtocol; + return this; + } + + /** + * Gets the OTLP HTTP protocol for traces. + *

+ * Maps to the {@code OTEL_EXPORTER_OTLP_TRACES_PROTOCOL} environment variable. + * + * @return the traces protocol, or {@code null} + */ + public String getOtlpTracesProtocol() { + return otlpTracesProtocol; + } + + /** + * Sets the OTLP HTTP protocol for traces. + * + * @param otlpTracesProtocol + * the protocol ({@code "http/json"} or {@code "http/protobuf"}) + * @return this config for method chaining + */ + public TelemetryConfig setOtlpTracesProtocol(String otlpTracesProtocol) { + this.otlpTracesProtocol = otlpTracesProtocol; + return this; + } + + /** + * Gets the OTLP HTTP protocol for metrics. + *

+ * Maps to the {@code OTEL_EXPORTER_OTLP_METRICS_PROTOCOL} environment variable. + * + * @return the metrics protocol, or {@code null} + */ + public String getOtlpMetricsProtocol() { + return otlpMetricsProtocol; + } + + /** + * Sets the OTLP HTTP protocol for metrics. + * + * @param otlpMetricsProtocol + * the protocol ({@code "http/json"} or {@code "http/protobuf"}) + * @return this config for method chaining + */ + public TelemetryConfig setOtlpMetricsProtocol(String otlpMetricsProtocol) { + this.otlpMetricsProtocol = otlpMetricsProtocol; + return this; + } + /** * Gets the file path for the file exporter. *

diff --git a/java/src/test/java/com/github/copilot/CliServerManagerTest.java b/java/src/test/java/com/github/copilot/CliServerManagerTest.java index 2df5dafabb..e50c278dfb 100644 --- a/java/src/test/java/com/github/copilot/CliServerManagerTest.java +++ b/java/src/test/java/com/github/copilot/CliServerManagerTest.java @@ -231,8 +231,10 @@ void startCliServerWithNullCliPath() throws Exception { void startCliServerWithTelemetryAllOptions() throws Exception { // The telemetry env vars are applied before ProcessBuilder.start() // so even with a nonexistent CLI path, the telemetry code path is exercised - var telemetry = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setFilePath("/tmp/telemetry.log") - .setExporterType("otlp-http").setSourceName("test-app").setCaptureContent(true); + var telemetry = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setOtlpProtocol("http/protobuf") + .setOtlpTracesProtocol("http/json").setOtlpMetricsProtocol("http/protobuf") + .setFilePath("/tmp/telemetry.log").setExporterType("otlp-http").setSourceName("test-app") + .setCaptureContent(true); var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setTelemetry(telemetry).setUseStdio(true); var manager = new CliServerManager(options); diff --git a/java/src/test/java/com/github/copilot/TelemetryConfigTest.java b/java/src/test/java/com/github/copilot/TelemetryConfigTest.java index 2dd41c28a7..2fc0ea9f03 100644 --- a/java/src/test/java/com/github/copilot/TelemetryConfigTest.java +++ b/java/src/test/java/com/github/copilot/TelemetryConfigTest.java @@ -19,6 +19,9 @@ class TelemetryConfigTest { void defaultValuesAreNull() { var config = new TelemetryConfig(); assertNull(config.getOtlpEndpoint()); + assertNull(config.getOtlpProtocol()); + assertNull(config.getOtlpTracesProtocol()); + assertNull(config.getOtlpMetricsProtocol()); assertNull(config.getFilePath()); assertNull(config.getExporterType()); assertNull(config.getSourceName()); @@ -32,6 +35,27 @@ void otlpEndpointGetterSetter() { assertEquals("http://localhost:4318", config.getOtlpEndpoint()); } + @Test + void otlpProtocolGetterSetter() { + var config = new TelemetryConfig(); + config.setOtlpProtocol("http/protobuf"); + assertEquals("http/protobuf", config.getOtlpProtocol()); + } + + @Test + void otlpTracesProtocolGetterSetter() { + var config = new TelemetryConfig(); + config.setOtlpTracesProtocol("http/json"); + assertEquals("http/json", config.getOtlpTracesProtocol()); + } + + @Test + void otlpMetricsProtocolGetterSetter() { + var config = new TelemetryConfig(); + config.setOtlpMetricsProtocol("http/protobuf"); + assertEquals("http/protobuf", config.getOtlpMetricsProtocol()); + } + @Test void filePathGetterSetter() { var config = new TelemetryConfig(); @@ -65,10 +89,15 @@ void captureContentGetterSetter() { @Test void fluentChainingReturnsThis() { - var config = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setFilePath("/tmp/spans.json") - .setExporterType("file").setSourceName("sdk-test").setCaptureContent(true); + var config = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setOtlpProtocol("http/protobuf") + .setOtlpTracesProtocol("http/json").setOtlpMetricsProtocol("http/protobuf") + .setFilePath("/tmp/spans.json").setExporterType("file").setSourceName("sdk-test") + .setCaptureContent(true); assertEquals("http://localhost:4318", config.getOtlpEndpoint()); + assertEquals("http/protobuf", config.getOtlpProtocol()); + assertEquals("http/json", config.getOtlpTracesProtocol()); + assertEquals("http/protobuf", config.getOtlpMetricsProtocol()); assertEquals("/tmp/spans.json", config.getFilePath()); assertEquals("file", config.getExporterType()); assertEquals("sdk-test", config.getSourceName()); diff --git a/nodejs/README.md b/nodejs/README.md index 91aa5c4be7..240efe3c8f 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -786,6 +786,9 @@ With just this configuration, the CLI emits spans for every session, message, an **TelemetryConfig options:** - `otlpEndpoint?: string` - OTLP HTTP endpoint URL +- `otlpProtocol?: "http/json" | "http/protobuf"` - OTLP HTTP protocol for all signals +- `otlpTracesProtocol?: "http/json" | "http/protobuf"` - OTLP HTTP protocol override for traces +- `otlpMetricsProtocol?: "http/json" | "http/protobuf"` - OTLP HTTP protocol override for metrics - `filePath?: string` - File path for JSON-lines trace output - `exporterType?: string` - `"otlp-http"` or `"file"` - `sourceName?: string` - Instrumentation scope name diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index c59bd3e942..a72d0ab1bb 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -1929,6 +1929,12 @@ export class CopilotClient { envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true"; if (t.otlpEndpoint !== undefined) envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint; + if (t.otlpProtocol !== undefined) + envWithoutNodeDebug.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol; + if (t.otlpTracesProtocol !== undefined) + envWithoutNodeDebug.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = t.otlpTracesProtocol; + if (t.otlpMetricsProtocol !== undefined) + envWithoutNodeDebug.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = t.otlpMetricsProtocol; if (t.filePath !== undefined) envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath; if (t.exporterType !== undefined) diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 647ca79c11..e7864e22b3 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -55,6 +55,12 @@ export type TraceContextProvider = () => TraceContext | Promise; export interface TelemetryConfig { /** OTLP HTTP endpoint URL for trace/metric export. Sets OTEL_EXPORTER_OTLP_ENDPOINT. */ otlpEndpoint?: string; + /** OTLP HTTP protocol for all signals. Sets OTEL_EXPORTER_OTLP_PROTOCOL. */ + otlpProtocol?: "http/json" | "http/protobuf"; + /** OTLP HTTP protocol for traces. Sets OTEL_EXPORTER_OTLP_TRACES_PROTOCOL. */ + otlpTracesProtocol?: "http/json" | "http/protobuf"; + /** OTLP HTTP protocol for metrics. Sets OTEL_EXPORTER_OTLP_METRICS_PROTOCOL. */ + otlpMetricsProtocol?: "http/json" | "http/protobuf"; /** File path for JSON-lines trace output. Sets COPILOT_OTEL_FILE_EXPORTER_PATH. */ filePath?: string; /** Exporter backend type: "otlp-http" or "file". Sets COPILOT_OTEL_EXPORTER_TYPE. */ diff --git a/nodejs/test/e2e/client_options.e2e.test.ts b/nodejs/test/e2e/client_options.e2e.test.ts index dadce08e11..8eb4a76b5c 100644 --- a/nodejs/test/e2e/client_options.e2e.test.ts +++ b/nodejs/test/e2e/client_options.e2e.test.ts @@ -29,6 +29,9 @@ function saveCapture() { COPILOT_SDK_AUTH_TOKEN: process.env.COPILOT_SDK_AUTH_TOKEN, COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL, + OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, + OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH, COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE, COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME, @@ -247,6 +250,9 @@ describe("Client options", async () => { sessionIdleTimeoutSeconds: 17, telemetry: { otlpEndpoint: "http://127.0.0.1:4318", + otlpProtocol: "http/protobuf", + otlpTracesProtocol: "http/json", + otlpMetricsProtocol: "http/protobuf", filePath: telemetryPath, exporterType: "file", sourceName: "ts-sdk-e2e", @@ -283,6 +289,9 @@ describe("Client options", async () => { expect(capture.env.COPILOT_SDK_AUTH_TOKEN).toBe("process-option-token"); expect(capture.env.COPILOT_OTEL_ENABLED).toBe("true"); expect(capture.env.OTEL_EXPORTER_OTLP_ENDPOINT).toBe("http://127.0.0.1:4318"); + expect(capture.env.OTEL_EXPORTER_OTLP_PROTOCOL).toBe("http/protobuf"); + expect(capture.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL).toBe("http/json"); + expect(capture.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL).toBe("http/protobuf"); expect(capture.env.COPILOT_OTEL_FILE_EXPORTER_PATH).toBe(telemetryPath); expect(capture.env.COPILOT_OTEL_EXPORTER_TYPE).toBe("file"); expect(capture.env.COPILOT_OTEL_SOURCE_NAME).toBe("ts-sdk-e2e"); @@ -315,6 +324,49 @@ describe("Client options", async () => { await session.disconnect(); }); + it("should only set configured otlp protocol env vars", async () => { + const cliPath = path.join( + workDir, + `fake-cli-${Date.now()}-${Math.random().toString(36).slice(2)}.js` + ); + const capturePath = path.join( + workDir, + `fake-cli-capture-${Date.now()}-${Math.random().toString(36).slice(2)}.json` + ); + fs.writeFileSync(cliPath, FAKE_STDIO_CLI_SCRIPT); + + const client = new CopilotClient({ + workingDirectory: workDir, + env, + connection: RuntimeConnection.forStdio({ + path: cliPath, + args: ["--capture-file", capturePath], + }), + gitHubToken: "process-option-token", + telemetry: { + otlpTracesProtocol: "http/protobuf", + }, + useLoggedInUser: false, + }); + onTestFinished(async () => { + try { + await client.forceStop(); + } catch { + // Ignore cleanup errors + } + }); + + await client.start(); + + const capture = JSON.parse(fs.readFileSync(capturePath, "utf8")) as { + env: Record; + }; + expect(capture.env.COPILOT_OTEL_ENABLED).toBe("true"); + expect(capture.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL).toBe("http/protobuf"); + expect(capture.env).not.toHaveProperty("OTEL_EXPORTER_OTLP_PROTOCOL"); + expect(capture.env).not.toHaveProperty("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"); + }); + it("should throw when gitHubToken used with forUri", () => { expect(() => { new CopilotClient({ diff --git a/nodejs/test/telemetry.test.ts b/nodejs/test/telemetry.test.ts index 9ad97b63a1..5889810e12 100644 --- a/nodejs/test/telemetry.test.ts +++ b/nodejs/test/telemetry.test.ts @@ -64,6 +64,9 @@ describe("telemetry", () => { it("sets correct env vars for full telemetry config", async () => { const telemetry = { otlpEndpoint: "http://localhost:4318", + otlpProtocol: "http/protobuf", + otlpTracesProtocol: "http/json", + otlpMetricsProtocol: "http/protobuf", filePath: "/tmp/traces.jsonl", exporterType: "otlp-http", sourceName: "my-app", @@ -76,6 +79,11 @@ describe("telemetry", () => { const t = telemetry; env.COPILOT_OTEL_ENABLED = "true"; if (t.otlpEndpoint !== undefined) env.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint; + if (t.otlpProtocol !== undefined) env.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol; + if (t.otlpTracesProtocol !== undefined) + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = t.otlpTracesProtocol; + if (t.otlpMetricsProtocol !== undefined) + env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = t.otlpMetricsProtocol; if (t.filePath !== undefined) env.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath; if (t.exporterType !== undefined) env.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType; if (t.sourceName !== undefined) env.COPILOT_OTEL_SOURCE_NAME = t.sourceName; @@ -88,6 +96,9 @@ describe("telemetry", () => { expect(env).toEqual({ COPILOT_OTEL_ENABLED: "true", OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318", + OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf", + OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: "http/json", + OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: "http/protobuf", COPILOT_OTEL_FILE_EXPORTER_PATH: "/tmp/traces.jsonl", COPILOT_OTEL_EXPORTER_TYPE: "otlp-http", COPILOT_OTEL_SOURCE_NAME: "my-app", @@ -103,6 +114,11 @@ describe("telemetry", () => { const t = telemetry as any; env.COPILOT_OTEL_ENABLED = "true"; if (t.otlpEndpoint !== undefined) env.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint; + if (t.otlpProtocol !== undefined) env.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol; + if (t.otlpTracesProtocol !== undefined) + env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = t.otlpTracesProtocol; + if (t.otlpMetricsProtocol !== undefined) + env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = t.otlpMetricsProtocol; if (t.filePath !== undefined) env.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath; if (t.exporterType !== undefined) env.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType; if (t.sourceName !== undefined) env.COPILOT_OTEL_SOURCE_NAME = t.sourceName; diff --git a/python/README.md b/python/README.md index e77fcf16c5..015cc053ee 100644 --- a/python/README.md +++ b/python/README.md @@ -557,6 +557,9 @@ client = CopilotClient( **TelemetryConfig options:** - `otlp_endpoint` (str): OTLP HTTP endpoint URL +- `otlp_protocol` (str): OTLP HTTP protocol for all signals (`"http/json"` or `"http/protobuf"`) +- `otlp_traces_protocol` (str): OTLP HTTP protocol override for traces +- `otlp_metrics_protocol` (str): OTLP HTTP protocol override for metrics - `file_path` (str): File path for JSON-lines trace output - `exporter_type` (str): `"otlp-http"` or `"file"` - `source_name` (str): Instrumentation scope name diff --git a/python/copilot/client.py b/python/copilot/client.py index 24eec9d727..99a9949119 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -182,6 +182,12 @@ class TelemetryConfig(TypedDict, total=False): otlp_endpoint: str """OTLP HTTP endpoint URL for trace/metric export. Sets OTEL_EXPORTER_OTLP_ENDPOINT.""" + otlp_protocol: str + """OTLP HTTP protocol for all signals. Sets OTEL_EXPORTER_OTLP_PROTOCOL.""" + otlp_traces_protocol: str + """OTLP HTTP protocol for traces. Sets OTEL_EXPORTER_OTLP_TRACES_PROTOCOL.""" + otlp_metrics_protocol: str + """OTLP HTTP protocol for metrics. Sets OTEL_EXPORTER_OTLP_METRICS_PROTOCOL.""" file_path: str """File path for JSON-lines trace output. Sets COPILOT_OTEL_FILE_EXPORTER_PATH.""" exporter_type: str @@ -3225,6 +3231,12 @@ async def _start_cli_server(self) -> None: env["COPILOT_OTEL_ENABLED"] = "true" if "otlp_endpoint" in telemetry: env["OTEL_EXPORTER_OTLP_ENDPOINT"] = telemetry["otlp_endpoint"] + if "otlp_protocol" in telemetry: + env["OTEL_EXPORTER_OTLP_PROTOCOL"] = telemetry["otlp_protocol"] + if "otlp_traces_protocol" in telemetry: + env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] = telemetry["otlp_traces_protocol"] + if "otlp_metrics_protocol" in telemetry: + env["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] = telemetry["otlp_metrics_protocol"] if "file_path" in telemetry: env["COPILOT_OTEL_FILE_EXPORTER_PATH"] = telemetry["file_path"] if "exporter_type" in telemetry: diff --git a/python/e2e/test_client_options_e2e.py b/python/e2e/test_client_options_e2e.py index f32d923a38..04b9a8c228 100644 --- a/python/e2e/test_client_options_e2e.py +++ b/python/e2e/test_client_options_e2e.py @@ -92,6 +92,9 @@ def _get_available_port() -> int: COPILOT_SDK_AUTH_TOKEN: process.env.COPILOT_SDK_AUTH_TOKEN, COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL, + OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, + OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH, COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE, COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME, @@ -218,6 +221,9 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes session_idle_timeout_seconds=17, telemetry={ "otlp_endpoint": "http://127.0.0.1:4318", + "otlp_protocol": "http/protobuf", + "otlp_traces_protocol": "http/json", + "otlp_metrics_protocol": "http/protobuf", "file_path": telemetry_path, "exporter_type": "file", "source_name": "python-sdk-e2e", @@ -246,6 +252,9 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes assert env["COPILOT_SDK_AUTH_TOKEN"] == "process-option-token" assert env["COPILOT_OTEL_ENABLED"] == "true" assert env["OTEL_EXPORTER_OTLP_ENDPOINT"] == "http://127.0.0.1:4318" + assert env["OTEL_EXPORTER_OTLP_PROTOCOL"] == "http/protobuf" + assert env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] == "http/json" + assert env["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] == "http/protobuf" assert env["COPILOT_OTEL_FILE_EXPORTER_PATH"] == telemetry_path assert env["COPILOT_OTEL_EXPORTER_TYPE"] == "file" assert env["COPILOT_OTEL_SOURCE_NAME"] == "python-sdk-e2e" @@ -274,3 +283,38 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes await client.stop() except Exception: await client.force_stop() + + async def test_should_only_set_configured_otlp_protocol_env_vars(self, ctx: E2ETestContext): + cli_path = os.path.join(ctx.work_dir, "fake-cli-protocol-partial.js") + capture_path = os.path.join(ctx.work_dir, "fake-cli-protocol-partial-capture.json") + with open(cli_path, "w") as f: + f.write(FAKE_STDIO_CLI_SCRIPT) + + client = CopilotClient( + **_make_options( + ctx, + cli_path=cli_path, + cli_args=["--capture-file", capture_path], + github_token="process-option-token", + telemetry={ + "otlp_traces_protocol": "http/protobuf", + }, + use_logged_in_user=False, + ), + ) + try: + await client.start() + + with open(capture_path) as f: + capture = json.load(f) + env = capture["env"] + + assert env["COPILOT_OTEL_ENABLED"] == "true" + assert env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] == "http/protobuf" + assert "OTEL_EXPORTER_OTLP_PROTOCOL" not in env + assert "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL" not in env + finally: + try: + await client.stop() + except Exception: + await client.force_stop() diff --git a/python/e2e/test_telemetry_e2e.py b/python/e2e/test_telemetry_e2e.py index f18a9fb882..fc12b1f3ac 100644 --- a/python/e2e/test_telemetry_e2e.py +++ b/python/e2e/test_telemetry_e2e.py @@ -186,6 +186,9 @@ async def test_default_values_are_unset(self): # constructor leaves every field unset (equivalent to C#'s null defaults). cfg: TelemetryConfig = TelemetryConfig() assert cfg.get("otlp_endpoint") is None + assert cfg.get("otlp_protocol") is None + assert cfg.get("otlp_traces_protocol") is None + assert cfg.get("otlp_metrics_protocol") is None assert cfg.get("file_path") is None assert cfg.get("exporter_type") is None assert cfg.get("source_name") is None @@ -194,12 +197,18 @@ async def test_default_values_are_unset(self): async def test_can_set_all_properties(self): cfg: TelemetryConfig = TelemetryConfig( otlp_endpoint="http://localhost:4318", + otlp_protocol="http/protobuf", + otlp_traces_protocol="http/json", + otlp_metrics_protocol="http/protobuf", file_path="/tmp/traces.json", exporter_type="otlp-http", source_name="my-app", capture_content=True, ) assert cfg["otlp_endpoint"] == "http://localhost:4318" + assert cfg["otlp_protocol"] == "http/protobuf" + assert cfg["otlp_traces_protocol"] == "http/json" + assert cfg["otlp_metrics_protocol"] == "http/protobuf" assert cfg["file_path"] == "/tmp/traces.json" assert cfg["exporter_type"] == "otlp-http" assert cfg["source_name"] == "my-app" diff --git a/python/test_telemetry.py b/python/test_telemetry.py index 6481fd525d..0d2bd75ef6 100644 --- a/python/test_telemetry.py +++ b/python/test_telemetry.py @@ -77,6 +77,9 @@ def test_telemetry_env_var_mapping(self): """TelemetryConfig fields map to expected environment variable names.""" config: TelemetryConfig = { "otlp_endpoint": "http://localhost:4318", + "otlp_protocol": "http/protobuf", + "otlp_traces_protocol": "http/json", + "otlp_metrics_protocol": "http/protobuf", "file_path": "/tmp/traces.jsonl", "exporter_type": "file", "source_name": "test-app", @@ -87,6 +90,12 @@ def test_telemetry_env_var_mapping(self): env["COPILOT_OTEL_ENABLED"] = "true" if "otlp_endpoint" in config: env["OTEL_EXPORTER_OTLP_ENDPOINT"] = config["otlp_endpoint"] + if "otlp_protocol" in config: + env["OTEL_EXPORTER_OTLP_PROTOCOL"] = config["otlp_protocol"] + if "otlp_traces_protocol" in config: + env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] = config["otlp_traces_protocol"] + if "otlp_metrics_protocol" in config: + env["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] = config["otlp_metrics_protocol"] if "file_path" in config: env["COPILOT_OTEL_FILE_EXPORTER_PATH"] = config["file_path"] if "exporter_type" in config: @@ -100,6 +109,9 @@ def test_telemetry_env_var_mapping(self): assert env["COPILOT_OTEL_ENABLED"] == "true" assert env["OTEL_EXPORTER_OTLP_ENDPOINT"] == "http://localhost:4318" + assert env["OTEL_EXPORTER_OTLP_PROTOCOL"] == "http/protobuf" + assert env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] == "http/json" + assert env["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] == "http/protobuf" assert env["COPILOT_OTEL_FILE_EXPORTER_PATH"] == "/tmp/traces.jsonl" assert env["COPILOT_OTEL_EXPORTER_TYPE"] == "file" assert env["COPILOT_OTEL_SOURCE_NAME"] == "test-app" diff --git a/rust/README.md b/rust/README.md index 0b5bec1cd5..52ccad635c 100644 --- a/rust/README.md +++ b/rust/README.md @@ -588,11 +588,12 @@ Provider types include `"openai"`, `"azure"`, and `"anthropic"`. Set `wire_api` Forward OpenTelemetry signals from the spawned CLI process to your collector: ```rust,ignore -use github_copilot_sdk::{ClientOptions, OtelExporterType, TelemetryConfig}; +use github_copilot_sdk::{ClientOptions, OtelExporterType, OtlpHttpProtocol, TelemetryConfig}; let mut telem = TelemetryConfig::default(); telem.exporter_type = Some(OtelExporterType::OtlpHttp); telem.otlp_endpoint = Some("http://localhost:4318".to_string()); +telem.otlp_protocol = Some(OtlpHttpProtocol::HttpProtobuf); telem.source_name = Some("my-app".to_string()); let mut opts = ClientOptions::default(); @@ -600,7 +601,7 @@ opts.telemetry = Some(telem); let client = Client::start(opts).await?; ``` -The SDK injects the appropriate environment variables (`COPILOT_OTEL_EXPORTER_TYPE`, `OTEL_EXPORTER_OTLP_ENDPOINT`, ...) into the spawned CLI process. The SDK takes no OpenTelemetry dependency; the CLI itself owns the exporter pipeline. Caller-supplied `ClientOptions::env` entries override telemetry-injected values. +The SDK injects the appropriate environment variables (`COPILOT_OTEL_EXPORTER_TYPE`, `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_PROTOCOL`, ...) into the spawned CLI process. The SDK takes no OpenTelemetry dependency; the CLI itself owns the exporter pipeline. Caller-supplied `ClientOptions::env` entries override telemetry-injected values. ### Progress Reporting (`send_and_wait`) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index cab34b4764..832e998616 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -402,6 +402,33 @@ impl OtelExporterType { } } +/// OTLP HTTP protocol used by the CLI's OpenTelemetry OTLP exporter. +/// +/// Maps to the standard `OTEL_EXPORTER_OTLP_PROTOCOL`, +/// `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL`, and +/// `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` environment variables on the spawned +/// CLI process. Wire values are `"http/json"` and `"http/protobuf"`. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub enum OtlpHttpProtocol { + /// Export using OTLP/HTTP JSON. + #[serde(rename = "http/json")] + HttpJson, + /// Export using OTLP/HTTP protobuf. + #[serde(rename = "http/protobuf")] + HttpProtobuf, +} + +impl OtlpHttpProtocol { + /// Environment-variable value (`"http/json"` or `"http/protobuf"`). + pub fn as_str(self) -> &'static str { + match self { + Self::HttpJson => "http/json", + Self::HttpProtobuf => "http/protobuf", + } + } +} + /// OpenTelemetry configuration forwarded to the spawned GitHub Copilot CLI /// process. /// @@ -417,6 +444,9 @@ impl OtelExporterType { /// |----------------------|-------------------------------------------------------| /// | (any field set) | `COPILOT_OTEL_ENABLED=true` | /// | [`otlp_endpoint`] | `OTEL_EXPORTER_OTLP_ENDPOINT` | +/// | [`otlp_protocol`] | `OTEL_EXPORTER_OTLP_PROTOCOL` | +/// | [`otlp_traces_protocol`] | `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | +/// | [`otlp_metrics_protocol`] | `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` | /// | [`file_path`] | `COPILOT_OTEL_FILE_EXPORTER_PATH` | /// | [`exporter_type`] | `COPILOT_OTEL_EXPORTER_TYPE` | /// | [`source_name`] | `COPILOT_OTEL_SOURCE_NAME` | @@ -430,6 +460,9 @@ impl OtelExporterType { /// added without breaking callers. /// /// [`otlp_endpoint`]: Self::otlp_endpoint +/// [`otlp_protocol`]: Self::otlp_protocol +/// [`otlp_traces_protocol`]: Self::otlp_traces_protocol +/// [`otlp_metrics_protocol`]: Self::otlp_metrics_protocol /// [`file_path`]: Self::file_path /// [`exporter_type`]: Self::exporter_type /// [`source_name`]: Self::source_name @@ -439,6 +472,12 @@ impl OtelExporterType { pub struct TelemetryConfig { /// OTLP HTTP endpoint URL for trace/metric export. pub otlp_endpoint: Option, + /// OTLP HTTP protocol for all signals. + pub otlp_protocol: Option, + /// OTLP HTTP protocol for traces. + pub otlp_traces_protocol: Option, + /// OTLP HTTP protocol for metrics. + pub otlp_metrics_protocol: Option, /// File path for JSON-lines trace output. pub file_path: Option, /// Exporter backend type. Typically [`OtelExporterType::OtlpHttp`] or @@ -467,6 +506,24 @@ impl TelemetryConfig { self } + /// Set the OTLP HTTP protocol for all signals. + pub fn with_otlp_protocol(mut self, protocol: OtlpHttpProtocol) -> Self { + self.otlp_protocol = Some(protocol); + self + } + + /// Set the OTLP HTTP protocol for traces. + pub fn with_otlp_traces_protocol(mut self, protocol: OtlpHttpProtocol) -> Self { + self.otlp_traces_protocol = Some(protocol); + self + } + + /// Set the OTLP HTTP protocol for metrics. + pub fn with_otlp_metrics_protocol(mut self, protocol: OtlpHttpProtocol) -> Self { + self.otlp_metrics_protocol = Some(protocol); + self + } + /// Set the file path for JSON-lines trace output. pub fn with_file_path(mut self, path: impl Into) -> Self { self.file_path = Some(path.into()); @@ -499,6 +556,9 @@ impl TelemetryConfig { /// to decide whether to set `COPILOT_OTEL_ENABLED`. pub fn is_empty(&self) -> bool { self.otlp_endpoint.is_none() + && self.otlp_protocol.is_none() + && self.otlp_traces_protocol.is_none() + && self.otlp_metrics_protocol.is_none() && self.file_path.is_none() && self.exporter_type.is_none() && self.source_name.is_none() @@ -1209,6 +1269,15 @@ impl Client { if let Some(endpoint) = &telemetry.otlp_endpoint { command.env("OTEL_EXPORTER_OTLP_ENDPOINT", endpoint); } + if let Some(protocol) = telemetry.otlp_protocol { + command.env("OTEL_EXPORTER_OTLP_PROTOCOL", protocol.as_str()); + } + if let Some(protocol) = telemetry.otlp_traces_protocol { + command.env("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", protocol.as_str()); + } + if let Some(protocol) = telemetry.otlp_metrics_protocol { + command.env("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", protocol.as_str()); + } if let Some(path) = &telemetry.file_path { command.env("COPILOT_OTEL_FILE_EXPORTER_PATH", path); } @@ -2115,12 +2184,21 @@ mod tests { fn telemetry_config_builder_composes() { let cfg = TelemetryConfig::new() .with_otlp_endpoint("http://collector:4318") + .with_otlp_protocol(OtlpHttpProtocol::HttpProtobuf) + .with_otlp_traces_protocol(OtlpHttpProtocol::HttpJson) + .with_otlp_metrics_protocol(OtlpHttpProtocol::HttpProtobuf) .with_file_path(PathBuf::from("/var/log/copilot.jsonl")) .with_exporter_type(OtelExporterType::OtlpHttp) .with_source_name("my-app") .with_capture_content(true); assert_eq!(cfg.otlp_endpoint.as_deref(), Some("http://collector:4318")); + assert_eq!(cfg.otlp_protocol, Some(OtlpHttpProtocol::HttpProtobuf)); + assert_eq!(cfg.otlp_traces_protocol, Some(OtlpHttpProtocol::HttpJson),); + assert_eq!( + cfg.otlp_metrics_protocol, + Some(OtlpHttpProtocol::HttpProtobuf), + ); assert_eq!( cfg.file_path.as_deref(), Some(Path::new("/var/log/copilot.jsonl")), @@ -2137,6 +2215,9 @@ mod tests { let opts = ClientOptions { telemetry: Some(TelemetryConfig { otlp_endpoint: Some("http://collector:4318".to_string()), + otlp_protocol: Some(OtlpHttpProtocol::HttpProtobuf), + otlp_traces_protocol: Some(OtlpHttpProtocol::HttpJson), + otlp_metrics_protocol: Some(OtlpHttpProtocol::HttpProtobuf), file_path: Some(PathBuf::from("/var/log/copilot.jsonl")), exporter_type: Some(OtelExporterType::OtlpHttp), source_name: Some("my-app".to_string()), @@ -2153,6 +2234,18 @@ mod tests { env_value(&cmd, "OTEL_EXPORTER_OTLP_ENDPOINT"), Some(std::ffi::OsStr::new("http://collector:4318")), ); + assert_eq!( + env_value(&cmd, "OTEL_EXPORTER_OTLP_PROTOCOL"), + Some(std::ffi::OsStr::new("http/protobuf")), + ); + assert_eq!( + env_value(&cmd, "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"), + Some(std::ffi::OsStr::new("http/json")), + ); + assert_eq!( + env_value(&cmd, "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"), + Some(std::ffi::OsStr::new("http/protobuf")), + ); assert_eq!( env_value(&cmd, "COPILOT_OTEL_FILE_EXPORTER_PATH"), Some(std::ffi::OsStr::new("/var/log/copilot.jsonl")), @@ -2178,6 +2271,9 @@ mod tests { for key in [ "COPILOT_OTEL_ENABLED", "OTEL_EXPORTER_OTLP_ENDPOINT", + "OTEL_EXPORTER_OTLP_PROTOCOL", + "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", + "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", "COPILOT_OTEL_FILE_EXPORTER_PATH", "COPILOT_OTEL_EXPORTER_TYPE", "COPILOT_OTEL_SOURCE_NAME", @@ -2211,6 +2307,9 @@ mod tests { ); // None of the other fields should leak as env vars. for key in [ + "OTEL_EXPORTER_OTLP_PROTOCOL", + "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", + "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", "COPILOT_OTEL_FILE_EXPORTER_PATH", "COPILOT_OTEL_EXPORTER_TYPE", "COPILOT_OTEL_SOURCE_NAME", From 56c56c4e729785c79ff07cacf257275be4a71df4 Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Mon, 15 Jun 2026 15:30:41 -0400 Subject: [PATCH 2/4] Remove signal-specific OTLP protocol options Keep the SDK telemetry API focused on the global OTLP HTTP protocol option and rely on direct environment variables for uncommon signal-specific overrides. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/getting-started.md | 4 +- docs/observability/opentelemetry.md | 4 +- dotnet/README.md | 2 - dotnet/src/Client.cs | 2 - dotnet/src/Types.cs | 16 ------ dotnet/test/E2E/ClientOptionsE2ETests.cs | 32 ----------- dotnet/test/Unit/TelemetryTests.cs | 6 -- go/README.md | 2 - go/client.go | 6 -- go/internal/e2e/client_options_e2e_test.go | 57 ++----------------- go/internal/e2e/telemetry_e2e_test.go | 26 ++------- go/types.go | 8 --- .../com/github/copilot/CliServerManager.java | 6 -- .../github/copilot/rpc/TelemetryConfig.java | 48 ---------------- .../github/copilot/CliServerManagerTest.java | 1 - .../github/copilot/TelemetryConfigTest.java | 19 ------- nodejs/README.md | 2 - nodejs/src/client.ts | 4 -- nodejs/src/types.ts | 4 -- nodejs/test/e2e/client_options.e2e.test.ts | 49 ---------------- nodejs/test/telemetry.test.ts | 12 ---- python/README.md | 2 - python/copilot/client.py | 8 --- python/e2e/test_client_options_e2e.py | 41 ------------- python/e2e/test_telemetry_e2e.py | 6 -- python/test_telemetry.py | 8 --- rust/src/lib.rs | 56 +----------------- 27 files changed, 17 insertions(+), 414 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 33ccf4caaf..4173fb4dd3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2228,14 +2228,12 @@ Dependency: `io.opentelemetry:opentelemetry-api` |---|---|---|---|---|---|---|---| | OTLP endpoint | `otlpEndpoint` | `otlp_endpoint` | `OTLPEndpoint` | `otlp_endpoint` | `otlpEndpoint` | `OtlpEndpoint` | OTLP HTTP endpoint URL | | OTLP protocol | `otlpProtocol` | `otlp_protocol` | `OTLPProtocol` | `otlp_protocol` | `otlpProtocol` | `OtlpProtocol` | OTLP HTTP protocol for all signals: `"http/json"` or `"http/protobuf"` | -| OTLP traces protocol | `otlpTracesProtocol` | `otlp_traces_protocol` | `OTLPTracesProtocol` | `otlp_traces_protocol` | `otlpTracesProtocol` | `OtlpTracesProtocol` | OTLP HTTP protocol override for traces | -| OTLP metrics protocol | `otlpMetricsProtocol` | `otlp_metrics_protocol` | `OTLPMetricsProtocol` | `otlp_metrics_protocol` | `otlpMetricsProtocol` | `OtlpMetricsProtocol` | OTLP HTTP protocol override for metrics | | File path | `filePath` | `file_path` | `FilePath` | `file_path` | `filePath` | `FilePath` | File path for JSON-lines trace output | | Exporter type | `exporterType` | `exporter_type` | `ExporterType` | `exporter_type` | `exporterType` | `ExporterType` | `"otlp-http"` or `"file"` | | Source name | `sourceName` | `source_name` | `SourceName` | `source_name` | `sourceName` | `SourceName` | Instrumentation scope name | | Capture content | `captureContent` | `capture_content` | `CaptureContent` | `capture_content` | `captureContent` | `CaptureContent` | Whether to capture message content | -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. +The OTLP protocol field configures the CLI's `"otlp-http"` exporter for all signals. Leave it unset to use the CLI default (`"http/json"`), or set it to `"http/protobuf"` to export protobuf over HTTP. ### File export diff --git a/docs/observability/opentelemetry.md b/docs/observability/opentelemetry.md index af7ad4caa6..d883e66a7c 100644 --- a/docs/observability/opentelemetry.md +++ b/docs/observability/opentelemetry.md @@ -105,14 +105,12 @@ let client = Client::start(ClientOptions::new() |---|---|---|---|---|---|---|---| | OTLP endpoint | `otlpEndpoint` | `otlp_endpoint` | `OTLPEndpoint` | `OtlpEndpoint` | `otlpEndpoint` | `otlp_endpoint` | OTLP HTTP endpoint URL | | OTLP protocol | `otlpProtocol` | `otlp_protocol` | `OTLPProtocol` | `OtlpProtocol` | `otlpProtocol` | `otlp_protocol` | OTLP HTTP protocol for all signals: `"http/json"` or `"http/protobuf"` | -| OTLP traces protocol | `otlpTracesProtocol` | `otlp_traces_protocol` | `OTLPTracesProtocol` | `OtlpTracesProtocol` | `otlpTracesProtocol` | `otlp_traces_protocol` | OTLP HTTP protocol override for traces | -| OTLP metrics protocol | `otlpMetricsProtocol` | `otlp_metrics_protocol` | `OTLPMetricsProtocol` | `OtlpMetricsProtocol` | `otlpMetricsProtocol` | `otlp_metrics_protocol` | OTLP HTTP protocol override for metrics | | File path | `filePath` | `file_path` | `FilePath` | `FilePath` | `filePath` | `file_path` | File path for JSON-lines trace output | | Exporter type | `exporterType` | `exporter_type` | `ExporterType` | `ExporterType` | `exporterType` | `exporter_type` | `"otlp-http"` or `"file"` | | Source name | `sourceName` | `source_name` | `SourceName` | `SourceName` | `sourceName` | `source_name` | Instrumentation scope name | | Capture content | `captureContent` | `capture_content` | `CaptureContent` | `CaptureContent` | `captureContent` | `capture_content` | Whether to capture message content | -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. +The OTLP protocol field configures the CLI's `"otlp-http"` exporter for all signals. Leave it unset to use the CLI default (`"http/json"`), or set it to `"http/protobuf"` to export protobuf over HTTP. ### Trace context propagation diff --git a/dotnet/README.md b/dotnet/README.md index d51b9629c4..b7a73e826b 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -739,8 +739,6 @@ var client = new CopilotClient(new CopilotClientOptions - `OtlpEndpoint` - OTLP HTTP endpoint URL - `OtlpProtocol` - OTLP HTTP protocol for all signals (`"http/json"` or `"http/protobuf"`) -- `OtlpTracesProtocol` - OTLP HTTP protocol override for traces -- `OtlpMetricsProtocol` - OTLP HTTP protocol override for metrics - `FilePath` - File path for JSON-lines trace output - `ExporterType` - `"otlp-http"` or `"file"` - `SourceName` - Instrumentation scope name diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 45d08d1738..e83c71bc07 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1806,8 +1806,6 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex) startInfo.Environment["COPILOT_OTEL_ENABLED"] = "true"; if (telemetry.OtlpEndpoint is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_ENDPOINT"] = telemetry.OtlpEndpoint; if (telemetry.OtlpProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_PROTOCOL"] = telemetry.OtlpProtocol; - if (telemetry.OtlpTracesProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] = telemetry.OtlpTracesProtocol; - if (telemetry.OtlpMetricsProtocol is not null) startInfo.Environment["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] = telemetry.OtlpMetricsProtocol; if (telemetry.FilePath is not null) startInfo.Environment["COPILOT_OTEL_FILE_EXPORTER_PATH"] = telemetry.FilePath; if (telemetry.ExporterType is not null) startInfo.Environment["COPILOT_OTEL_EXPORTER_TYPE"] = telemetry.ExporterType; if (telemetry.SourceName is not null) startInfo.Environment["COPILOT_OTEL_SOURCE_NAME"] = telemetry.SourceName; diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index c05ea93296..c6c30e3e83 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -421,22 +421,6 @@ public sealed class TelemetryConfig /// public string? OtlpProtocol { get; set; } - ///

- /// OTLP HTTP protocol for traces ("http/json" or "http/protobuf"). - /// - /// - /// Maps to the OTEL_EXPORTER_OTLP_TRACES_PROTOCOL environment variable. - /// - public string? OtlpTracesProtocol { get; set; } - - /// - /// OTLP HTTP protocol for metrics ("http/json" or "http/protobuf"). - /// - /// - /// Maps to the OTEL_EXPORTER_OTLP_METRICS_PROTOCOL environment variable. - /// - public string? OtlpMetricsProtocol { get; set; } - /// /// File path for the file exporter. /// diff --git a/dotnet/test/E2E/ClientOptionsE2ETests.cs b/dotnet/test/E2E/ClientOptionsE2ETests.cs index 6e95654e37..c2f16a042b 100644 --- a/dotnet/test/E2E/ClientOptionsE2ETests.cs +++ b/dotnet/test/E2E/ClientOptionsE2ETests.cs @@ -79,8 +79,6 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli() { OtlpEndpoint = "http://127.0.0.1:4318", OtlpProtocol = "http/protobuf", - OtlpTracesProtocol = "http/json", - OtlpMetricsProtocol = "http/protobuf", FilePath = telemetryPath, ExporterType = "file", SourceName = "dotnet-sdk-e2e", @@ -108,8 +106,6 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli() Assert.Equal("true", capturedEnv.GetProperty("COPILOT_OTEL_ENABLED").GetString()); Assert.Equal("http://127.0.0.1:4318", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_ENDPOINT").GetString()); Assert.Equal("http/protobuf", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_PROTOCOL").GetString()); - Assert.Equal("http/json", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL").GetString()); - Assert.Equal("http/protobuf", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL").GetString()); Assert.Equal(telemetryPath, capturedEnv.GetProperty("COPILOT_OTEL_FILE_EXPORTER_PATH").GetString()); Assert.Equal("file", capturedEnv.GetProperty("COPILOT_OTEL_EXPORTER_TYPE").GetString()); Assert.Equal("dotnet-sdk-e2e", capturedEnv.GetProperty("COPILOT_OTEL_SOURCE_NAME").GetString()); @@ -130,32 +126,6 @@ public async Task Should_Propagate_Process_Options_To_Spawned_Cli() await session.DisposeAsync(); } - [Fact] - public async Task Should_Only_Set_Configured_Otlp_Protocol_Env_Vars() - { - var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); - - await using var client = Ctx.CreateClient(options: new CopilotClientOptions - { - Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), - GitHubToken = "process-option-token", - Telemetry = new TelemetryConfig - { - OtlpTracesProtocol = "http/protobuf", - }, - UseLoggedInUser = false, - }); - - await client.StartAsync(); - - using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); - var capturedEnv = capture.RootElement.GetProperty("env"); - Assert.Equal("true", capturedEnv.GetProperty("COPILOT_OTEL_ENABLED").GetString()); - Assert.Equal("http/protobuf", capturedEnv.GetProperty("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL").GetString()); - Assert.False(capturedEnv.TryGetProperty("OTEL_EXPORTER_OTLP_PROTOCOL", out _)); - Assert.False(capturedEnv.TryGetProperty("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", out _)); - } - [Fact] public async Task Should_Forward_EnableSessionTelemetry_In_Wire_Request() { @@ -675,8 +645,6 @@ function saveCapture() { COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL, - OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, - OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH, COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE, COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME, diff --git a/dotnet/test/Unit/TelemetryTests.cs b/dotnet/test/Unit/TelemetryTests.cs index e275184a0c..979e575b3c 100644 --- a/dotnet/test/Unit/TelemetryTests.cs +++ b/dotnet/test/Unit/TelemetryTests.cs @@ -17,8 +17,6 @@ public void TelemetryConfig_DefaultValues_AreNull() Assert.Null(config.OtlpEndpoint); Assert.Null(config.OtlpProtocol); - Assert.Null(config.OtlpTracesProtocol); - Assert.Null(config.OtlpMetricsProtocol); Assert.Null(config.FilePath); Assert.Null(config.ExporterType); Assert.Null(config.SourceName); @@ -32,8 +30,6 @@ public void TelemetryConfig_CanSetAllProperties() { OtlpEndpoint = "http://localhost:4318", OtlpProtocol = "http/protobuf", - OtlpTracesProtocol = "http/json", - OtlpMetricsProtocol = "http/protobuf", FilePath = "/tmp/traces.json", ExporterType = "otlp-http", SourceName = "my-app", @@ -42,8 +38,6 @@ public void TelemetryConfig_CanSetAllProperties() Assert.Equal("http://localhost:4318", config.OtlpEndpoint); Assert.Equal("http/protobuf", config.OtlpProtocol); - Assert.Equal("http/json", config.OtlpTracesProtocol); - Assert.Equal("http/protobuf", config.OtlpMetricsProtocol); Assert.Equal("/tmp/traces.json", config.FilePath); Assert.Equal("otlp-http", config.ExporterType); Assert.Equal("my-app", config.SourceName); diff --git a/go/README.md b/go/README.md index b9ce90d248..f595eb7691 100644 --- a/go/README.md +++ b/go/README.md @@ -574,8 +574,6 @@ client, err := copilot.NewClient(copilot.ClientOptions{ - `OTLPEndpoint` (string): OTLP HTTP endpoint URL - `OTLPProtocol` (string): OTLP HTTP protocol for all signals (`"http/json"` or `"http/protobuf"`) -- `OTLPTracesProtocol` (string): OTLP HTTP protocol override for traces -- `OTLPMetricsProtocol` (string): OTLP HTTP protocol override for metrics - `FilePath` (string): File path for JSON-lines trace output - `ExporterType` (string): `"otlp-http"` or `"file"` - `SourceName` (string): Instrumentation scope name diff --git a/go/client.go b/go/client.go index f76431ce1e..b73240b1b0 100644 --- a/go/client.go +++ b/go/client.go @@ -1698,12 +1698,6 @@ func (c *Client) startCLIServer(ctx context.Context) error { if t.OTLPProtocol != "" { c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_PROTOCOL", t.OTLPProtocol) } - if t.OTLPTracesProtocol != "" { - c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", t.OTLPTracesProtocol) - } - if t.OTLPMetricsProtocol != "" { - c.process.Env = setEnvValue(c.process.Env, "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", t.OTLPMetricsProtocol) - } if t.FilePath != "" { c.process.Env = setEnvValue(c.process.Env, "COPILOT_OTEL_FILE_EXPORTER_PATH", t.FilePath) } diff --git a/go/internal/e2e/client_options_e2e_test.go b/go/internal/e2e/client_options_e2e_test.go index c291e46ef8..aa42be1f3a 100644 --- a/go/internal/e2e/client_options_e2e_test.go +++ b/go/internal/e2e/client_options_e2e_test.go @@ -109,14 +109,12 @@ func TestClientOptionsE2E(t *testing.T) { opts.LogLevel = "debug" opts.SessionIdleTimeoutSeconds = 17 opts.Telemetry = &copilot.TelemetryConfig{ - OTLPEndpoint: "http://127.0.0.1:4318", - OTLPProtocol: "http/protobuf", - OTLPTracesProtocol: "http/json", - OTLPMetricsProtocol: "http/protobuf", - FilePath: telemetryPath, - ExporterType: "file", - SourceName: "go-sdk-e2e", - CaptureContent: copilot.Bool(true), + OTLPEndpoint: "http://127.0.0.1:4318", + OTLPProtocol: "http/protobuf", + FilePath: telemetryPath, + ExporterType: "file", + SourceName: "go-sdk-e2e", + CaptureContent: copilot.Bool(true), } opts.UseLoggedInUser = copilot.Bool(false) }) @@ -151,8 +149,6 @@ func TestClientOptionsE2E(t *testing.T) { "COPILOT_OTEL_ENABLED": "true", "OTEL_EXPORTER_OTLP_ENDPOINT": "http://127.0.0.1:4318", "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf", - "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL": "http/json", - "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": "http/protobuf", "COPILOT_OTEL_FILE_EXPORTER_PATH": telemetryPath, "COPILOT_OTEL_EXPORTER_TYPE": "file", "COPILOT_OTEL_SOURCE_NAME": "go-sdk-e2e", @@ -202,45 +198,6 @@ func TestClientOptionsE2E(t *testing.T) { } }) - t.Run("should only set configured OTLP protocol env vars", func(t *testing.T) { - ctx := testharness.NewTestContext(t) - - cliPath := filepath.Join(ctx.WorkDir, "fake-cli-"+randomHex(t)+".js") - capturePath := filepath.Join(ctx.WorkDir, "fake-cli-capture-"+randomHex(t)+".json") - if err := os.WriteFile(cliPath, []byte(fakeStdioCliScript), 0644); err != nil { - t.Fatalf("Failed to write fake CLI script: %v", err) - } - - client := ctx.NewClient(func(opts *copilot.ClientOptions) { - opts.Connection = copilot.StdioConnection{ - Path: cliPath, - Args: []string{"--capture-file", capturePath}, - } - opts.GitHubToken = "process-option-token" - opts.Telemetry = &copilot.TelemetryConfig{ - OTLPTracesProtocol: "http/protobuf", - } - opts.UseLoggedInUser = copilot.Bool(false) - }) - t.Cleanup(func() { client.ForceStop() }) - - if err := client.Start(t.Context()); err != nil { - t.Fatalf("Start failed: %v", err) - } - - capture := readCapture(t, capturePath) - if got := capture.Env["COPILOT_OTEL_ENABLED"]; got != "true" { - t.Errorf("Expected COPILOT_OTEL_ENABLED=true, got %q", got) - } - if got := capture.Env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"]; got != "http/protobuf" { - t.Errorf("Expected OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf, got %q", got) - } - for _, key := range []string{"OTEL_EXPORTER_OTLP_PROTOCOL", "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"} { - if got, ok := capture.Env[key]; ok { - t.Errorf("Expected %s to be unset, got %q", key, got) - } - } - }) } // --------------------------------------------------------------------------- @@ -419,8 +376,6 @@ function saveCapture() { COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL, - OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, - OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH, COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE, COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME, diff --git a/go/internal/e2e/telemetry_e2e_test.go b/go/internal/e2e/telemetry_e2e_test.go index 998fd86807..67a4cf82ea 100644 --- a/go/internal/e2e/telemetry_e2e_test.go +++ b/go/internal/e2e/telemetry_e2e_test.go @@ -310,12 +310,6 @@ func TestTelemetryConfigUnit(t *testing.T) { if cfg.OTLPProtocol != "" { t.Errorf("Expected empty OTLPProtocol, got %q", cfg.OTLPProtocol) } - if cfg.OTLPTracesProtocol != "" { - t.Errorf("Expected empty OTLPTracesProtocol, got %q", cfg.OTLPTracesProtocol) - } - if cfg.OTLPMetricsProtocol != "" { - t.Errorf("Expected empty OTLPMetricsProtocol, got %q", cfg.OTLPMetricsProtocol) - } if cfg.FilePath != "" { t.Errorf("Expected empty FilePath, got %q", cfg.FilePath) } @@ -333,14 +327,12 @@ func TestTelemetryConfigUnit(t *testing.T) { t.Run("can set all properties", func(t *testing.T) { // Mirrors: TelemetryConfig_CanSetAllProperties cfg := copilot.TelemetryConfig{ - OTLPEndpoint: "http://localhost:4318", - OTLPProtocol: "http/protobuf", - OTLPTracesProtocol: "http/json", - OTLPMetricsProtocol: "http/protobuf", - FilePath: "/tmp/traces.json", - ExporterType: "otlp-http", - SourceName: "my-app", - CaptureContent: copilot.Bool(true), + OTLPEndpoint: "http://localhost:4318", + OTLPProtocol: "http/protobuf", + FilePath: "/tmp/traces.json", + ExporterType: "otlp-http", + SourceName: "my-app", + CaptureContent: copilot.Bool(true), } if cfg.OTLPEndpoint != "http://localhost:4318" { t.Errorf("OTLPEndpoint mismatch: %q", cfg.OTLPEndpoint) @@ -348,12 +340,6 @@ func TestTelemetryConfigUnit(t *testing.T) { if cfg.OTLPProtocol != "http/protobuf" { t.Errorf("OTLPProtocol mismatch: %q", cfg.OTLPProtocol) } - if cfg.OTLPTracesProtocol != "http/json" { - t.Errorf("OTLPTracesProtocol mismatch: %q", cfg.OTLPTracesProtocol) - } - if cfg.OTLPMetricsProtocol != "http/protobuf" { - t.Errorf("OTLPMetricsProtocol mismatch: %q", cfg.OTLPMetricsProtocol) - } if cfg.FilePath != "/tmp/traces.json" { t.Errorf("FilePath mismatch: %q", cfg.FilePath) } diff --git a/go/types.go b/go/types.go index ed538bbf22..8225db0ef8 100644 --- a/go/types.go +++ b/go/types.go @@ -163,14 +163,6 @@ type TelemetryConfig struct { // Sets OTEL_EXPORTER_OTLP_PROTOCOL. OTLPProtocol string - // OTLPTracesProtocol is the OTLP HTTP protocol for traces. - // Sets OTEL_EXPORTER_OTLP_TRACES_PROTOCOL. - OTLPTracesProtocol string - - // OTLPMetricsProtocol is the OTLP HTTP protocol for metrics. - // Sets OTEL_EXPORTER_OTLP_METRICS_PROTOCOL. - OTLPMetricsProtocol string - // FilePath is the file path for JSON-lines trace output. // Sets COPILOT_OTEL_FILE_EXPORTER_PATH. FilePath string diff --git a/java/src/main/java/com/github/copilot/CliServerManager.java b/java/src/main/java/com/github/copilot/CliServerManager.java index af3e01546b..acc683a720 100644 --- a/java/src/main/java/com/github/copilot/CliServerManager.java +++ b/java/src/main/java/com/github/copilot/CliServerManager.java @@ -153,12 +153,6 @@ ProcessInfo startCliServer() throws IOException, InterruptedException { if (telemetry.getOtlpProtocol() != null) { pb.environment().put("OTEL_EXPORTER_OTLP_PROTOCOL", telemetry.getOtlpProtocol()); } - if (telemetry.getOtlpTracesProtocol() != null) { - pb.environment().put("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", telemetry.getOtlpTracesProtocol()); - } - if (telemetry.getOtlpMetricsProtocol() != null) { - pb.environment().put("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", telemetry.getOtlpMetricsProtocol()); - } if (telemetry.getFilePath() != null) { pb.environment().put("COPILOT_OTEL_FILE_EXPORTER_PATH", telemetry.getFilePath()); } diff --git a/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java b/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java index e73b2a5436..593908ff8c 100644 --- a/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java @@ -31,8 +31,6 @@ public class TelemetryConfig { private String otlpEndpoint; private String otlpProtocol; - private String otlpTracesProtocol; - private String otlpMetricsProtocol; private String filePath; private String exporterType; private String sourceName; @@ -84,52 +82,6 @@ public TelemetryConfig setOtlpProtocol(String otlpProtocol) { return this; } - /** - * Gets the OTLP HTTP protocol for traces. - *

- * Maps to the {@code OTEL_EXPORTER_OTLP_TRACES_PROTOCOL} environment variable. - * - * @return the traces protocol, or {@code null} - */ - public String getOtlpTracesProtocol() { - return otlpTracesProtocol; - } - - /** - * Sets the OTLP HTTP protocol for traces. - * - * @param otlpTracesProtocol - * the protocol ({@code "http/json"} or {@code "http/protobuf"}) - * @return this config for method chaining - */ - public TelemetryConfig setOtlpTracesProtocol(String otlpTracesProtocol) { - this.otlpTracesProtocol = otlpTracesProtocol; - return this; - } - - /** - * Gets the OTLP HTTP protocol for metrics. - *

- * Maps to the {@code OTEL_EXPORTER_OTLP_METRICS_PROTOCOL} environment variable. - * - * @return the metrics protocol, or {@code null} - */ - public String getOtlpMetricsProtocol() { - return otlpMetricsProtocol; - } - - /** - * Sets the OTLP HTTP protocol for metrics. - * - * @param otlpMetricsProtocol - * the protocol ({@code "http/json"} or {@code "http/protobuf"}) - * @return this config for method chaining - */ - public TelemetryConfig setOtlpMetricsProtocol(String otlpMetricsProtocol) { - this.otlpMetricsProtocol = otlpMetricsProtocol; - return this; - } - /** * Gets the file path for the file exporter. *

diff --git a/java/src/test/java/com/github/copilot/CliServerManagerTest.java b/java/src/test/java/com/github/copilot/CliServerManagerTest.java index e50c278dfb..b445d6153a 100644 --- a/java/src/test/java/com/github/copilot/CliServerManagerTest.java +++ b/java/src/test/java/com/github/copilot/CliServerManagerTest.java @@ -232,7 +232,6 @@ void startCliServerWithTelemetryAllOptions() throws Exception { // The telemetry env vars are applied before ProcessBuilder.start() // so even with a nonexistent CLI path, the telemetry code path is exercised var telemetry = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setOtlpProtocol("http/protobuf") - .setOtlpTracesProtocol("http/json").setOtlpMetricsProtocol("http/protobuf") .setFilePath("/tmp/telemetry.log").setExporterType("otlp-http").setSourceName("test-app") .setCaptureContent(true); var options = new CopilotClientOptions().setCliPath(NONEXISTENT_CLI).setTelemetry(telemetry).setUseStdio(true); diff --git a/java/src/test/java/com/github/copilot/TelemetryConfigTest.java b/java/src/test/java/com/github/copilot/TelemetryConfigTest.java index 2fc0ea9f03..739d6a5102 100644 --- a/java/src/test/java/com/github/copilot/TelemetryConfigTest.java +++ b/java/src/test/java/com/github/copilot/TelemetryConfigTest.java @@ -20,8 +20,6 @@ void defaultValuesAreNull() { var config = new TelemetryConfig(); assertNull(config.getOtlpEndpoint()); assertNull(config.getOtlpProtocol()); - assertNull(config.getOtlpTracesProtocol()); - assertNull(config.getOtlpMetricsProtocol()); assertNull(config.getFilePath()); assertNull(config.getExporterType()); assertNull(config.getSourceName()); @@ -42,20 +40,6 @@ void otlpProtocolGetterSetter() { assertEquals("http/protobuf", config.getOtlpProtocol()); } - @Test - void otlpTracesProtocolGetterSetter() { - var config = new TelemetryConfig(); - config.setOtlpTracesProtocol("http/json"); - assertEquals("http/json", config.getOtlpTracesProtocol()); - } - - @Test - void otlpMetricsProtocolGetterSetter() { - var config = new TelemetryConfig(); - config.setOtlpMetricsProtocol("http/protobuf"); - assertEquals("http/protobuf", config.getOtlpMetricsProtocol()); - } - @Test void filePathGetterSetter() { var config = new TelemetryConfig(); @@ -90,14 +74,11 @@ void captureContentGetterSetter() { @Test void fluentChainingReturnsThis() { var config = new TelemetryConfig().setOtlpEndpoint("http://localhost:4318").setOtlpProtocol("http/protobuf") - .setOtlpTracesProtocol("http/json").setOtlpMetricsProtocol("http/protobuf") .setFilePath("/tmp/spans.json").setExporterType("file").setSourceName("sdk-test") .setCaptureContent(true); assertEquals("http://localhost:4318", config.getOtlpEndpoint()); assertEquals("http/protobuf", config.getOtlpProtocol()); - assertEquals("http/json", config.getOtlpTracesProtocol()); - assertEquals("http/protobuf", config.getOtlpMetricsProtocol()); assertEquals("/tmp/spans.json", config.getFilePath()); assertEquals("file", config.getExporterType()); assertEquals("sdk-test", config.getSourceName()); diff --git a/nodejs/README.md b/nodejs/README.md index 240efe3c8f..08a691a291 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -787,8 +787,6 @@ With just this configuration, the CLI emits spans for every session, message, an - `otlpEndpoint?: string` - OTLP HTTP endpoint URL - `otlpProtocol?: "http/json" | "http/protobuf"` - OTLP HTTP protocol for all signals -- `otlpTracesProtocol?: "http/json" | "http/protobuf"` - OTLP HTTP protocol override for traces -- `otlpMetricsProtocol?: "http/json" | "http/protobuf"` - OTLP HTTP protocol override for metrics - `filePath?: string` - File path for JSON-lines trace output - `exporterType?: string` - `"otlp-http"` or `"file"` - `sourceName?: string` - Instrumentation scope name diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index a72d0ab1bb..223e92468c 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -1931,10 +1931,6 @@ export class CopilotClient { envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint; if (t.otlpProtocol !== undefined) envWithoutNodeDebug.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol; - if (t.otlpTracesProtocol !== undefined) - envWithoutNodeDebug.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = t.otlpTracesProtocol; - if (t.otlpMetricsProtocol !== undefined) - envWithoutNodeDebug.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = t.otlpMetricsProtocol; if (t.filePath !== undefined) envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath; if (t.exporterType !== undefined) diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index e7864e22b3..4d36ca0a91 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -57,10 +57,6 @@ export interface TelemetryConfig { otlpEndpoint?: string; /** OTLP HTTP protocol for all signals. Sets OTEL_EXPORTER_OTLP_PROTOCOL. */ otlpProtocol?: "http/json" | "http/protobuf"; - /** OTLP HTTP protocol for traces. Sets OTEL_EXPORTER_OTLP_TRACES_PROTOCOL. */ - otlpTracesProtocol?: "http/json" | "http/protobuf"; - /** OTLP HTTP protocol for metrics. Sets OTEL_EXPORTER_OTLP_METRICS_PROTOCOL. */ - otlpMetricsProtocol?: "http/json" | "http/protobuf"; /** File path for JSON-lines trace output. Sets COPILOT_OTEL_FILE_EXPORTER_PATH. */ filePath?: string; /** Exporter backend type: "otlp-http" or "file". Sets COPILOT_OTEL_EXPORTER_TYPE. */ diff --git a/nodejs/test/e2e/client_options.e2e.test.ts b/nodejs/test/e2e/client_options.e2e.test.ts index 8eb4a76b5c..2cfc69456f 100644 --- a/nodejs/test/e2e/client_options.e2e.test.ts +++ b/nodejs/test/e2e/client_options.e2e.test.ts @@ -30,8 +30,6 @@ function saveCapture() { COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL, - OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, - OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH, COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE, COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME, @@ -251,8 +249,6 @@ describe("Client options", async () => { telemetry: { otlpEndpoint: "http://127.0.0.1:4318", otlpProtocol: "http/protobuf", - otlpTracesProtocol: "http/json", - otlpMetricsProtocol: "http/protobuf", filePath: telemetryPath, exporterType: "file", sourceName: "ts-sdk-e2e", @@ -290,8 +286,6 @@ describe("Client options", async () => { expect(capture.env.COPILOT_OTEL_ENABLED).toBe("true"); expect(capture.env.OTEL_EXPORTER_OTLP_ENDPOINT).toBe("http://127.0.0.1:4318"); expect(capture.env.OTEL_EXPORTER_OTLP_PROTOCOL).toBe("http/protobuf"); - expect(capture.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL).toBe("http/json"); - expect(capture.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL).toBe("http/protobuf"); expect(capture.env.COPILOT_OTEL_FILE_EXPORTER_PATH).toBe(telemetryPath); expect(capture.env.COPILOT_OTEL_EXPORTER_TYPE).toBe("file"); expect(capture.env.COPILOT_OTEL_SOURCE_NAME).toBe("ts-sdk-e2e"); @@ -324,49 +318,6 @@ describe("Client options", async () => { await session.disconnect(); }); - it("should only set configured otlp protocol env vars", async () => { - const cliPath = path.join( - workDir, - `fake-cli-${Date.now()}-${Math.random().toString(36).slice(2)}.js` - ); - const capturePath = path.join( - workDir, - `fake-cli-capture-${Date.now()}-${Math.random().toString(36).slice(2)}.json` - ); - fs.writeFileSync(cliPath, FAKE_STDIO_CLI_SCRIPT); - - const client = new CopilotClient({ - workingDirectory: workDir, - env, - connection: RuntimeConnection.forStdio({ - path: cliPath, - args: ["--capture-file", capturePath], - }), - gitHubToken: "process-option-token", - telemetry: { - otlpTracesProtocol: "http/protobuf", - }, - useLoggedInUser: false, - }); - onTestFinished(async () => { - try { - await client.forceStop(); - } catch { - // Ignore cleanup errors - } - }); - - await client.start(); - - const capture = JSON.parse(fs.readFileSync(capturePath, "utf8")) as { - env: Record; - }; - expect(capture.env.COPILOT_OTEL_ENABLED).toBe("true"); - expect(capture.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL).toBe("http/protobuf"); - expect(capture.env).not.toHaveProperty("OTEL_EXPORTER_OTLP_PROTOCOL"); - expect(capture.env).not.toHaveProperty("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"); - }); - it("should throw when gitHubToken used with forUri", () => { expect(() => { new CopilotClient({ diff --git a/nodejs/test/telemetry.test.ts b/nodejs/test/telemetry.test.ts index 5889810e12..78d9654ede 100644 --- a/nodejs/test/telemetry.test.ts +++ b/nodejs/test/telemetry.test.ts @@ -65,8 +65,6 @@ describe("telemetry", () => { const telemetry = { otlpEndpoint: "http://localhost:4318", otlpProtocol: "http/protobuf", - otlpTracesProtocol: "http/json", - otlpMetricsProtocol: "http/protobuf", filePath: "/tmp/traces.jsonl", exporterType: "otlp-http", sourceName: "my-app", @@ -80,10 +78,6 @@ describe("telemetry", () => { env.COPILOT_OTEL_ENABLED = "true"; if (t.otlpEndpoint !== undefined) env.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint; if (t.otlpProtocol !== undefined) env.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol; - if (t.otlpTracesProtocol !== undefined) - env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = t.otlpTracesProtocol; - if (t.otlpMetricsProtocol !== undefined) - env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = t.otlpMetricsProtocol; if (t.filePath !== undefined) env.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath; if (t.exporterType !== undefined) env.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType; if (t.sourceName !== undefined) env.COPILOT_OTEL_SOURCE_NAME = t.sourceName; @@ -97,8 +91,6 @@ describe("telemetry", () => { COPILOT_OTEL_ENABLED: "true", OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318", OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf", - OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: "http/json", - OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: "http/protobuf", COPILOT_OTEL_FILE_EXPORTER_PATH: "/tmp/traces.jsonl", COPILOT_OTEL_EXPORTER_TYPE: "otlp-http", COPILOT_OTEL_SOURCE_NAME: "my-app", @@ -115,10 +107,6 @@ describe("telemetry", () => { env.COPILOT_OTEL_ENABLED = "true"; if (t.otlpEndpoint !== undefined) env.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint; if (t.otlpProtocol !== undefined) env.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol; - if (t.otlpTracesProtocol !== undefined) - env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = t.otlpTracesProtocol; - if (t.otlpMetricsProtocol !== undefined) - env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = t.otlpMetricsProtocol; if (t.filePath !== undefined) env.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath; if (t.exporterType !== undefined) env.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType; if (t.sourceName !== undefined) env.COPILOT_OTEL_SOURCE_NAME = t.sourceName; diff --git a/python/README.md b/python/README.md index 015cc053ee..19373567cf 100644 --- a/python/README.md +++ b/python/README.md @@ -558,8 +558,6 @@ client = CopilotClient( - `otlp_endpoint` (str): OTLP HTTP endpoint URL - `otlp_protocol` (str): OTLP HTTP protocol for all signals (`"http/json"` or `"http/protobuf"`) -- `otlp_traces_protocol` (str): OTLP HTTP protocol override for traces -- `otlp_metrics_protocol` (str): OTLP HTTP protocol override for metrics - `file_path` (str): File path for JSON-lines trace output - `exporter_type` (str): `"otlp-http"` or `"file"` - `source_name` (str): Instrumentation scope name diff --git a/python/copilot/client.py b/python/copilot/client.py index 99a9949119..15c2b44988 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -184,10 +184,6 @@ class TelemetryConfig(TypedDict, total=False): """OTLP HTTP endpoint URL for trace/metric export. Sets OTEL_EXPORTER_OTLP_ENDPOINT.""" otlp_protocol: str """OTLP HTTP protocol for all signals. Sets OTEL_EXPORTER_OTLP_PROTOCOL.""" - otlp_traces_protocol: str - """OTLP HTTP protocol for traces. Sets OTEL_EXPORTER_OTLP_TRACES_PROTOCOL.""" - otlp_metrics_protocol: str - """OTLP HTTP protocol for metrics. Sets OTEL_EXPORTER_OTLP_METRICS_PROTOCOL.""" file_path: str """File path for JSON-lines trace output. Sets COPILOT_OTEL_FILE_EXPORTER_PATH.""" exporter_type: str @@ -3233,10 +3229,6 @@ async def _start_cli_server(self) -> None: env["OTEL_EXPORTER_OTLP_ENDPOINT"] = telemetry["otlp_endpoint"] if "otlp_protocol" in telemetry: env["OTEL_EXPORTER_OTLP_PROTOCOL"] = telemetry["otlp_protocol"] - if "otlp_traces_protocol" in telemetry: - env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] = telemetry["otlp_traces_protocol"] - if "otlp_metrics_protocol" in telemetry: - env["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] = telemetry["otlp_metrics_protocol"] if "file_path" in telemetry: env["COPILOT_OTEL_FILE_EXPORTER_PATH"] = telemetry["file_path"] if "exporter_type" in telemetry: diff --git a/python/e2e/test_client_options_e2e.py b/python/e2e/test_client_options_e2e.py index 04b9a8c228..9a70c6cbfe 100644 --- a/python/e2e/test_client_options_e2e.py +++ b/python/e2e/test_client_options_e2e.py @@ -93,8 +93,6 @@ def _get_available_port() -> int: COPILOT_OTEL_ENABLED: process.env.COPILOT_OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_PROTOCOL, - OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, - OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, COPILOT_OTEL_FILE_EXPORTER_PATH: process.env.COPILOT_OTEL_FILE_EXPORTER_PATH, COPILOT_OTEL_EXPORTER_TYPE: process.env.COPILOT_OTEL_EXPORTER_TYPE, COPILOT_OTEL_SOURCE_NAME: process.env.COPILOT_OTEL_SOURCE_NAME, @@ -222,8 +220,6 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes telemetry={ "otlp_endpoint": "http://127.0.0.1:4318", "otlp_protocol": "http/protobuf", - "otlp_traces_protocol": "http/json", - "otlp_metrics_protocol": "http/protobuf", "file_path": telemetry_path, "exporter_type": "file", "source_name": "python-sdk-e2e", @@ -253,8 +249,6 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes assert env["COPILOT_OTEL_ENABLED"] == "true" assert env["OTEL_EXPORTER_OTLP_ENDPOINT"] == "http://127.0.0.1:4318" assert env["OTEL_EXPORTER_OTLP_PROTOCOL"] == "http/protobuf" - assert env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] == "http/json" - assert env["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] == "http/protobuf" assert env["COPILOT_OTEL_FILE_EXPORTER_PATH"] == telemetry_path assert env["COPILOT_OTEL_EXPORTER_TYPE"] == "file" assert env["COPILOT_OTEL_SOURCE_NAME"] == "python-sdk-e2e" @@ -283,38 +277,3 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes await client.stop() except Exception: await client.force_stop() - - async def test_should_only_set_configured_otlp_protocol_env_vars(self, ctx: E2ETestContext): - cli_path = os.path.join(ctx.work_dir, "fake-cli-protocol-partial.js") - capture_path = os.path.join(ctx.work_dir, "fake-cli-protocol-partial-capture.json") - with open(cli_path, "w") as f: - f.write(FAKE_STDIO_CLI_SCRIPT) - - client = CopilotClient( - **_make_options( - ctx, - cli_path=cli_path, - cli_args=["--capture-file", capture_path], - github_token="process-option-token", - telemetry={ - "otlp_traces_protocol": "http/protobuf", - }, - use_logged_in_user=False, - ), - ) - try: - await client.start() - - with open(capture_path) as f: - capture = json.load(f) - env = capture["env"] - - assert env["COPILOT_OTEL_ENABLED"] == "true" - assert env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] == "http/protobuf" - assert "OTEL_EXPORTER_OTLP_PROTOCOL" not in env - assert "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL" not in env - finally: - try: - await client.stop() - except Exception: - await client.force_stop() diff --git a/python/e2e/test_telemetry_e2e.py b/python/e2e/test_telemetry_e2e.py index fc12b1f3ac..cc87cf321d 100644 --- a/python/e2e/test_telemetry_e2e.py +++ b/python/e2e/test_telemetry_e2e.py @@ -187,8 +187,6 @@ async def test_default_values_are_unset(self): cfg: TelemetryConfig = TelemetryConfig() assert cfg.get("otlp_endpoint") is None assert cfg.get("otlp_protocol") is None - assert cfg.get("otlp_traces_protocol") is None - assert cfg.get("otlp_metrics_protocol") is None assert cfg.get("file_path") is None assert cfg.get("exporter_type") is None assert cfg.get("source_name") is None @@ -198,8 +196,6 @@ async def test_can_set_all_properties(self): cfg: TelemetryConfig = TelemetryConfig( otlp_endpoint="http://localhost:4318", otlp_protocol="http/protobuf", - otlp_traces_protocol="http/json", - otlp_metrics_protocol="http/protobuf", file_path="/tmp/traces.json", exporter_type="otlp-http", source_name="my-app", @@ -207,8 +203,6 @@ async def test_can_set_all_properties(self): ) assert cfg["otlp_endpoint"] == "http://localhost:4318" assert cfg["otlp_protocol"] == "http/protobuf" - assert cfg["otlp_traces_protocol"] == "http/json" - assert cfg["otlp_metrics_protocol"] == "http/protobuf" assert cfg["file_path"] == "/tmp/traces.json" assert cfg["exporter_type"] == "otlp-http" assert cfg["source_name"] == "my-app" diff --git a/python/test_telemetry.py b/python/test_telemetry.py index 0d2bd75ef6..8a34f19b2b 100644 --- a/python/test_telemetry.py +++ b/python/test_telemetry.py @@ -78,8 +78,6 @@ def test_telemetry_env_var_mapping(self): config: TelemetryConfig = { "otlp_endpoint": "http://localhost:4318", "otlp_protocol": "http/protobuf", - "otlp_traces_protocol": "http/json", - "otlp_metrics_protocol": "http/protobuf", "file_path": "/tmp/traces.jsonl", "exporter_type": "file", "source_name": "test-app", @@ -92,10 +90,6 @@ def test_telemetry_env_var_mapping(self): env["OTEL_EXPORTER_OTLP_ENDPOINT"] = config["otlp_endpoint"] if "otlp_protocol" in config: env["OTEL_EXPORTER_OTLP_PROTOCOL"] = config["otlp_protocol"] - if "otlp_traces_protocol" in config: - env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] = config["otlp_traces_protocol"] - if "otlp_metrics_protocol" in config: - env["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] = config["otlp_metrics_protocol"] if "file_path" in config: env["COPILOT_OTEL_FILE_EXPORTER_PATH"] = config["file_path"] if "exporter_type" in config: @@ -110,8 +104,6 @@ def test_telemetry_env_var_mapping(self): assert env["COPILOT_OTEL_ENABLED"] == "true" assert env["OTEL_EXPORTER_OTLP_ENDPOINT"] == "http://localhost:4318" assert env["OTEL_EXPORTER_OTLP_PROTOCOL"] == "http/protobuf" - assert env["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] == "http/json" - assert env["OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"] == "http/protobuf" assert env["COPILOT_OTEL_FILE_EXPORTER_PATH"] == "/tmp/traces.jsonl" assert env["COPILOT_OTEL_EXPORTER_TYPE"] == "file" assert env["COPILOT_OTEL_SOURCE_NAME"] == "test-app" diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 832e998616..d849669982 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -404,10 +404,9 @@ impl OtelExporterType { /// OTLP HTTP protocol used by the CLI's OpenTelemetry OTLP exporter. /// -/// Maps to the standard `OTEL_EXPORTER_OTLP_PROTOCOL`, -/// `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL`, and -/// `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` environment variables on the spawned -/// CLI process. Wire values are `"http/json"` and `"http/protobuf"`. +/// Maps to the standard `OTEL_EXPORTER_OTLP_PROTOCOL` environment variable on +/// the spawned CLI process. Wire values are `"http/json"` and +/// `"http/protobuf"`. #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[non_exhaustive] pub enum OtlpHttpProtocol { @@ -445,8 +444,6 @@ impl OtlpHttpProtocol { /// | (any field set) | `COPILOT_OTEL_ENABLED=true` | /// | [`otlp_endpoint`] | `OTEL_EXPORTER_OTLP_ENDPOINT` | /// | [`otlp_protocol`] | `OTEL_EXPORTER_OTLP_PROTOCOL` | -/// | [`otlp_traces_protocol`] | `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | -/// | [`otlp_metrics_protocol`] | `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` | /// | [`file_path`] | `COPILOT_OTEL_FILE_EXPORTER_PATH` | /// | [`exporter_type`] | `COPILOT_OTEL_EXPORTER_TYPE` | /// | [`source_name`] | `COPILOT_OTEL_SOURCE_NAME` | @@ -461,8 +458,6 @@ impl OtlpHttpProtocol { /// /// [`otlp_endpoint`]: Self::otlp_endpoint /// [`otlp_protocol`]: Self::otlp_protocol -/// [`otlp_traces_protocol`]: Self::otlp_traces_protocol -/// [`otlp_metrics_protocol`]: Self::otlp_metrics_protocol /// [`file_path`]: Self::file_path /// [`exporter_type`]: Self::exporter_type /// [`source_name`]: Self::source_name @@ -474,10 +469,6 @@ pub struct TelemetryConfig { pub otlp_endpoint: Option, /// OTLP HTTP protocol for all signals. pub otlp_protocol: Option, - /// OTLP HTTP protocol for traces. - pub otlp_traces_protocol: Option, - /// OTLP HTTP protocol for metrics. - pub otlp_metrics_protocol: Option, /// File path for JSON-lines trace output. pub file_path: Option, /// Exporter backend type. Typically [`OtelExporterType::OtlpHttp`] or @@ -512,18 +503,6 @@ impl TelemetryConfig { self } - /// Set the OTLP HTTP protocol for traces. - pub fn with_otlp_traces_protocol(mut self, protocol: OtlpHttpProtocol) -> Self { - self.otlp_traces_protocol = Some(protocol); - self - } - - /// Set the OTLP HTTP protocol for metrics. - pub fn with_otlp_metrics_protocol(mut self, protocol: OtlpHttpProtocol) -> Self { - self.otlp_metrics_protocol = Some(protocol); - self - } - /// Set the file path for JSON-lines trace output. pub fn with_file_path(mut self, path: impl Into) -> Self { self.file_path = Some(path.into()); @@ -557,8 +536,6 @@ impl TelemetryConfig { pub fn is_empty(&self) -> bool { self.otlp_endpoint.is_none() && self.otlp_protocol.is_none() - && self.otlp_traces_protocol.is_none() - && self.otlp_metrics_protocol.is_none() && self.file_path.is_none() && self.exporter_type.is_none() && self.source_name.is_none() @@ -1272,12 +1249,6 @@ impl Client { if let Some(protocol) = telemetry.otlp_protocol { command.env("OTEL_EXPORTER_OTLP_PROTOCOL", protocol.as_str()); } - if let Some(protocol) = telemetry.otlp_traces_protocol { - command.env("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", protocol.as_str()); - } - if let Some(protocol) = telemetry.otlp_metrics_protocol { - command.env("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", protocol.as_str()); - } if let Some(path) = &telemetry.file_path { command.env("COPILOT_OTEL_FILE_EXPORTER_PATH", path); } @@ -2185,8 +2156,6 @@ mod tests { let cfg = TelemetryConfig::new() .with_otlp_endpoint("http://collector:4318") .with_otlp_protocol(OtlpHttpProtocol::HttpProtobuf) - .with_otlp_traces_protocol(OtlpHttpProtocol::HttpJson) - .with_otlp_metrics_protocol(OtlpHttpProtocol::HttpProtobuf) .with_file_path(PathBuf::from("/var/log/copilot.jsonl")) .with_exporter_type(OtelExporterType::OtlpHttp) .with_source_name("my-app") @@ -2194,11 +2163,6 @@ mod tests { assert_eq!(cfg.otlp_endpoint.as_deref(), Some("http://collector:4318")); assert_eq!(cfg.otlp_protocol, Some(OtlpHttpProtocol::HttpProtobuf)); - assert_eq!(cfg.otlp_traces_protocol, Some(OtlpHttpProtocol::HttpJson),); - assert_eq!( - cfg.otlp_metrics_protocol, - Some(OtlpHttpProtocol::HttpProtobuf), - ); assert_eq!( cfg.file_path.as_deref(), Some(Path::new("/var/log/copilot.jsonl")), @@ -2216,8 +2180,6 @@ mod tests { telemetry: Some(TelemetryConfig { otlp_endpoint: Some("http://collector:4318".to_string()), otlp_protocol: Some(OtlpHttpProtocol::HttpProtobuf), - otlp_traces_protocol: Some(OtlpHttpProtocol::HttpJson), - otlp_metrics_protocol: Some(OtlpHttpProtocol::HttpProtobuf), file_path: Some(PathBuf::from("/var/log/copilot.jsonl")), exporter_type: Some(OtelExporterType::OtlpHttp), source_name: Some("my-app".to_string()), @@ -2238,14 +2200,6 @@ mod tests { env_value(&cmd, "OTEL_EXPORTER_OTLP_PROTOCOL"), Some(std::ffi::OsStr::new("http/protobuf")), ); - assert_eq!( - env_value(&cmd, "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"), - Some(std::ffi::OsStr::new("http/json")), - ); - assert_eq!( - env_value(&cmd, "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"), - Some(std::ffi::OsStr::new("http/protobuf")), - ); assert_eq!( env_value(&cmd, "COPILOT_OTEL_FILE_EXPORTER_PATH"), Some(std::ffi::OsStr::new("/var/log/copilot.jsonl")), @@ -2272,8 +2226,6 @@ mod tests { "COPILOT_OTEL_ENABLED", "OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_PROTOCOL", - "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", - "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", "COPILOT_OTEL_FILE_EXPORTER_PATH", "COPILOT_OTEL_EXPORTER_TYPE", "COPILOT_OTEL_SOURCE_NAME", @@ -2308,8 +2260,6 @@ mod tests { // None of the other fields should leak as env vars. for key in [ "OTEL_EXPORTER_OTLP_PROTOCOL", - "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", - "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", "COPILOT_OTEL_FILE_EXPORTER_PATH", "COPILOT_OTEL_EXPORTER_TYPE", "COPILOT_OTEL_SOURCE_NAME", From baa0b181d8d31ba4afa5c8100200a31e6965366c Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Mon, 15 Jun 2026 16:13:48 -0400 Subject: [PATCH 3/4] Address telemetry protocol review feedback Narrow the Python OTLP protocol type, avoid hardcoding the CLI default in docs, and add Rust serde coverage tying protocol wire values to env values. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/getting-started.md | 2 +- docs/observability/opentelemetry.md | 2 +- python/copilot/client.py | 4 ++-- rust/src/lib.rs | 16 ++++++++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 4173fb4dd3..ad401894fd 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2233,7 +2233,7 @@ Dependency: `io.opentelemetry:opentelemetry-api` | Source name | `sourceName` | `source_name` | `SourceName` | `source_name` | `sourceName` | `SourceName` | Instrumentation scope name | | Capture content | `captureContent` | `capture_content` | `CaptureContent` | `capture_content` | `captureContent` | `CaptureContent` | Whether to capture message content | -The OTLP protocol field configures the CLI's `"otlp-http"` exporter for all signals. Leave it unset to use the CLI default (`"http/json"`), or set it to `"http/protobuf"` to export protobuf over HTTP. +The 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. ### File export diff --git a/docs/observability/opentelemetry.md b/docs/observability/opentelemetry.md index d883e66a7c..022a41c795 100644 --- a/docs/observability/opentelemetry.md +++ b/docs/observability/opentelemetry.md @@ -110,7 +110,7 @@ let client = Client::start(ClientOptions::new() | Source name | `sourceName` | `source_name` | `SourceName` | `SourceName` | `sourceName` | `source_name` | Instrumentation scope name | | Capture content | `captureContent` | `capture_content` | `CaptureContent` | `CaptureContent` | `captureContent` | `capture_content` | Whether to capture message content | -The OTLP protocol field configures the CLI's `"otlp-http"` exporter for all signals. Leave it unset to use the CLI default (`"http/json"`), or set it to `"http/protobuf"` to export protobuf over HTTP. +The 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. ### Trace context propagation diff --git a/python/copilot/client.py b/python/copilot/client.py index 15c2b44988..a27749b87d 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -182,8 +182,8 @@ class TelemetryConfig(TypedDict, total=False): otlp_endpoint: str """OTLP HTTP endpoint URL for trace/metric export. Sets OTEL_EXPORTER_OTLP_ENDPOINT.""" - otlp_protocol: str - """OTLP HTTP protocol for all signals. Sets OTEL_EXPORTER_OTLP_PROTOCOL.""" + otlp_protocol: Literal["http/json", "http/protobuf"] + """OTLP HTTP protocol for all signals ("http/json" or "http/protobuf"). Sets OTEL_EXPORTER_OTLP_PROTOCOL.""" file_path: str """File path for JSON-lines trace output. Sets COPILOT_OTEL_FILE_EXPORTER_PATH.""" exporter_type: str diff --git a/rust/src/lib.rs b/rust/src/lib.rs index d849669982..662218e46d 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2174,6 +2174,22 @@ mod tests { assert!(TelemetryConfig::new().is_empty()); } + #[test] + fn otlp_http_protocol_serde_matches_env_value() { + for (protocol, wire) in [ + (OtlpHttpProtocol::HttpJson, "http/json"), + (OtlpHttpProtocol::HttpProtobuf, "http/protobuf"), + ] { + assert_eq!(protocol.as_str(), wire); + + let serialized = serde_json::to_string(&protocol).unwrap(); + assert_eq!(serialized, format!("\"{wire}\"")); + + let deserialized: OtlpHttpProtocol = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized, protocol); + } + } + #[test] fn build_command_sets_otel_env_when_telemetry_enabled() { let opts = ClientOptions { From 2d0be753a1bd3e07f64c0ba354b7a9149e474168 Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Mon, 15 Jun 2026 16:17:38 -0400 Subject: [PATCH 4/4] Fix Python telemetry protocol docstring lint Wrap the OTLP protocol TypedDict docstring so Python CI lint passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/copilot/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/copilot/client.py b/python/copilot/client.py index a27749b87d..7dadad6c8b 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -183,7 +183,10 @@ class TelemetryConfig(TypedDict, total=False): otlp_endpoint: str """OTLP HTTP endpoint URL for trace/metric export. Sets OTEL_EXPORTER_OTLP_ENDPOINT.""" otlp_protocol: Literal["http/json", "http/protobuf"] - """OTLP HTTP protocol for all signals ("http/json" or "http/protobuf"). Sets OTEL_EXPORTER_OTLP_PROTOCOL.""" + """OTLP HTTP protocol for all signals. + + Allowed values are "http/json" and "http/protobuf". Sets OTEL_EXPORTER_OTLP_PROTOCOL. + """ file_path: str """File path for JSON-lines trace output. Sets COPILOT_OTEL_FILE_EXPORTER_PATH.""" exporter_type: str