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 f4e92f6fe..33ccf4caa 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 d89f68ca9..af7ad4caa 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 c2829277d..d51b9629c 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 e98513c53..45d08d173 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 7a2ad2951..c05ea9329 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 6360cb55a..6e95654e3 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 9229285b9..e275184a0 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 4bdbc75f5..b9ce90d24 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 cad460557..f76431ce1 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 4f8b74f2b..c291e46ef 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 071030281..998fd8680 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 6245ce519..ed538bbf2 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 a6a08a848..af3e01546 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 c0b75f29d..e73b2a543 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 2df5dafab..e50c278df 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 2dd41c28a..2fc0ea9f0 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 91aa5c4be..240efe3c8 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 c59bd3e94..a72d0ab1b 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 647ca79c1..e7864e22b 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 dadce08e1..8eb4a76b5 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 9ad97b63a..5889810e1 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 e77fcf16c..015cc053e 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 24eec9d72..99a994911 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 f32d923a3..04b9a8c22 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 f18a9fb88..fc12b1f3a 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 6481fd525..0d2bd75ef 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 0b5bec1cd..52ccad635 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 cab34b476..832e99861 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 33ccf4caa..4173fb4dd 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 af7ad4caa..d883e66a7 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 d51b9629c..b7a73e826 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 45d08d173..e83c71bc0 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 c05ea9329..c6c30e3e8 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 6e95654e3..c2f16a042 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 e275184a0..979e575b3 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 b9ce90d24..f595eb769 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 f76431ce1..b73240b1b 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 c291e46ef..aa42be1f3 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 998fd8680..67a4cf82e 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 ed538bbf2..8225db0ef 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 af3e01546..acc683a72 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 e73b2a543..593908ff8 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 e50c278df..b445d6153 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 2fc0ea9f0..739d6a510 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 240efe3c8..08a691a29 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 a72d0ab1b..223e92468 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 e7864e22b..4d36ca0a9 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 8eb4a76b5..2cfc69456 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 5889810e1..78d9654ed 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 015cc053e..19373567c 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 99a994911..15c2b4498 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 04b9a8c22..9a70c6cbf 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 fc12b1f3a..cc87cf321 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 0d2bd75ef..8a34f19b2 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 832e99861..d84966998 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 4173fb4dd..ad401894f 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 d883e66a7..022a41c79 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 15c2b4498..a27749b87 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 d84966998..662218e46 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 a27749b87..7dadad6c8 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