1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-11-24 08:22:25 +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:
David Ashpole 2024-11-22 13:39:20 -05:00 committed by GitHub
parent 99c3c661e0
commit 2edaccb023
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 69 additions and 18 deletions

View File

@ -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/otlptrace/otlptracehttp`. (#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 -->
<!-- Don't change this section unless doing release -->

View File

@ -547,7 +547,8 @@ func addExemplars[N int64 | float64](m prometheus.Metric, exemplars []metricdata
func attributesToLabels(attrs []attribute.KeyValue) prometheus.Labels {
labels := make(map[string]string)
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
}

View File

@ -949,37 +949,94 @@ func TestShutdownExporter(t *testing.T) {
func TestExemplars(t *testing.T) {
attrsOpt := otelmetric.WithAttributes(
attribute.Key("A").String("B"),
attribute.Key("C").String("D"),
attribute.Key("E").Bool(true),
attribute.Key("F").Int(42),
attribute.Key("A.1").String("B"),
attribute.Key("C.2").String("D"),
attribute.Key("E.3").Bool(true),
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 {
name string
recordMetrics func(ctx context.Context, meter otelmetric.Meter)
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) {
counter, err := meter.Float64Counter("foo")
require.NoError(t, err)
counter.Add(ctx, 9, attrsOpt)
},
expectedExemplarValue: 9,
expectedLabels: expectedEscapedLabels,
escapingScheme: model.UnderscoreEscaping,
validationScheme: model.LegacyValidation,
},
{
name: "histogram",
name: "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: 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.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
ctx := context.Background()
registry := prometheus.NewRegistry()
@ -1044,17 +1101,9 @@ func TestExemplars(t *testing.T) {
}
require.NotNil(t, exemplar)
require.Equal(t, tc.expectedExemplarValue, exemplar.GetValue())
expectedLabels := map[string]string{
traceIDExemplarKey: "01000000000000000000000000000000",
spanIDExemplarKey: "0100000000000000",
"A": "B",
"C": "D",
"E": "true",
"F": "42",
}
require.Equal(t, len(expectedLabels), len(exemplar.GetLabel()))
require.Equal(t, len(tc.expectedLabels), len(exemplar.GetLabel()))
for _, label := range exemplar.GetLabel() {
val, ok := expectedLabels[label.GetName()]
val, ok := tc.expectedLabels[label.GetName()]
require.True(t, ok)
require.Equal(t, label.GetValue(), val)
}