From c41d92c9ebe5c055cc0edecad8e05aab0a03244b Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Tue, 24 Feb 2026 20:00:08 +0530 Subject: [PATCH 1/6] fix: log configuration --- .../telemetry/attribute_processor_test.go | 53 +++++++++++++++++++ router/pkg/trace/errors.go | 34 ++++++++++++ router/pkg/trace/meter.go | 4 +- 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 router/pkg/trace/errors.go diff --git a/router-tests/telemetry/attribute_processor_test.go b/router-tests/telemetry/attribute_processor_test.go index 4e58aa05b5..df244183c8 100644 --- a/router-tests/telemetry/attribute_processor_test.go +++ b/router-tests/telemetry/attribute_processor_test.go @@ -1,8 +1,11 @@ package telemetry import ( + "context" + "fmt" "strings" "testing" + "time" "github.com/stretchr/testify/require" "github.com/wundergraph/cosmo/router-tests/testenv" @@ -10,6 +13,7 @@ import ( "github.com/wundergraph/cosmo/router/pkg/config" "github.com/wundergraph/cosmo/router/pkg/trace/tracetest" "go.opentelemetry.io/otel/attribute" + sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.uber.org/zap/zapcore" ) @@ -184,6 +188,36 @@ func TestAttributeProcessorIntegration(t *testing.T) { }) }) + t.Run("invalid UTF-8 export error logs config hint", func(t *testing.T) { + t.Parallel() + + exporter := &invalidUTF8Exporter{} + + testenv.Run(t, &testenv.Config{ + TraceExporter: exporter, + LogObservation: testenv.LogObservationConfig{ + Enabled: true, + LogLevel: zapcore.ErrorLevel, + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query { employees { id } }`, + }) + require.Equal(t, 200, res.Response.StatusCode) + + // The SimpleSpanProcessor calls otel.Handle(err) synchronously on span end, + // but give a small window for log propagation. + require.Eventually(t, func() bool { + logs := xEnv.Observer().FilterMessageSnippet("sanitize_utf8").All() + return len(logs) > 0 + }, 5*time.Second, 100*time.Millisecond) + + logs := xEnv.Observer().FilterMessageSnippet("sanitize_utf8").All() + require.NotEmpty(t, logs) + require.Contains(t, logs[0].Message, "telemetry.tracing.sanitize_utf8.enabled") + }) + }) + t.Run("IPAnonymization hashes IP attributes", func(t *testing.T) { t.Parallel() @@ -220,3 +254,22 @@ func TestAttributeProcessorIntegration(t *testing.T) { }) }) } + +// errInvalidUTF8 mimics google.golang.org/protobuf/internal/impl.errInvalidUTF8 +// so that errors.As can match it via the invalidUTF8Error interface used in the router. +type errInvalidUTF8 struct{} + +func (errInvalidUTF8) Error() string { return "string field contains invalid UTF-8" } +func (errInvalidUTF8) InvalidUTF8() bool { return true } + +// invalidUTF8Exporter is a SpanExporter that always returns an invalid UTF-8 error, +// simulating what happens when protobuf marshaling encounters invalid UTF-8 in span attributes. +type invalidUTF8Exporter struct{} + +func (e *invalidUTF8Exporter) ExportSpans(_ context.Context, _ []sdktrace.ReadOnlySpan) error { + return fmt.Errorf("traces export: %w", errInvalidUTF8{}) +} + +func (e *invalidUTF8Exporter) Shutdown(_ context.Context) error { + return nil +} diff --git a/router/pkg/trace/errors.go b/router/pkg/trace/errors.go new file mode 100644 index 0000000000..9ec3ebece4 --- /dev/null +++ b/router/pkg/trace/errors.go @@ -0,0 +1,34 @@ +package trace + +import ( + "errors" + "go.uber.org/zap" +) + +// invalidUTF8Error matches the InvalidUTF8() method exposed by +// google.golang.org/protobuf/internal/impl.errInvalidUTF8. +type invalidUTF8Error interface { + InvalidUTF8() bool +} + +// hasInvalidUTF8Error walks the error chain looking for an error +// that implements the invalidUTF8Error interface. +func hasInvalidUTF8Error(err error) bool { + var target invalidUTF8Error + if errors.As(err, &target) { + return target.InvalidUTF8() + } + return false +} + +func errHandler(config *ProviderConfig) func(err error) { + return func(err error) { + if hasInvalidUTF8Error(err) { + config.Logger.Error( + "otel error: Enable 'telemetry.tracing.sanitize_utf8.enabled' in your config to sanitize invalid UTF-8 attributes.", + zap.Error(err)) + return + } + config.Logger.Error("otel error", zap.Error(err)) + } +} diff --git a/router/pkg/trace/meter.go b/router/pkg/trace/meter.go index 780cb6807d..69cef72beb 100644 --- a/router/pkg/trace/meter.go +++ b/router/pkg/trace/meter.go @@ -219,9 +219,7 @@ func NewTracerProvider(ctx context.Context, config *ProviderConfig) (*sdktrace.T otel.SetTracerProvider(tp) } - otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) { - config.Logger.Error("otel error", zap.Error(err)) - })) + otel.SetErrorHandler(otel.ErrorHandlerFunc(errHandler(config))) return tp, nil } From eb1a06ecf3afb6facfe95f315f6571085d9c2669 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Tue, 24 Feb 2026 20:04:17 +0530 Subject: [PATCH 2/6] fix: reword hints --- router-tests/telemetry/attribute_processor_test.go | 10 ++-------- router/pkg/trace/errors.go | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/router-tests/telemetry/attribute_processor_test.go b/router-tests/telemetry/attribute_processor_test.go index df244183c8..17985038ef 100644 --- a/router-tests/telemetry/attribute_processor_test.go +++ b/router-tests/telemetry/attribute_processor_test.go @@ -191,10 +191,8 @@ func TestAttributeProcessorIntegration(t *testing.T) { t.Run("invalid UTF-8 export error logs config hint", func(t *testing.T) { t.Parallel() - exporter := &invalidUTF8Exporter{} - testenv.Run(t, &testenv.Config{ - TraceExporter: exporter, + TraceExporter: &invalidUTF8Exporter{}, LogObservation: testenv.LogObservationConfig{ Enabled: true, LogLevel: zapcore.ErrorLevel, @@ -205,8 +203,6 @@ func TestAttributeProcessorIntegration(t *testing.T) { }) require.Equal(t, 200, res.Response.StatusCode) - // The SimpleSpanProcessor calls otel.Handle(err) synchronously on span end, - // but give a small window for log propagation. require.Eventually(t, func() bool { logs := xEnv.Observer().FilterMessageSnippet("sanitize_utf8").All() return len(logs) > 0 @@ -214,7 +210,7 @@ func TestAttributeProcessorIntegration(t *testing.T) { logs := xEnv.Observer().FilterMessageSnippet("sanitize_utf8").All() require.NotEmpty(t, logs) - require.Contains(t, logs[0].Message, "telemetry.tracing.sanitize_utf8.enabled") + require.Equal(t, logs[0].Message, "otel error: traces export: string field contains invalid UTF-8: Enable 'telemetry.tracing.sanitize_utf8.enabled' in your config to sanitize invalid UTF-8 attributes.") }) }) @@ -262,8 +258,6 @@ type errInvalidUTF8 struct{} func (errInvalidUTF8) Error() string { return "string field contains invalid UTF-8" } func (errInvalidUTF8) InvalidUTF8() bool { return true } -// invalidUTF8Exporter is a SpanExporter that always returns an invalid UTF-8 error, -// simulating what happens when protobuf marshaling encounters invalid UTF-8 in span attributes. type invalidUTF8Exporter struct{} func (e *invalidUTF8Exporter) ExportSpans(_ context.Context, _ []sdktrace.ReadOnlySpan) error { diff --git a/router/pkg/trace/errors.go b/router/pkg/trace/errors.go index 9ec3ebece4..b0a0ba107f 100644 --- a/router/pkg/trace/errors.go +++ b/router/pkg/trace/errors.go @@ -25,7 +25,7 @@ func errHandler(config *ProviderConfig) func(err error) { return func(err error) { if hasInvalidUTF8Error(err) { config.Logger.Error( - "otel error: Enable 'telemetry.tracing.sanitize_utf8.enabled' in your config to sanitize invalid UTF-8 attributes.", + "otel error: traces export: string field contains invalid UTF-8: Enable 'telemetry.tracing.sanitize_utf8.enabled' in your config to sanitize invalid UTF-8 attributes.", zap.Error(err)) return } From 17654dc4c00ee72b24e12cce11589a8e90a20c03 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Tue, 24 Feb 2026 22:28:38 +0530 Subject: [PATCH 3/6] fix: review comments --- router-tests/telemetry/attribute_processor_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router-tests/telemetry/attribute_processor_test.go b/router-tests/telemetry/attribute_processor_test.go index 17985038ef..23c5e2bf52 100644 --- a/router-tests/telemetry/attribute_processor_test.go +++ b/router-tests/telemetry/attribute_processor_test.go @@ -210,7 +210,7 @@ func TestAttributeProcessorIntegration(t *testing.T) { logs := xEnv.Observer().FilterMessageSnippet("sanitize_utf8").All() require.NotEmpty(t, logs) - require.Equal(t, logs[0].Message, "otel error: traces export: string field contains invalid UTF-8: Enable 'telemetry.tracing.sanitize_utf8.enabled' in your config to sanitize invalid UTF-8 attributes.") + require.Equal(t, "otel error: traces export: string field contains invalid UTF-8: Enable 'telemetry.tracing.sanitize_utf8.enabled' in your config to sanitize invalid UTF-8 attributes.", logs[0].Message) }) }) From 91adabb053148c9bc0a77d4933cb3c3793dd0ce9 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Thu, 5 Mar 2026 15:31:48 +0530 Subject: [PATCH 4/6] fix: add envars --- router/pkg/config/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index 10881ddd25..1024d636ab 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -148,9 +148,9 @@ type Metrics struct { } type MetricsLogExporter struct { - Enabled bool `yaml:"enabled" envDefault:"false"` - ExcludeMetrics RegExArray `yaml:"exclude_metrics,omitempty"` - IncludeMetrics RegExArray `yaml:"include_metrics,omitempty"` + Enabled bool `yaml:"enabled" envDefault:"false" env:"METRICS_OTLP_LOG_EXPORTER_ENABLED"` + ExcludeMetrics RegExArray `yaml:"exclude_metrics,omitempty" env:"METRICS_OTLP_LOG_EXPORTER_EXCLUDE_METRICS"` + IncludeMetrics RegExArray `yaml:"include_metrics,omitempty" env:"METRICS_OTLP_LOG_EXPORTER_INCLUDE_METRICS"` } type MetricsOTLP struct { From 3fb13fc23af111bca403eb9e04d7a45aff8b8fb5 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Fri, 6 Mar 2026 02:01:19 +0530 Subject: [PATCH 5/6] fix: updates --- router/pkg/config/config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index 1024d636ab..e50a2e510d 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -148,9 +148,9 @@ type Metrics struct { } type MetricsLogExporter struct { - Enabled bool `yaml:"enabled" envDefault:"false" env:"METRICS_OTLP_LOG_EXPORTER_ENABLED"` - ExcludeMetrics RegExArray `yaml:"exclude_metrics,omitempty" env:"METRICS_OTLP_LOG_EXPORTER_EXCLUDE_METRICS"` - IncludeMetrics RegExArray `yaml:"include_metrics,omitempty" env:"METRICS_OTLP_LOG_EXPORTER_INCLUDE_METRICS"` + Enabled bool `yaml:"enabled" envDefault:"false" env:"ENABLED"` + ExcludeMetrics RegExArray `yaml:"exclude_metrics,omitempty" env:"EXCLUDE_METRICS"` + IncludeMetrics RegExArray `yaml:"include_metrics,omitempty" env:"INCLUDE_METRICS"` } type MetricsOTLP struct { @@ -164,7 +164,7 @@ type MetricsOTLP struct { ExcludeMetrics RegExArray `yaml:"exclude_metrics,omitempty" env:"METRICS_OTLP_EXCLUDE_METRICS"` ExcludeMetricLabels RegExArray `yaml:"exclude_metric_labels,omitempty" env:"METRICS_OTLP_EXCLUDE_METRIC_LABELS"` Exporters []MetricsOTLPExporter `yaml:"exporters"` - LogExporter MetricsLogExporter `yaml:"log_exporter"` + LogExporter MetricsLogExporter `yaml:"log_exporter" envPrefix:"METRICS_OTLP_LOG_EXPORTER_"` } type Telemetry struct { From e32c73b3225cec064c8825f880dab4d487dd4cd6 Mon Sep 17 00:00:00 2001 From: Milinda Dias Date: Fri, 6 Mar 2026 02:36:49 +0530 Subject: [PATCH 6/6] fix: updates --- router-tests/telemetry/attribute_processor_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/router-tests/telemetry/attribute_processor_test.go b/router-tests/telemetry/attribute_processor_test.go index 23c5e2bf52..9d9b6041ce 100644 --- a/router-tests/telemetry/attribute_processor_test.go +++ b/router-tests/telemetry/attribute_processor_test.go @@ -198,15 +198,16 @@ func TestAttributeProcessorIntegration(t *testing.T) { LogLevel: zapcore.ErrorLevel, }, }, func(t *testing.T, xEnv *testenv.Environment) { - res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ - Query: `query { employees { id } }`, - }) - require.Equal(t, 200, res.Response.StatusCode) - require.Eventually(t, func() bool { + res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query { employees { id } }`, + }) + if res.Response.StatusCode != 200 { + return false + } logs := xEnv.Observer().FilterMessageSnippet("sanitize_utf8").All() return len(logs) > 0 - }, 5*time.Second, 100*time.Millisecond) + }, 10*time.Second, 500*time.Millisecond) logs := xEnv.Observer().FilterMessageSnippet("sanitize_utf8").All() require.NotEmpty(t, logs)