Skip to content

Commit 137dfcb

Browse files
iypetrovyurishkuro
andauthored
[Fix] Fix prometheus label value is not valid utf 8 cause api timeout (#7128)
## Which problem is this PR solving? - Example: Resolves #2944 ## Description of the changes - Use `hist.GetMetricWithLabelValues` instead of `hist.WithLabelValues` to avoid the panic 'Label value is not valid UTF-8' and receive instead of that empty counter/gauge/timer/histogram. ## How was this change tested? - Four new tests were added to ensure the system remains stable when it receives an invalid label. ## Checklist - [x] I have read https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING_GUIDELINES.md - [x] I have signed all commits - [x] I have added unit tests for the new functionality - [x] I have run lint and test steps successfully - for `jaeger`: `make lint test` - for `jaeger-ui`: `npm run lint` and `npm run test` --------- Signed-off-by: Ilia Petrov <[email protected]> Signed-off-by: Yuri Shkuro <[email protected]> Co-authored-by: Yuri Shkuro <[email protected]>
1 parent 2f2b6fb commit 137dfcb

File tree

4 files changed

+152
-4
lines changed

4 files changed

+152
-4
lines changed

internal/jptrace/utf8.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2025 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package jptrace
5+
6+
import (
7+
"golang.org/x/text/runes"
8+
"golang.org/x/text/transform"
9+
)
10+
11+
func SanitizeUTF8(input string) (string, error) {
12+
t := transform.Chain(runes.ReplaceIllFormed(), transform.Nop)
13+
sanitized, _, err := transform.String(t, input)
14+
return sanitized, err
15+
}

internal/jptrace/utf8_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) 2025 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package jptrace
5+
6+
import (
7+
"testing"
8+
"unicode/utf8"
9+
)
10+
11+
func TestSanitizeUTF8(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
input string
15+
want string
16+
}{
17+
{
18+
name: "Valid ASCII",
19+
input: "Hello, world!",
20+
want: "Hello, world!",
21+
},
22+
{
23+
name: "Valid UTF-8 multibyte",
24+
input: "こんにちは世界",
25+
want: "こんにちは世界",
26+
},
27+
{
28+
name: "Valid Emoji",
29+
input: "😃🚀🔥",
30+
want: "😃🚀🔥",
31+
},
32+
{
33+
name: "String with replacement rune",
34+
input: string([]byte{0xff, 0xfe, 0xfd}),
35+
want: "���",
36+
},
37+
{
38+
name: "Mixed valid and invalid",
39+
input: "Good\xffMorning",
40+
want: "Good�Morning",
41+
},
42+
{
43+
name: "Empty string",
44+
input: "",
45+
want: "",
46+
},
47+
}
48+
49+
for _, tt := range tests {
50+
t.Run(tt.name, func(t *testing.T) {
51+
sanitized, err := SanitizeUTF8(tt.input)
52+
if err != nil {
53+
t.Errorf("SanitizeUTF8 failed: %v", err)
54+
}
55+
if !utf8.ValidString(sanitized) {
56+
t.Errorf("SanitizeUTF8 returned invalid UTF-8 string: %s, want %s", sanitized, tt.want)
57+
}
58+
if sanitized != tt.want {
59+
t.Errorf("SanitizeUTF8 gave not correct output: %s, want %s", sanitized, tt.want)
60+
}
61+
})
62+
}
63+
}

internal/metrics/prometheus/factory.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,14 @@ func (f *Factory) Counter(options metrics.Options) metrics.Counter {
129129
Help: help,
130130
}
131131
cv := f.cache.getOrMakeCounterVec(opts, labelNames)
132+
labelValues := f.tagsAsLabelValues(labelNames, tags)
133+
metric, err := cv.GetMetricWithLabelValues(labelValues...)
134+
if err != nil {
135+
return metrics.NullCounter
136+
}
137+
132138
return &counter{
133-
counter: cv.WithLabelValues(f.tagsAsLabelValues(labelNames, tags)...),
139+
counter: metric,
134140
}
135141
}
136142

@@ -148,8 +154,13 @@ func (f *Factory) Gauge(options metrics.Options) metrics.Gauge {
148154
Help: help,
149155
}
150156
gv := f.cache.getOrMakeGaugeVec(opts, labelNames)
157+
labelValues := f.tagsAsLabelValues(labelNames, tags)
158+
metric, err := gv.GetMetricWithLabelValues(labelValues...)
159+
if err != nil {
160+
return metrics.NullGauge
161+
}
151162
return &gauge{
152-
gauge: gv.WithLabelValues(f.tagsAsLabelValues(labelNames, tags)...),
163+
gauge: metric,
153164
}
154165
}
155166

@@ -169,8 +180,13 @@ func (f *Factory) Timer(options metrics.TimerOptions) metrics.Timer {
169180
Buckets: buckets,
170181
}
171182
hv := f.cache.getOrMakeHistogramVec(opts, labelNames)
183+
labelValues := f.tagsAsLabelValues(labelNames, tags)
184+
metric, err := hv.GetMetricWithLabelValues(labelValues...)
185+
if err != nil {
186+
return metrics.NullTimer
187+
}
172188
return &timer{
173-
histogram: hv.WithLabelValues(f.tagsAsLabelValues(labelNames, tags)...),
189+
histogram: metric,
174190
}
175191
}
176192

@@ -198,8 +214,14 @@ func (f *Factory) Histogram(options metrics.HistogramOptions) metrics.Histogram
198214
Buckets: buckets,
199215
}
200216
hv := f.cache.getOrMakeHistogramVec(opts, labelNames)
217+
labelValues := f.tagsAsLabelValues(labelNames, tags)
218+
metric, err := hv.GetMetricWithLabelValues(labelValues...)
219+
if err != nil {
220+
return metrics.NullHistogram
221+
}
222+
201223
return &histogram{
202-
histogram: hv.WithLabelValues(f.tagsAsLabelValues(labelNames, tags)...),
224+
histogram: metric,
203225
}
204226
}
205227

internal/metrics/prometheus/factory_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ func TestCounterDefaultHelp(t *testing.T) {
9696
assert.Equal(t, "rodriguez", snapshot[0].GetHelp())
9797
}
9898

99+
func TestCounterNotValidLabel(t *testing.T) {
100+
registry := prometheus.NewPedanticRegistry()
101+
f1 := promMetrics.New(
102+
promMetrics.WithRegisterer(registry),
103+
)
104+
c1 := f1.Counter(metrics.Options{
105+
Name: "ilia",
106+
Tags: map[string]string{"x": "label__3d\x85_this_will_panic-repro"},
107+
})
108+
assert.Equal(t, c1, metrics.NullCounter, "Expected NullCounter, got %v", c1)
109+
}
110+
99111
func TestGauge(t *testing.T) {
100112
registry := prometheus.NewPedanticRegistry()
101113
f1 := promMetrics.New(promMetrics.WithRegisterer(registry))
@@ -153,6 +165,18 @@ func TestGaugeDefaultHelp(t *testing.T) {
153165
assert.Equal(t, "rodriguez", snapshot[0].GetHelp())
154166
}
155167

168+
func TestGaugeNotValidLabel(t *testing.T) {
169+
registry := prometheus.NewPedanticRegistry()
170+
f1 := promMetrics.New(
171+
promMetrics.WithRegisterer(registry),
172+
)
173+
c1 := f1.Gauge(metrics.Options{
174+
Name: "ilia",
175+
Tags: map[string]string{"x": "label__3d\x85_this_will_panic-repro"},
176+
})
177+
assert.Equal(t, c1, metrics.NullGauge, "Expected NullGauge, got %v", c1)
178+
}
179+
156180
func TestTimer(t *testing.T) {
157181
registry := prometheus.NewPedanticRegistry()
158182
f1 := promMetrics.New(promMetrics.WithRegisterer(registry))
@@ -232,6 +256,18 @@ func TestTimerDefaultHelp(t *testing.T) {
232256
assert.Equal(t, "rodriguez", snapshot[0].GetHelp())
233257
}
234258

259+
func TestTimerNotValidLabel(t *testing.T) {
260+
registry := prometheus.NewPedanticRegistry()
261+
f1 := promMetrics.New(
262+
promMetrics.WithRegisterer(registry),
263+
)
264+
c1 := f1.Timer(metrics.TimerOptions{
265+
Name: "ilia",
266+
Tags: map[string]string{"x": "label__3d\x85_this_will_panic-repro"},
267+
})
268+
assert.Equal(t, c1, metrics.NullTimer, "Expected NullTimer, got %v", c1)
269+
}
270+
235271
func TestTimerCustomBuckets(t *testing.T) {
236272
registry := prometheus.NewPedanticRegistry()
237273
f1 := promMetrics.New(promMetrics.WithRegisterer(registry), promMetrics.WithBuckets([]float64{1.5}))
@@ -374,6 +410,18 @@ func TestHistogramCustomBuckets(t *testing.T) {
374410
assert.Len(t, m1.GetHistogram().GetBucket(), 1)
375411
}
376412

413+
func TestHistogramNotValidLabel(t *testing.T) {
414+
registry := prometheus.NewPedanticRegistry()
415+
f1 := promMetrics.New(
416+
promMetrics.WithRegisterer(registry),
417+
)
418+
c1 := f1.Histogram(metrics.HistogramOptions{
419+
Name: "ilia",
420+
Tags: map[string]string{"x": "label__3d\x85_this_will_panic-repro"},
421+
})
422+
assert.Equal(t, c1, metrics.NullHistogram, "Expected NullHistogram, got %v", c1)
423+
}
424+
377425
func TestHistogramDefaultBuckets(t *testing.T) {
378426
registry := prometheus.NewPedanticRegistry()
379427
f1 := promMetrics.New(promMetrics.WithRegisterer(registry), promMetrics.WithBuckets([]float64{1.5}))

0 commit comments

Comments
 (0)