-
Notifications
You must be signed in to change notification settings - Fork 20
feat: OpenTelemetry OTLP trace export from MCP Gateway and proxy #3178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
18fe18f
e6856e4
e0df370
62cae2e
57f1564
f638ba5
95e2119
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package cmd | ||
|
|
||
| // Tracing-related flags for OpenTelemetry OTLP trace export. | ||
|
|
||
| import ( | ||
| "github.com/github/gh-aw-mcpg/internal/config" | ||
| "github.com/github/gh-aw-mcpg/internal/envutil" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| // Tracing flag variables | ||
| var ( | ||
| otlpEndpoint string | ||
| otlpServiceName string | ||
| otlpSampleRate float64 | ||
| ) | ||
|
|
||
| func init() { | ||
| RegisterFlag(func(cmd *cobra.Command) { | ||
| cmd.Flags().StringVar(&otlpEndpoint, "otlp-endpoint", getDefaultOTLPEndpoint(), | ||
| "OTLP HTTP endpoint for trace export (e.g. http://localhost:4318). Overrides OTEL_EXPORTER_OTLP_ENDPOINT env var. Tracing is disabled when empty.") | ||
| cmd.Flags().StringVar(&otlpServiceName, "otlp-service-name", getDefaultOTLPServiceName(), | ||
| "Service name reported in traces. Overrides OTEL_SERVICE_NAME env var.") | ||
| cmd.Flags().Float64Var(&otlpSampleRate, "otlp-sample-rate", config.DefaultTracingSampleRate, | ||
| "Fraction of traces to sample and export (0.0–1.0). Default 1.0 samples everything.") | ||
| }) | ||
| } | ||
|
|
||
| // getDefaultOTLPEndpoint returns the OTLP endpoint, checking OTEL_EXPORTER_OTLP_ENDPOINT | ||
| // environment variable first, then falling back to empty (disabled). | ||
| func getDefaultOTLPEndpoint() string { | ||
| return envutil.GetEnvString("OTEL_EXPORTER_OTLP_ENDPOINT", "") | ||
| } | ||
|
|
||
| // getDefaultOTLPServiceName returns the OTLP service name, checking OTEL_SERVICE_NAME | ||
| // environment variable first, then falling back to the default. | ||
| func getDefaultOTLPServiceName() string { | ||
| return envutil.GetEnvString("OTEL_SERVICE_NAME", config.DefaultTracingServiceName) | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,7 @@ import ( | |||||||||||||||||||||||||||||||
| "github.com/github/gh-aw-mcpg/internal/difc" | ||||||||||||||||||||||||||||||||
| "github.com/github/gh-aw-mcpg/internal/logger" | ||||||||||||||||||||||||||||||||
| "github.com/github/gh-aw-mcpg/internal/server" | ||||||||||||||||||||||||||||||||
| "github.com/github/gh-aw-mcpg/internal/tracing" | ||||||||||||||||||||||||||||||||
| "github.com/github/gh-aw-mcpg/internal/version" | ||||||||||||||||||||||||||||||||
| "github.com/spf13/cobra" | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
@@ -312,6 +313,57 @@ func run(cmd *cobra.Command, args []string) error { | |||||||||||||||||||||||||||||||
| logger.LogInfoMd("startup", "Generated temporary random API key (spec §7.3)") | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Apply tracing flags: CLI flags override config values. | ||||||||||||||||||||||||||||||||
| // Merge CLI/env tracing settings into gateway config. | ||||||||||||||||||||||||||||||||
| if otlpEndpoint != "" || cmd.Flags().Changed("otlp-endpoint") { | ||||||||||||||||||||||||||||||||
| if cfg.Gateway.Tracing == nil { | ||||||||||||||||||||||||||||||||
| cfg.Gateway.Tracing = &config.TracingConfig{} | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| cfg.Gateway.Tracing.Endpoint = otlpEndpoint | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if cmd.Flags().Changed("otlp-service-name") { | ||||||||||||||||||||||||||||||||
| if cfg.Gateway.Tracing == nil { | ||||||||||||||||||||||||||||||||
| cfg.Gateway.Tracing = &config.TracingConfig{} | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| cfg.Gateway.Tracing.ServiceName = otlpServiceName | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if cmd.Flags().Changed("otlp-sample-rate") { | ||||||||||||||||||||||||||||||||
| if cfg.Gateway.Tracing == nil { | ||||||||||||||||||||||||||||||||
| cfg.Gateway.Tracing = &config.TracingConfig{} | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| cfg.Gateway.Tracing.SampleRate = otlpSampleRate | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Initialize OpenTelemetry tracer provider. | ||||||||||||||||||||||||||||||||
| // When no endpoint is configured, a noop provider is used (zero overhead). | ||||||||||||||||||||||||||||||||
| var tracingCfg *config.TracingConfig | ||||||||||||||||||||||||||||||||
| if cfg.Gateway != nil { | ||||||||||||||||||||||||||||||||
| tracingCfg = cfg.Gateway.Tracing | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| tracingProvider, err := tracing.InitProvider(ctx, tracingCfg) | ||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||
| log.Printf("Warning: failed to initialize tracing provider: %v", err) | ||||||||||||||||||||||||||||||||
| logger.LogWarn("startup", "Failed to initialize tracing provider: %v", err) | ||||||||||||||||||||||||||||||||
| // Non-fatal: continue without tracing | ||||||||||||||||||||||||||||||||
| tracingProvider, _ = tracing.InitProvider(ctx, nil) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| defer func() { | ||||||||||||||||||||||||||||||||
| shutdownCtxTracing, cancelTracing := context.WithTimeout(context.Background(), 5*time.Second) | ||||||||||||||||||||||||||||||||
| defer cancelTracing() | ||||||||||||||||||||||||||||||||
| if err := tracingProvider.Shutdown(shutdownCtxTracing); err != nil { | ||||||||||||||||||||||||||||||||
| log.Printf("Warning: tracing provider shutdown error: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if tracingCfg != nil && tracingCfg.Endpoint != "" { | ||||||||||||||||||||||||||||||||
| log.Printf("OpenTelemetry tracing enabled: endpoint=%s, service=%s, sampleRate=%.2f", | ||||||||||||||||||||||||||||||||
| tracingCfg.Endpoint, tracingCfg.ServiceName, tracingCfg.SampleRate) | ||||||||||||||||||||||||||||||||
| logger.LogInfoMd("startup", "OpenTelemetry tracing enabled: endpoint=%s, service=%s", | ||||||||||||||||||||||||||||||||
| tracingCfg.Endpoint, tracingCfg.ServiceName) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| if tracingCfg != nil && tracingCfg.Endpoint != "" { | |
| log.Printf("OpenTelemetry tracing enabled: endpoint=%s, service=%s, sampleRate=%.2f", | |
| tracingCfg.Endpoint, tracingCfg.ServiceName, tracingCfg.SampleRate) | |
| logger.LogInfoMd("startup", "OpenTelemetry tracing enabled: endpoint=%s, service=%s", | |
| tracingCfg.Endpoint, tracingCfg.ServiceName) | |
| resolvedTracingEndpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") | |
| if resolvedTracingEndpoint == "" && tracingCfg != nil { | |
| resolvedTracingEndpoint = tracingCfg.Endpoint | |
| } | |
| if resolvedTracingEndpoint != "" { | |
| log.Printf("OpenTelemetry tracing enabled: endpoint=%s, service=%s, sampleRate=%.2f", | |
| resolvedTracingEndpoint, tracingCfg.ServiceName, tracingCfg.SampleRate) | |
| logger.LogInfoMd("startup", "OpenTelemetry tracing enabled: endpoint=%s, service=%s", | |
| resolvedTracingEndpoint, tracingCfg.ServiceName) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // Package config provides configuration loading and parsing. | ||
| // This file defines the tracing configuration for OpenTelemetry OTLP export. | ||
| package config | ||
|
|
||
| // DefaultTracingSampleRate is the default sample rate for tracing (100% sampling). | ||
| const DefaultTracingSampleRate = 1.0 | ||
|
|
||
| // DefaultTracingServiceName is the default service name for tracing. | ||
| const DefaultTracingServiceName = "mcp-gateway" | ||
|
|
||
| // TracingConfig holds OpenTelemetry tracing configuration. | ||
| // Tracing is disabled when Endpoint is empty. | ||
| // | ||
| // Configuration can also be provided via standard OTEL environment variables: | ||
| // - OTEL_EXPORTER_OTLP_ENDPOINT — overrides Endpoint | ||
| // - OTEL_SERVICE_NAME — overrides ServiceName | ||
| // | ||
| // Example TOML: | ||
| // | ||
| // [gateway.tracing] | ||
| // endpoint = "http://localhost:4318" | ||
| // service_name = "mcp-gateway" | ||
| // sample_rate = 1.0 | ||
| type TracingConfig struct { | ||
| // Endpoint is the OTLP HTTP endpoint to export traces to. | ||
| // Example: "http://localhost:4318" (Jaeger, Grafana Tempo, Honeycomb, etc.) | ||
| // If empty, tracing is disabled and a noop tracer is used. | ||
| Endpoint string `toml:"endpoint" json:"endpoint,omitempty"` | ||
|
|
||
| // ServiceName is the service name reported in traces. | ||
| // Defaults to "mcp-gateway". | ||
| ServiceName string `toml:"service_name" json:"service_name,omitempty"` | ||
|
|
||
| // SampleRate controls the fraction of traces that are sampled and exported. | ||
| // Valid range: 0.0 (no sampling) to 1.0 (sample everything). | ||
| // Defaults to 1.0 (100% sampling). | ||
| SampleRate float64 `toml:"sample_rate" json:"sample_rate,omitempty"` | ||
| } | ||
|
|
||
| func init() { | ||
| // Register default setter for Tracing config | ||
| RegisterDefaults(func(cfg *Config) { | ||
| if cfg.Gateway != nil && cfg.Gateway.Tracing != nil { | ||
| if cfg.Gateway.Tracing.ServiceName == "" { | ||
| cfg.Gateway.Tracing.ServiceName = DefaultTracingServiceName | ||
| } | ||
| if cfg.Gateway.Tracing.SampleRate == 0 { | ||
| cfg.Gateway.Tracing.SampleRate = DefaultTracingSampleRate | ||
| } | ||
| } | ||
|
Comment on lines
+51
to
+56
|
||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flag help text says
--otlp-endpoint/--otlp-service-nameoverride the corresponding OTEL env vars, buttracing.resolveEndpoint/resolveServiceNamecurrently preferOTEL_EXPORTER_OTLP_ENDPOINT/OTEL_SERVICE_NAMEover config/flags. This means operators cannot override (or disable) an env-configured endpoint via CLI, which is surprising and contradicts the help. Consider changing precedence to CLI > env > config (e.g., resolve envs in cmd layer and pass resolved values to InitProvider), or update the help text to match actual precedence.