Skip to content

Commit 67b7d90

Browse files
authored
fix: drop invalid OpenMetrics 2.0 exemplars instead of failing exposition (#970)
* fix: drop invalid OpenMetrics 2.0 exemplars instead of failing exposition Per the OpenMetrics 2.0 specification failure modes, failures specific to exemplars should not cause the entire exposition to fail. Invalid exemplars (e.g. invalid timestamps, malformed labels) are now dropped so that the rest of the metric exposition succeeds. Signed-off-by: David Ashpole <dashpole@google.com> * expfmt: add comment explaining why invalid OpenMetrics 2.0 exemplars are dropped Signed-off-by: David Ashpole <dashpole@google.com> --------- Signed-off-by: David Ashpole <dashpole@google.com>
1 parent 715ac36 commit 67b7d90

2 files changed

Lines changed: 104 additions & 32 deletions

File tree

expfmt/openmetrics_2_0_create.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ func writeOpenMetrics20Sample(w enhancedWriter, name string, metric *dto.Metric,
263263
}
264264
}
265265

266-
if exemplar != nil && exemplar.Timestamp != nil {
266+
if exemplar != nil {
267267
n, err = writeExemplar20(w, exemplar)
268268
written += n
269269
if err != nil {
@@ -280,13 +280,14 @@ func writeOpenMetrics20Sample(w enhancedWriter, name string, metric *dto.Metric,
280280
}
281281

282282
// writeExemplar20 writes the provided exemplar in OpenMetrics 2.0 format to w.
283-
// In OpenMetrics 2.0, exemplars without a timestamp are dropped.
283+
// In OpenMetrics 2.0, invalid exemplars or exemplars without a timestamp are dropped.
284284
func writeExemplar20(w enhancedWriter, e *dto.Exemplar) (int, error) {
285-
if e == nil || e.Timestamp == nil {
285+
if e == nil {
286286
return 0, nil
287287
}
288+
// In OpenMetrics 2.0, invalid exemplars are dropped rather than failing the entire exposition.
288289
if err := validateExemplar20(e); err != nil {
289-
return 0, err
290+
return 0, nil
290291
}
291292
written := 0
292293
n, err := w.WriteString(" # ")
@@ -318,10 +319,6 @@ func writeExemplar20(w enhancedWriter, e *dto.Exemplar) (int, error) {
318319
if err != nil {
319320
return written, err
320321
}
321-
err = e.Timestamp.CheckValid()
322-
if err != nil {
323-
return written, err
324-
}
325322
ts := e.Timestamp
326323
n, err = writeProtoTimestamp(w, ts)
327324
written += n
@@ -387,6 +384,9 @@ func containsRawNewline(s string) bool {
387384
}
388385

389386
func validateExemplar20(e *dto.Exemplar) error {
387+
if e.Timestamp == nil {
388+
return errors.New("exemplar timestamp is required")
389+
}
390390
if err := e.Timestamp.CheckValid(); err != nil {
391391
return err
392392
}

expfmt/openmetrics_2_0_create_test.go

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,102 @@ http_requests_total 1027.0 # {} 1.0 1234567890.5
205205
},
206206
out: `# TYPE http_requests_total counter
207207
http_requests_total 1027.0
208+
`,
209+
},
210+
{
211+
name: "CounterWithInvalidExemplarTimestamp",
212+
in: &dto.MetricFamily{
213+
Name: proto.String("http_requests_total"),
214+
Type: dto.MetricType_COUNTER.Enum(),
215+
Metric: []*dto.Metric{
216+
{
217+
Counter: &dto.Counter{
218+
Value: proto.Float64(1027),
219+
Exemplar: &dto.Exemplar{
220+
Label: []*dto.LabelPair{
221+
{Name: proto.String("trace_id"), Value: proto.String("1234")},
222+
},
223+
Value: proto.Float64(1),
224+
Timestamp: &timestamppb.Timestamp{
225+
Nanos: -1,
226+
},
227+
},
228+
},
229+
},
230+
},
231+
},
232+
out: `# TYPE http_requests_total counter
233+
http_requests_total 1027.0
234+
`,
235+
},
236+
{
237+
name: "CounterWithInvalidExemplarLabel",
238+
in: &dto.MetricFamily{
239+
Name: proto.String("http_requests_total"),
240+
Type: dto.MetricType_COUNTER.Enum(),
241+
Metric: []*dto.Metric{
242+
{
243+
Counter: &dto.Counter{
244+
Value: proto.Float64(1027),
245+
Exemplar: &dto.Exemplar{
246+
Label: []*dto.LabelPair{
247+
{Name: proto.String(""), Value: proto.String("1234")},
248+
},
249+
Value: proto.Float64(1),
250+
Timestamp: &timestamppb.Timestamp{Seconds: 1234567890},
251+
},
252+
},
253+
},
254+
},
255+
},
256+
out: `# TYPE http_requests_total counter
257+
http_requests_total 1027.0
258+
`,
259+
},
260+
{
261+
name: "CounterWithNewlineInExemplarLabelName",
262+
in: &dto.MetricFamily{
263+
Name: proto.String("http_requests_total"),
264+
Type: dto.MetricType_COUNTER.Enum(),
265+
Metric: []*dto.Metric{
266+
{
267+
Counter: &dto.Counter{
268+
Value: proto.Float64(1027),
269+
Exemplar: &dto.Exemplar{
270+
Label: []*dto.LabelPair{
271+
{Name: proto.String("trace\nid"), Value: proto.String("1234")},
272+
},
273+
Value: proto.Float64(1),
274+
Timestamp: &timestamppb.Timestamp{Seconds: 1234567890},
275+
},
276+
},
277+
},
278+
},
279+
},
280+
out: `# TYPE http_requests_total counter
281+
http_requests_total 1027.0
282+
`,
283+
},
284+
{
285+
name: "CounterWithNilExemplarLabelPair",
286+
in: &dto.MetricFamily{
287+
Name: proto.String("http_requests_total"),
288+
Type: dto.MetricType_COUNTER.Enum(),
289+
Metric: []*dto.Metric{
290+
{
291+
Counter: &dto.Counter{
292+
Value: proto.Float64(1027),
293+
Exemplar: &dto.Exemplar{
294+
Label: []*dto.LabelPair{nil},
295+
Value: proto.Float64(1),
296+
Timestamp: &timestamppb.Timestamp{Seconds: 1234567890},
297+
},
298+
},
299+
},
300+
},
301+
},
302+
out: `# TYPE http_requests_total counter
303+
http_requests_total 1027.0
208304
`,
209305
},
210306
{
@@ -556,30 +652,6 @@ func TestCreateOpenMetrics20_Errors(t *testing.T) {
556652
},
557653
expectedErr: "invalid created timestamp in metric test_counter_total",
558654
},
559-
{
560-
name: "ExemplarInvalidTimestamp",
561-
in: &dto.MetricFamily{
562-
Name: proto.String("test_counter_total"),
563-
Type: dto.MetricType_COUNTER.Enum(),
564-
Metric: []*dto.Metric{
565-
{
566-
Counter: &dto.Counter{
567-
Value: proto.Float64(1.0),
568-
Exemplar: &dto.Exemplar{
569-
Label: []*dto.LabelPair{
570-
{Name: proto.String("trace_id"), Value: proto.String("1234")},
571-
},
572-
Value: proto.Float64(1.0),
573-
Timestamp: &timestamppb.Timestamp{
574-
Nanos: -1,
575-
},
576-
},
577-
},
578-
},
579-
},
580-
},
581-
expectedErr: "has out-of-range nanos",
582-
},
583655
}
584656

585657
for _, tc := range tests {

0 commit comments

Comments
 (0)