Skip to content

Commit

Permalink
sanitize exemplar keys
Browse files Browse the repository at this point in the history
  • Loading branch information
dashpole committed Nov 21, 2024
1 parent 99c3c66 commit a978599
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`. (#TODO)

<!-- Released section -->
<!-- Don't change this section unless doing release -->
Expand Down
3 changes: 2 additions & 1 deletion exporters/prometheus/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
82 changes: 65 additions & 17 deletions exporters/prometheus/exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -949,37 +949,93 @@ 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),
)
expectedUnsanitizedLabels := map[string]string{
traceIDExemplarKey: "01000000000000000000000000000000",
spanIDExemplarKey: "0100000000000000",
"A.1": "B",
"C.2": "D",
"E.3": "true",
"F.4": "42",
}
expectedSanitizedLabels := 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: "sanitized 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: expectedSanitizedLabels,
escapingScheme: model.UnderscoreEscaping,
validationScheme: model.LegacyValidation,
},
{
name: "histogram",
name: "sanitized 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: expectedSanitizedLabels,
escapingScheme: model.UnderscoreEscaping,
validationScheme: model.LegacyValidation,
},
{
name: "unsanitized 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: expectedUnsanitizedLabels,
escapingScheme: model.NoEscaping,
validationScheme: model.UTF8Validation,
},
{
name: "unsanitized 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: expectedUnsanitizedLabels,
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
defer func() {
model.NameEscapingScheme = originalEscapingScheme
model.NameValidationScheme = originalValidationScheme
}()
// initialize registry exporter
ctx := context.Background()
registry := prometheus.NewRegistry()
Expand Down Expand Up @@ -1044,17 +1100,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)
}
Expand Down

0 comments on commit a978599

Please sign in to comment.