Skip to content

Commit 4beead7

Browse files
authored
[jaeger-v2] Implement empty service name sanitizer for OTLP (jaegertracing#6077)
## Which problem is this PR solving? - Towards jaegertracing#5545 ## Description of the changes - Created a new `Sanitizer` type for sanitizing traces in OTLP format - Implemented the empty service name sanitizer; I'll open a separate PR for the UTF-8 sanitizer ## How was this change tested? - Unit tests ## 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`: `yarn lint` and `yarn test` --------- Signed-off-by: Mahad Zaryab <[email protected]>
1 parent 563f3b0 commit 4beead7

File tree

7 files changed

+234
-1
lines changed

7 files changed

+234
-1
lines changed

cmd/jaeger/internal/exporters/storageexporter/exporter.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,24 @@ import (
1212
"go.uber.org/zap"
1313

1414
"github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerstorage"
15+
"github.com/jaegertracing/jaeger/cmd/jaeger/internal/sanitizer"
1516
"github.com/jaegertracing/jaeger/storage_v2/spanstore"
1617
)
1718

1819
type storageExporter struct {
1920
config *Config
2021
logger *zap.Logger
2122
traceWriter spanstore.Writer
23+
sanitizer sanitizer.Func
2224
}
2325

2426
func newExporter(config *Config, otel component.TelemetrySettings) *storageExporter {
2527
return &storageExporter{
2628
config: config,
2729
logger: otel.Logger,
30+
sanitizer: sanitizer.NewChainedSanitizer(
31+
sanitizer.NewStandardSanitizers()...,
32+
),
2833
}
2934
}
3035

@@ -47,5 +52,5 @@ func (*storageExporter) close(_ context.Context) error {
4752
}
4853

4954
func (exp *storageExporter) pushTraces(ctx context.Context, td ptrace.Traces) error {
50-
return exp.traceWriter.WriteTraces(ctx, td)
55+
return exp.traceWriter.WriteTraces(ctx, exp.sanitizer(td))
5156
}

cmd/jaeger/internal/exporters/storageexporter/exporter_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ func TestExporter(t *testing.T) {
157157
requiredTrace, err := spanReader.GetTrace(ctx, requiredTraceID)
158158
require.NoError(t, err)
159159
assert.Equal(t, spanID.String(), requiredTrace.Spans[0].SpanID.String())
160+
161+
// check that the service name attribute was added by the sanitizer
162+
require.Equal(t, "missing-service-name", requiredTrace.Spans[0].Process.ServiceName)
160163
}
161164

162165
func makeStorageExtension(t *testing.T, memstoreName string) component.Host {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) 2024 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package sanitizer
5+
6+
import (
7+
"go.opentelemetry.io/collector/pdata/pcommon"
8+
"go.opentelemetry.io/collector/pdata/ptrace"
9+
10+
"github.com/jaegertracing/jaeger/pkg/otelsemconv"
11+
)
12+
13+
const (
14+
emptyServiceName = "empty-service-name"
15+
serviceNameWrongType = "service-name-wrong-type"
16+
missingServiceName = "missing-service-name"
17+
)
18+
19+
// NewEmptyServiceNameSanitizer returns a sanitizer function that replaces
20+
// empty and missing service names with placeholder strings.
21+
func NewEmptyServiceNameSanitizer() Func {
22+
return sanitizeEmptyServiceName
23+
}
24+
25+
func sanitizeEmptyServiceName(traces ptrace.Traces) ptrace.Traces {
26+
resourceSpans := traces.ResourceSpans()
27+
for i := 0; i < resourceSpans.Len(); i++ {
28+
resourceSpan := resourceSpans.At(i)
29+
attributes := resourceSpan.Resource().Attributes()
30+
serviceName, ok := attributes.Get(string(otelsemconv.ServiceNameKey))
31+
switch {
32+
case !ok:
33+
attributes.PutStr(string(otelsemconv.ServiceNameKey), missingServiceName)
34+
case serviceName.Type() != pcommon.ValueTypeStr:
35+
attributes.PutStr(string(otelsemconv.ServiceNameKey), serviceNameWrongType)
36+
case serviceName.Str() == "":
37+
attributes.PutStr(string(otelsemconv.ServiceNameKey), emptyServiceName)
38+
}
39+
}
40+
return traces
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) 2024 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package sanitizer
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
"go.opentelemetry.io/collector/pdata/ptrace"
11+
)
12+
13+
func TestEmptyServiceNameSanitizer_SubstitutesCorrectlyForStrings(t *testing.T) {
14+
emptyServiceName := ""
15+
nonEmptyServiceName := "hello"
16+
tests := []struct {
17+
name string
18+
serviceName *string
19+
expectedServiceName string
20+
}{
21+
{
22+
name: "no service name",
23+
expectedServiceName: "missing-service-name",
24+
},
25+
{
26+
name: "empty service name",
27+
serviceName: &emptyServiceName,
28+
expectedServiceName: "empty-service-name",
29+
},
30+
{
31+
name: "non-empty service name",
32+
serviceName: &nonEmptyServiceName,
33+
expectedServiceName: "hello",
34+
},
35+
}
36+
37+
for _, test := range tests {
38+
t.Run(test.name, func(t *testing.T) {
39+
traces := ptrace.NewTraces()
40+
attributes := traces.
41+
ResourceSpans().
42+
AppendEmpty().
43+
Resource().
44+
Attributes()
45+
if test.serviceName != nil {
46+
attributes.PutStr("service.name", *test.serviceName)
47+
}
48+
sanitizer := NewEmptyServiceNameSanitizer()
49+
sanitized := sanitizer(traces)
50+
serviceName, ok := sanitized.
51+
ResourceSpans().
52+
At(0).
53+
Resource().
54+
Attributes().
55+
Get("service.name")
56+
require.True(t, ok)
57+
require.Equal(t, test.expectedServiceName, serviceName.Str())
58+
})
59+
}
60+
}
61+
62+
func TestEmptyServiceNameSanitizer_SubstitutesCorrectlyForNonStringType(t *testing.T) {
63+
traces := ptrace.NewTraces()
64+
traces.
65+
ResourceSpans().
66+
AppendEmpty().
67+
Resource().
68+
Attributes().
69+
PutInt("service.name", 1)
70+
sanitizer := NewEmptyServiceNameSanitizer()
71+
sanitized := sanitizer(traces)
72+
serviceName, ok := sanitized.
73+
ResourceSpans().
74+
At(0).
75+
Resource().
76+
Attributes().
77+
Get("service.name")
78+
require.True(t, ok)
79+
require.Equal(t, "service-name-wrong-type", serviceName.Str())
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) 2024 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package sanitizer
5+
6+
import (
7+
"testing"
8+
9+
"github.com/jaegertracing/jaeger/pkg/testutils"
10+
)
11+
12+
func TestMain(m *testing.M) {
13+
testutils.VerifyGoLeaks(m)
14+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) 2024 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package sanitizer
5+
6+
import (
7+
"go.opentelemetry.io/collector/pdata/ptrace"
8+
)
9+
10+
// Func is a function that performs enrichment, clean-up, or normalization of trace data.
11+
type Func func(traces ptrace.Traces) ptrace.Traces
12+
13+
// NewStandardSanitizers returns a list of all the sanitizers that are used by the
14+
// storage exporter.
15+
func NewStandardSanitizers() []Func {
16+
return []Func{
17+
NewEmptyServiceNameSanitizer(),
18+
}
19+
}
20+
21+
// NewChainedSanitizer creates a Sanitizer from the variadic list of passed Sanitizers.
22+
// If the list only has one element, it is returned directly to minimize indirection.
23+
func NewChainedSanitizer(sanitizers ...Func) Func {
24+
if len(sanitizers) == 1 {
25+
return sanitizers[0]
26+
}
27+
return func(traces ptrace.Traces) ptrace.Traces {
28+
for _, s := range sanitizers {
29+
traces = s(traces)
30+
}
31+
return traces
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) 2024 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package sanitizer
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
"go.opentelemetry.io/collector/pdata/ptrace"
11+
)
12+
13+
func TestNewStandardSanitizers(t *testing.T) {
14+
sanitizers := NewStandardSanitizers()
15+
require.Len(t, sanitizers, 1)
16+
}
17+
18+
func TestNewChainedSanitizer(t *testing.T) {
19+
var s1 Func = func(traces ptrace.Traces) ptrace.Traces {
20+
traces.
21+
ResourceSpans().
22+
AppendEmpty().
23+
Resource().
24+
Attributes().
25+
PutStr("hello", "world")
26+
return traces
27+
}
28+
var s2 Func = func(traces ptrace.Traces) ptrace.Traces {
29+
traces.
30+
ResourceSpans().
31+
At(0).
32+
Resource().
33+
Attributes().
34+
PutStr("hello", "goodbye")
35+
return traces
36+
}
37+
c1 := NewChainedSanitizer(s1)
38+
t1 := c1(ptrace.NewTraces())
39+
hello, ok := t1.
40+
ResourceSpans().
41+
At(0).
42+
Resource().
43+
Attributes().
44+
Get("hello")
45+
require.True(t, ok)
46+
require.Equal(t, "world", hello.Str())
47+
c2 := NewChainedSanitizer(s1, s2)
48+
t2 := c2(ptrace.NewTraces())
49+
hello, ok = t2.
50+
ResourceSpans().
51+
At(0).
52+
Resource().
53+
Attributes().
54+
Get("hello")
55+
require.True(t, ok)
56+
require.Equal(t, "goodbye", hello.Str())
57+
}

0 commit comments

Comments
 (0)