diff --git a/expfmt/openmetrics_2_0_create.go b/expfmt/openmetrics_2_0_create.go index a218226b..aefdaf4f 100644 --- a/expfmt/openmetrics_2_0_create.go +++ b/expfmt/openmetrics_2_0_create.go @@ -263,7 +263,7 @@ func writeOpenMetrics20Sample(w enhancedWriter, name string, metric *dto.Metric, } } - if exemplar != nil && exemplar.Timestamp != nil { + if exemplar != nil { n, err = writeExemplar20(w, exemplar) written += n if err != nil { @@ -280,13 +280,14 @@ func writeOpenMetrics20Sample(w enhancedWriter, name string, metric *dto.Metric, } // writeExemplar20 writes the provided exemplar in OpenMetrics 2.0 format to w. -// In OpenMetrics 2.0, exemplars without a timestamp are dropped. +// In OpenMetrics 2.0, invalid exemplars or exemplars without a timestamp are dropped. func writeExemplar20(w enhancedWriter, e *dto.Exemplar) (int, error) { - if e == nil || e.Timestamp == nil { + if e == nil { return 0, nil } + // In OpenMetrics 2.0, invalid exemplars are dropped rather than failing the entire exposition. if err := validateExemplar20(e); err != nil { - return 0, err + return 0, nil } written := 0 n, err := w.WriteString(" # ") @@ -318,10 +319,6 @@ func writeExemplar20(w enhancedWriter, e *dto.Exemplar) (int, error) { if err != nil { return written, err } - err = e.Timestamp.CheckValid() - if err != nil { - return written, err - } ts := e.Timestamp n, err = writeProtoTimestamp(w, ts) written += n @@ -387,6 +384,9 @@ func containsRawNewline(s string) bool { } func validateExemplar20(e *dto.Exemplar) error { + if e.Timestamp == nil { + return errors.New("exemplar timestamp is required") + } if err := e.Timestamp.CheckValid(); err != nil { return err } diff --git a/expfmt/openmetrics_2_0_create_test.go b/expfmt/openmetrics_2_0_create_test.go index 747bff4c..e3e1766b 100644 --- a/expfmt/openmetrics_2_0_create_test.go +++ b/expfmt/openmetrics_2_0_create_test.go @@ -205,6 +205,102 @@ http_requests_total 1027.0 # {} 1.0 1234567890.5 }, out: `# TYPE http_requests_total counter http_requests_total 1027.0 +`, + }, + { + name: "CounterWithInvalidExemplarTimestamp", + in: &dto.MetricFamily{ + Name: proto.String("http_requests_total"), + Type: dto.MetricType_COUNTER.Enum(), + Metric: []*dto.Metric{ + { + Counter: &dto.Counter{ + Value: proto.Float64(1027), + Exemplar: &dto.Exemplar{ + Label: []*dto.LabelPair{ + {Name: proto.String("trace_id"), Value: proto.String("1234")}, + }, + Value: proto.Float64(1), + Timestamp: ×tamppb.Timestamp{ + Nanos: -1, + }, + }, + }, + }, + }, + }, + out: `# TYPE http_requests_total counter +http_requests_total 1027.0 +`, + }, + { + name: "CounterWithInvalidExemplarLabel", + in: &dto.MetricFamily{ + Name: proto.String("http_requests_total"), + Type: dto.MetricType_COUNTER.Enum(), + Metric: []*dto.Metric{ + { + Counter: &dto.Counter{ + Value: proto.Float64(1027), + Exemplar: &dto.Exemplar{ + Label: []*dto.LabelPair{ + {Name: proto.String(""), Value: proto.String("1234")}, + }, + Value: proto.Float64(1), + Timestamp: ×tamppb.Timestamp{Seconds: 1234567890}, + }, + }, + }, + }, + }, + out: `# TYPE http_requests_total counter +http_requests_total 1027.0 +`, + }, + { + name: "CounterWithNewlineInExemplarLabelName", + in: &dto.MetricFamily{ + Name: proto.String("http_requests_total"), + Type: dto.MetricType_COUNTER.Enum(), + Metric: []*dto.Metric{ + { + Counter: &dto.Counter{ + Value: proto.Float64(1027), + Exemplar: &dto.Exemplar{ + Label: []*dto.LabelPair{ + {Name: proto.String("trace\nid"), Value: proto.String("1234")}, + }, + Value: proto.Float64(1), + Timestamp: ×tamppb.Timestamp{Seconds: 1234567890}, + }, + }, + }, + }, + }, + out: `# TYPE http_requests_total counter +http_requests_total 1027.0 +`, + }, + { + name: "CounterWithNilExemplarLabelPair", + in: &dto.MetricFamily{ + Name: proto.String("http_requests_total"), + Type: dto.MetricType_COUNTER.Enum(), + Metric: []*dto.Metric{ + { + Counter: &dto.Counter{ + Value: proto.Float64(1027), + Exemplar: &dto.Exemplar{ + Label: []*dto.LabelPair{nil}, + Value: proto.Float64(1), + Timestamp: ×tamppb.Timestamp{Seconds: 1234567890}, + }, + }, + }, + }, + }, + out: `# TYPE http_requests_total counter +http_requests_total 1027.0 `, }, { @@ -556,30 +652,6 @@ func TestCreateOpenMetrics20_Errors(t *testing.T) { }, expectedErr: "invalid created timestamp in metric test_counter_total", }, - { - name: "ExemplarInvalidTimestamp", - in: &dto.MetricFamily{ - Name: proto.String("test_counter_total"), - Type: dto.MetricType_COUNTER.Enum(), - Metric: []*dto.Metric{ - { - Counter: &dto.Counter{ - Value: proto.Float64(1.0), - Exemplar: &dto.Exemplar{ - Label: []*dto.LabelPair{ - {Name: proto.String("trace_id"), Value: proto.String("1234")}, - }, - Value: proto.Float64(1.0), - Timestamp: ×tamppb.Timestamp{ - Nanos: -1, - }, - }, - }, - }, - }, - }, - expectedErr: "has out-of-range nanos", - }, } for _, tc := range tests {