You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-07-17 01:12:45 +02:00
Escape exemplar keys to fix invalid key errors (#5995)
Fixes https://github.com/open-telemetry/opentelemetry-go/issues/5936
This commit is contained in:
@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
- Fix inconsistent request body closing in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#5954)
|
- Fix inconsistent request body closing in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#5954)
|
||||||
- Fix inconsistent request body closing in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#5954)
|
- Fix inconsistent request body closing in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#5954)
|
||||||
- Fix inconsistent request body closing in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#5954)
|
- Fix inconsistent request body closing in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#5954)
|
||||||
|
- Fix invalid exemplar keys in `go.opentelemetry.io/otel/exporters/prometheus`. (#5995)
|
||||||
|
|
||||||
<!-- Released section -->
|
<!-- Released section -->
|
||||||
<!-- Don't change this section unless doing release -->
|
<!-- Don't change this section unless doing release -->
|
||||||
|
@ -547,7 +547,8 @@ func addExemplars[N int64 | float64](m prometheus.Metric, exemplars []metricdata
|
|||||||
func attributesToLabels(attrs []attribute.KeyValue) prometheus.Labels {
|
func attributesToLabels(attrs []attribute.KeyValue) prometheus.Labels {
|
||||||
labels := make(map[string]string)
|
labels := make(map[string]string)
|
||||||
for _, attr := range attrs {
|
for _, attr := range attrs {
|
||||||
labels[string(attr.Key)] = attr.Value.Emit()
|
key := model.EscapeName(string(attr.Key), model.NameEscapingScheme)
|
||||||
|
labels[key] = attr.Value.Emit()
|
||||||
}
|
}
|
||||||
return labels
|
return labels
|
||||||
}
|
}
|
||||||
|
@ -949,37 +949,94 @@ func TestShutdownExporter(t *testing.T) {
|
|||||||
|
|
||||||
func TestExemplars(t *testing.T) {
|
func TestExemplars(t *testing.T) {
|
||||||
attrsOpt := otelmetric.WithAttributes(
|
attrsOpt := otelmetric.WithAttributes(
|
||||||
attribute.Key("A").String("B"),
|
attribute.Key("A.1").String("B"),
|
||||||
attribute.Key("C").String("D"),
|
attribute.Key("C.2").String("D"),
|
||||||
attribute.Key("E").Bool(true),
|
attribute.Key("E.3").Bool(true),
|
||||||
attribute.Key("F").Int(42),
|
attribute.Key("F.4").Int(42),
|
||||||
)
|
)
|
||||||
|
expectedNonEscapedLabels := map[string]string{
|
||||||
|
traceIDExemplarKey: "01000000000000000000000000000000",
|
||||||
|
spanIDExemplarKey: "0100000000000000",
|
||||||
|
"A.1": "B",
|
||||||
|
"C.2": "D",
|
||||||
|
"E.3": "true",
|
||||||
|
"F.4": "42",
|
||||||
|
}
|
||||||
|
expectedEscapedLabels := map[string]string{
|
||||||
|
traceIDExemplarKey: "01000000000000000000000000000000",
|
||||||
|
spanIDExemplarKey: "0100000000000000",
|
||||||
|
"A_1": "B",
|
||||||
|
"C_2": "D",
|
||||||
|
"E_3": "true",
|
||||||
|
"F_4": "42",
|
||||||
|
}
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
recordMetrics func(ctx context.Context, meter otelmetric.Meter)
|
recordMetrics func(ctx context.Context, meter otelmetric.Meter)
|
||||||
expectedExemplarValue float64
|
expectedExemplarValue float64
|
||||||
|
expectedLabels map[string]string
|
||||||
|
escapingScheme model.EscapingScheme
|
||||||
|
validationScheme model.ValidationScheme
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "counter",
|
name: "escaped counter",
|
||||||
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||||
counter, err := meter.Float64Counter("foo")
|
counter, err := meter.Float64Counter("foo")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
counter.Add(ctx, 9, attrsOpt)
|
counter.Add(ctx, 9, attrsOpt)
|
||||||
},
|
},
|
||||||
expectedExemplarValue: 9,
|
expectedExemplarValue: 9,
|
||||||
|
expectedLabels: expectedEscapedLabels,
|
||||||
|
escapingScheme: model.UnderscoreEscaping,
|
||||||
|
validationScheme: model.LegacyValidation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "histogram",
|
name: "escaped histogram",
|
||||||
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||||
hist, err := meter.Int64Histogram("foo")
|
hist, err := meter.Int64Histogram("foo")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
hist.Record(ctx, 9, attrsOpt)
|
hist.Record(ctx, 9, attrsOpt)
|
||||||
},
|
},
|
||||||
expectedExemplarValue: 9,
|
expectedExemplarValue: 9,
|
||||||
|
expectedLabels: expectedEscapedLabels,
|
||||||
|
escapingScheme: model.UnderscoreEscaping,
|
||||||
|
validationScheme: model.LegacyValidation,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-escaped counter",
|
||||||
|
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||||
|
counter, err := meter.Float64Counter("foo")
|
||||||
|
require.NoError(t, err)
|
||||||
|
counter.Add(ctx, 9, attrsOpt)
|
||||||
|
},
|
||||||
|
expectedExemplarValue: 9,
|
||||||
|
expectedLabels: expectedNonEscapedLabels,
|
||||||
|
escapingScheme: model.NoEscaping,
|
||||||
|
validationScheme: model.UTF8Validation,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-escaped histogram",
|
||||||
|
recordMetrics: func(ctx context.Context, meter otelmetric.Meter) {
|
||||||
|
hist, err := meter.Int64Histogram("foo")
|
||||||
|
require.NoError(t, err)
|
||||||
|
hist.Record(ctx, 9, attrsOpt)
|
||||||
|
},
|
||||||
|
expectedExemplarValue: 9,
|
||||||
|
expectedLabels: expectedNonEscapedLabels,
|
||||||
|
escapingScheme: model.NoEscaping,
|
||||||
|
validationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Setenv("OTEL_GO_X_EXEMPLAR", "true")
|
originalEscapingScheme := model.NameEscapingScheme
|
||||||
|
originalValidationScheme := model.NameValidationScheme
|
||||||
|
model.NameEscapingScheme = tc.escapingScheme
|
||||||
|
model.NameValidationScheme = tc.validationScheme
|
||||||
|
// Restore original value after the test is complete
|
||||||
|
defer func() {
|
||||||
|
model.NameEscapingScheme = originalEscapingScheme
|
||||||
|
model.NameValidationScheme = originalValidationScheme
|
||||||
|
}()
|
||||||
// initialize registry exporter
|
// initialize registry exporter
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
@ -1044,17 +1101,9 @@ func TestExemplars(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.NotNil(t, exemplar)
|
require.NotNil(t, exemplar)
|
||||||
require.Equal(t, tc.expectedExemplarValue, exemplar.GetValue())
|
require.Equal(t, tc.expectedExemplarValue, exemplar.GetValue())
|
||||||
expectedLabels := map[string]string{
|
require.Equal(t, len(tc.expectedLabels), len(exemplar.GetLabel()))
|
||||||
traceIDExemplarKey: "01000000000000000000000000000000",
|
|
||||||
spanIDExemplarKey: "0100000000000000",
|
|
||||||
"A": "B",
|
|
||||||
"C": "D",
|
|
||||||
"E": "true",
|
|
||||||
"F": "42",
|
|
||||||
}
|
|
||||||
require.Equal(t, len(expectedLabels), len(exemplar.GetLabel()))
|
|
||||||
for _, label := range exemplar.GetLabel() {
|
for _, label := range exemplar.GetLabel() {
|
||||||
val, ok := expectedLabels[label.GetName()]
|
val, ok := tc.expectedLabels[label.GetName()]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Equal(t, label.GetValue(), val)
|
require.Equal(t, label.GetValue(), val)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user