feat: OTLP multi-endpoint fan-out via GH_AW_OTLP_ENDPOINTS by Copilot · Pull Request #7446 · github/gh-aw-mcpg · GitHub
Skip to content

feat: OTLP multi-endpoint fan-out via GH_AW_OTLP_ENDPOINTS#7446

Merged
lpcox merged 4 commits into
mainfrom
copilot/gh-aw-otlp-endpoints-support-multi-endpoint-fan-ou
Jun 12, 2026
Merged

feat: OTLP multi-endpoint fan-out via GH_AW_OTLP_ENDPOINTS#7446
lpcox merged 4 commits into
mainfrom
copilot/gh-aw-otlp-endpoints-support-multi-endpoint-fan-ou

Conversation

Copilot AI commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

The gateway only exported spans to a single OTLP endpoint (OTEL_EXPORTER_OTLP_ENDPOINT), so when gh-aw configured multiple backends via GH_AW_OTLP_ENDPOINTS, non-primary backends received lifecycle spans but zero gateway/tool-call spans.

Changes

New GH_AW_OTLP_ENDPOINTS env var (comma-separated URLs)

  • When set, supersedes the single-endpoint path; one otlptracehttp exporter is created per URL
  • All endpoints share headers from OTEL_EXPORTER_OTLP_HEADERS / config Headers
  • Falls back to existing OTEL_EXPORTER_OTLP_ENDPOINT behaviour when unset

internal/tracing/fanout.go — new fanoutExporter (sdktrace.SpanExporter)

  • Fans out ExportSpans / Shutdown to every underlying exporter
  • Partial-failure tolerant: a failing backend never blocks the others; errors are joined

internal/tracing/config_resolver.goresolveExtraEndpoints

  • Parses GH_AW_OTLP_ENDPOINTS, normalises each URL with the configured signal path (default /v1/traces)

internal/tracing/provider.go

  • InitProvider selects fan-out or single-endpoint based on GH_AW_OTLP_ENDPOINTS
  • Added IsEnabled() bool to distinguish real SDK providers from noop

internal/cmd/root.go

  • Startup log uses IsEnabled() and falls back to GH_AW_OTLP_ENDPOINTS as display value when primary endpoint is empty
# Fan-out to two backends; shared auth header applied to both
GH_AW_OTLP_ENDPOINTS=https://primary.otel.example.com,https://secondary.otel.example.com
OTEL_EXPORTER_OTLP_HEADERS=Authorization=******

GitHub Advanced Security started work on behalf of lpcox June 12, 2026 22:23 View session
GitHub Advanced Security finished work on behalf of lpcox June 12, 2026 22:24
GitHub Advanced Security started work on behalf of lpcox June 12, 2026 22:34 View session
GitHub Advanced Security started work on behalf of lpcox June 12, 2026 22:36 View session
Copilot AI changed the title [WIP] Add support for OTLP multi-endpoint fan-out in MCP gateway feat: OTLP multi-endpoint fan-out via GH_AW_OTLP_ENDPOINTS Jun 12, 2026
GitHub Advanced Security finished work on behalf of lpcox June 12, 2026 22:36
Copilot finished work on behalf of lpcox June 12, 2026 22:36
Copilot AI requested a review from lpcox June 12, 2026 22:36
GitHub Advanced Security finished work on behalf of lpcox June 12, 2026 22:38
@lpcox lpcox marked this pull request as ready for review June 12, 2026 22:58
Copilot AI review requested due to automatic review settings June 12, 2026 22:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds OpenTelemetry OTLP multi-endpoint “fan-out” support so the gateway can export spans to every backend listed in GH_AW_OTLP_ENDPOINTS, rather than only the single OTEL_EXPORTER_OTLP_ENDPOINT.

Changes:

  • Added GH_AW_OTLP_ENDPOINTS parsing and endpoint normalization (signal path handling) to support multiple OTLP/HTTP destinations.
  • Introduced a fanoutExporter SpanExporter to forward exports/shutdown across multiple underlying exporters.
  • Updated tracing initialization + startup logging to distinguish noop vs real SDK providers via Provider.IsEnabled(), and added tests/docs.
Show a summary per file
File Description
internal/tracing/resolve_endpoint_test.go Adds unit tests for parsing/normalizing GH_AW_OTLP_ENDPOINTS.
internal/tracing/provider.go Builds one exporter per endpoint and wraps them with a fan-out exporter; adds IsEnabled().
internal/tracing/provider_test.go Adds tests for IsEnabled() and fan-out export behavior.
internal/tracing/fanout.go New fan-out SpanExporter implementation.
internal/tracing/fanout_test.go Adds unit tests for the fan-out exporter.
internal/tracing/config_resolver.go Adds resolveExtraEndpoints to parse and normalize the multi-endpoint env var.
internal/cmd/root.go Updates startup logging to use IsEnabled() and display an endpoint value.
AGENTS.md Documents the new GH_AW_OTLP_ENDPOINTS env var behavior.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 8/8 changed files
  • Comments generated: 6

Comment thread internal/cmd/root.go Outdated
Comment on lines +339 to +344
displayEndpoint := endpoint
if displayEndpoint == "" {
displayEndpoint = os.Getenv("GH_AW_OTLP_ENDPOINTS")
}
logger.StartupInfo("OpenTelemetry tracing enabled: endpoint=%s, service=%s, sampleRate=%.2f",
endpoint, serviceName, sampleRate)
displayEndpoint, serviceName, sampleRate)
Comment on lines +166 to +168
if len(exporters) == 0 {
return nil, fmt.Errorf("failed to create any OTLP trace exporters")
}
Comment thread internal/tracing/fanout_test.go Outdated
assert.Equal(t, 1, ok.shutdowns, "healthy exporter should still be shut down")
}

// TestFanoutExporter_ExportSpans_Empty verifies no-op on empty span slice.
Comment thread internal/tracing/fanout.go Outdated
Comment on lines +27 to +38
// ExportSpans exports spans to each underlying exporter in order. Export
// continues to the next exporter even if the current one returns an error so
// that a single backend failure does not prevent delivery to the others. All
// errors are joined and returned.
func (f *fanoutExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error {
var errs []error
for _, exp := range f.exporters {
if err := exp.ExportSpans(ctx, spans); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
Comment on lines +27 to +33
func TestInitProvider_IsEnabled_Noop(t *testing.T) {
ctx := context.Background()
provider, err := tracing.InitProvider(ctx, nil)
require.NoError(t, err)
defer provider.Shutdown(ctx)
assert.False(t, provider.IsEnabled(), "noop provider should report IsEnabled=false")
}
Comment on lines +36 to +50
func TestInitProvider_IsEnabled_SDK(t *testing.T) {
ctx := context.Background()
cfg := &config.TracingConfig{
Endpoint: "http://localhost:14318",
ServiceName: "test",
}
provider, err := tracing.InitProvider(ctx, cfg)
require.NoError(t, err)
defer func() {
shutdownCtx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel()
_ = provider.Shutdown(shutdownCtx)
}()
assert.True(t, provider.IsEnabled(), "SDK provider should report IsEnabled=true")
}
@lpcox

lpcox commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

@copilot address review feedback

Copilot AI commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

GitHub Advanced Security started work on behalf of lpcox June 12, 2026 23:24 View session
Copilot finished work on behalf of lpcox June 12, 2026 23:24
GitHub Advanced Security finished work on behalf of lpcox June 12, 2026 23:26
@lpcox lpcox merged commit 426f338 into main Jun 12, 2026
27 checks passed
@lpcox lpcox deleted the copilot/gh-aw-otlp-endpoints-support-multi-endpoint-fan-ou branch June 12, 2026 23:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants