diff --git a/exporter/prometheusexporter/collector.go b/exporter/prometheusexporter/collector.go index 312df1ff524..cbf6152b9d5 100644 --- a/exporter/prometheusexporter/collector.go +++ b/exporter/prometheusexporter/collector.go @@ -87,7 +87,7 @@ func (c *collector) getMetricMetadata(metric pdata.Metric, labels pdata.StringMa values := make([]string, 0, labels.Len()) labels.ForEach(func(k string, v string) { - keys = append(keys, k) + keys = append(keys, sanitize(k)) values = append(values, v) }) diff --git a/exporter/prometheusexporter/collector_test.go b/exporter/prometheusexporter/collector_test.go index 0ac798620e0..e47954cb2b3 100644 --- a/exporter/prometheusexporter/collector_test.go +++ b/exporter/prometheusexporter/collector_test.go @@ -22,6 +22,7 @@ import ( io_prometheus_client "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "go.opentelemetry.io/collector/consumer/pdata" ) @@ -96,6 +97,72 @@ func TestConvertInvalidMetric(t *testing.T) { } } +// errorCheckCore keeps track of logged errors +type errorCheckCore struct { + errorMessages []string +} + +func (*errorCheckCore) Enabled(zapcore.Level) bool { return true } +func (c *errorCheckCore) With([]zap.Field) zapcore.Core { return c } +func (c *errorCheckCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if c.Enabled(ent.Level) { + return ce.AddCore(ent, c) + } + return ce +} +func (c *errorCheckCore) Write(ent zapcore.Entry, field []zapcore.Field) error { + if ent.Level == zapcore.ErrorLevel { + c.errorMessages = append(c.errorMessages, ent.Message) + } + return nil +} +func (*errorCheckCore) Sync() error { return nil } + +func TestCollectMetricsLabelSanitize(t *testing.T) { + dp := pdata.NewIntDataPoint() + dp.SetValue(42) + dp.LabelsMap().Insert("label.1", "1") + dp.LabelsMap().Insert("label/2", "2") + dp.SetTimestamp(pdata.TimestampFromTime(time.Now())) + + metric := pdata.NewMetric() + metric.SetName("test_metric") + metric.SetDataType(pdata.MetricDataTypeIntGauge) + metric.IntGauge().DataPoints().Append(dp) + metric.SetDescription("test description") + + loggerCore := errorCheckCore{} + c := collector{ + namespace: "test_space", + accumulator: &mockAccumulator{ + []pdata.Metric{metric}, + }, + sendTimestamps: false, + logger: zap.New(&loggerCore), + } + + ch := make(chan prometheus.Metric, 1) + go func() { + c.Collect(ch) + close(ch) + }() + + for m := range ch { + require.Contains(t, m.Desc().String(), "fqName: \"test_space_test_metric\"") + require.Contains(t, m.Desc().String(), "variableLabels: [label_1 label_2]") + + pbMetric := io_prometheus_client.Metric{} + m.Write(&pbMetric) + + labelsKeys := map[string]string{"label_1": "1", "label_2": "2"} + for _, l := range pbMetric.Label { + require.Equal(t, labelsKeys[*l.Name], *l.Value) + } + } + + require.Empty(t, loggerCore.errorMessages, "labels were not sanitized properly") +} + func TestCollectMetrics(t *testing.T) { tests := []struct { name string