449 lines
14 KiB
Go
449 lines
14 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package attributesprocessor
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap"
|
|
|
|
"go.opentelemetry.io/collector/component"
|
|
"go.opentelemetry.io/collector/consumer/consumertest"
|
|
"go.opentelemetry.io/collector/consumer/pdata"
|
|
"go.opentelemetry.io/collector/internal/processor/filterconfig"
|
|
"go.opentelemetry.io/collector/internal/processor/filterset"
|
|
"go.opentelemetry.io/collector/internal/testdata"
|
|
"go.opentelemetry.io/collector/processor/processorhelper"
|
|
"go.opentelemetry.io/collector/translator/conventions"
|
|
)
|
|
|
|
// Common structure for all the Tests
|
|
type testCase struct {
|
|
name string
|
|
serviceName string
|
|
inputAttributes map[string]pdata.AttributeValue
|
|
expectedAttributes map[string]pdata.AttributeValue
|
|
}
|
|
|
|
// runIndividualTestCase is the common logic of passing trace data through a configured attributes processor.
|
|
func runIndividualTestCase(t *testing.T, tt testCase, tp component.TracesProcessor) {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
td := generateTraceData(tt.serviceName, tt.name, tt.inputAttributes)
|
|
assert.NoError(t, tp.ConsumeTraces(context.Background(), td))
|
|
// Ensure that the modified `td` has the attributes sorted:
|
|
sortAttributes(td)
|
|
require.Equal(t, generateTraceData(tt.serviceName, tt.name, tt.expectedAttributes), td)
|
|
})
|
|
}
|
|
|
|
func generateTraceData(serviceName, spanName string, attrs map[string]pdata.AttributeValue) pdata.Traces {
|
|
td := pdata.NewTraces()
|
|
rs := td.ResourceSpans().AppendEmpty()
|
|
if serviceName != "" {
|
|
rs.Resource().Attributes().UpsertString(conventions.AttributeServiceName, serviceName)
|
|
}
|
|
span := rs.InstrumentationLibrarySpans().AppendEmpty().Spans().AppendEmpty()
|
|
span.SetName(spanName)
|
|
span.Attributes().InitFromMap(attrs).Sort()
|
|
return td
|
|
}
|
|
|
|
func sortAttributes(td pdata.Traces) {
|
|
rss := td.ResourceSpans()
|
|
for i := 0; i < rss.Len(); i++ {
|
|
rs := rss.At(i)
|
|
rs.Resource().Attributes().Sort()
|
|
ilss := rs.InstrumentationLibrarySpans()
|
|
for j := 0; j < ilss.Len(); j++ {
|
|
spans := ilss.At(j).Spans()
|
|
for k := 0; k < spans.Len(); k++ {
|
|
spans.At(k).Attributes().Sort()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSpanProcessor_Values tests all possible value types.
|
|
func TestSpanProcessor_NilEmptyData(t *testing.T) {
|
|
type nilEmptyTestCase struct {
|
|
name string
|
|
input pdata.Traces
|
|
output pdata.Traces
|
|
}
|
|
// TODO: Add test for "nil" Span/Attributes. This needs support from data slices to allow to construct that.
|
|
testCases := []nilEmptyTestCase{
|
|
{
|
|
name: "empty",
|
|
input: pdata.NewTraces(),
|
|
output: pdata.NewTraces(),
|
|
},
|
|
{
|
|
name: "one-empty-resource-spans",
|
|
input: testdata.GenerateTracesOneEmptyResourceSpans(),
|
|
output: testdata.GenerateTracesOneEmptyResourceSpans(),
|
|
},
|
|
{
|
|
name: "no-libraries",
|
|
input: testdata.GenerateTracesNoLibraries(),
|
|
output: testdata.GenerateTracesNoLibraries(),
|
|
},
|
|
{
|
|
name: "one-empty-instrumentation-library",
|
|
input: testdata.GenerateTracesOneEmptyInstrumentationLibrary(),
|
|
output: testdata.GenerateTracesOneEmptyInstrumentationLibrary(),
|
|
},
|
|
}
|
|
factory := NewFactory()
|
|
cfg := factory.CreateDefaultConfig()
|
|
oCfg := cfg.(*Config)
|
|
oCfg.Settings.Actions = []processorhelper.ActionKeyValue{
|
|
{Key: "attribute1", Action: processorhelper.INSERT, Value: 123},
|
|
{Key: "attribute1", Action: processorhelper.DELETE},
|
|
}
|
|
|
|
tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateSettings{Logger: zap.NewNop()}, oCfg, consumertest.NewNop())
|
|
require.Nil(t, err)
|
|
require.NotNil(t, tp)
|
|
for i := range testCases {
|
|
tt := testCases[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.NoError(t, tp.ConsumeTraces(context.Background(), tt.input))
|
|
assert.EqualValues(t, tt.output, tt.input)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAttributes_FilterSpans(t *testing.T) {
|
|
testCases := []testCase{
|
|
{
|
|
name: "apply processor",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"attribute1": pdata.NewAttributeValueInt(123),
|
|
},
|
|
},
|
|
{
|
|
name: "apply processor with different value for exclude property",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(false),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"attribute1": pdata.NewAttributeValueInt(123),
|
|
"NoModification": pdata.NewAttributeValueBool(false),
|
|
},
|
|
},
|
|
{
|
|
name: "incorrect name for include property",
|
|
serviceName: "noname",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{},
|
|
},
|
|
{
|
|
name: "attribute match for exclude property",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(true),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(true),
|
|
},
|
|
},
|
|
}
|
|
|
|
factory := NewFactory()
|
|
cfg := factory.CreateDefaultConfig()
|
|
oCfg := cfg.(*Config)
|
|
oCfg.Actions = []processorhelper.ActionKeyValue{
|
|
{Key: "attribute1", Action: processorhelper.INSERT, Value: 123},
|
|
}
|
|
oCfg.Include = &filterconfig.MatchProperties{
|
|
Services: []string{"svcA", "svcB.*"},
|
|
Config: *createConfig(filterset.Regexp),
|
|
}
|
|
oCfg.Exclude = &filterconfig.MatchProperties{
|
|
Attributes: []filterconfig.Attribute{
|
|
{Key: "NoModification", Value: true},
|
|
},
|
|
Config: *createConfig(filterset.Strict),
|
|
}
|
|
tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateSettings{}, cfg, consumertest.NewNop())
|
|
require.Nil(t, err)
|
|
require.NotNil(t, tp)
|
|
|
|
for _, tt := range testCases {
|
|
runIndividualTestCase(t, tt, tp)
|
|
}
|
|
}
|
|
|
|
func TestAttributes_FilterSpansByNameStrict(t *testing.T) {
|
|
testCases := []testCase{
|
|
{
|
|
name: "apply",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"attribute1": pdata.NewAttributeValueInt(123),
|
|
},
|
|
},
|
|
{
|
|
name: "apply",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(false),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"attribute1": pdata.NewAttributeValueInt(123),
|
|
"NoModification": pdata.NewAttributeValueBool(false),
|
|
},
|
|
},
|
|
{
|
|
name: "incorrect_span_name",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{},
|
|
},
|
|
{
|
|
name: "dont_apply",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{},
|
|
},
|
|
{
|
|
name: "incorrect_span_name_with_attr",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(true),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(true),
|
|
},
|
|
},
|
|
}
|
|
|
|
factory := NewFactory()
|
|
cfg := factory.CreateDefaultConfig()
|
|
oCfg := cfg.(*Config)
|
|
oCfg.Actions = []processorhelper.ActionKeyValue{
|
|
{Key: "attribute1", Action: processorhelper.INSERT, Value: 123},
|
|
}
|
|
oCfg.Include = &filterconfig.MatchProperties{
|
|
SpanNames: []string{"apply", "dont_apply"},
|
|
Config: *createConfig(filterset.Strict),
|
|
}
|
|
oCfg.Exclude = &filterconfig.MatchProperties{
|
|
SpanNames: []string{"dont_apply"},
|
|
Config: *createConfig(filterset.Strict),
|
|
}
|
|
tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateSettings{}, cfg, consumertest.NewNop())
|
|
require.Nil(t, err)
|
|
require.NotNil(t, tp)
|
|
|
|
for _, tt := range testCases {
|
|
runIndividualTestCase(t, tt, tp)
|
|
}
|
|
}
|
|
|
|
func TestAttributes_FilterSpansByNameRegexp(t *testing.T) {
|
|
testCases := []testCase{
|
|
{
|
|
name: "apply_to_span_with_no_attrs",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"attribute1": pdata.NewAttributeValueInt(123),
|
|
},
|
|
},
|
|
{
|
|
name: "apply_to_span_with_attr",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(false),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"attribute1": pdata.NewAttributeValueInt(123),
|
|
"NoModification": pdata.NewAttributeValueBool(false),
|
|
},
|
|
},
|
|
{
|
|
name: "incorrect_span_name",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{},
|
|
},
|
|
{
|
|
name: "apply_dont_apply",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{},
|
|
},
|
|
{
|
|
name: "incorrect_span_name_with_attr",
|
|
serviceName: "svcB",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(true),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(true),
|
|
},
|
|
},
|
|
}
|
|
|
|
factory := NewFactory()
|
|
cfg := factory.CreateDefaultConfig()
|
|
oCfg := cfg.(*Config)
|
|
oCfg.Actions = []processorhelper.ActionKeyValue{
|
|
{Key: "attribute1", Action: processorhelper.INSERT, Value: 123},
|
|
}
|
|
oCfg.Include = &filterconfig.MatchProperties{
|
|
SpanNames: []string{"^apply.*"},
|
|
Config: *createConfig(filterset.Regexp),
|
|
}
|
|
oCfg.Exclude = &filterconfig.MatchProperties{
|
|
SpanNames: []string{".*dont_apply$"},
|
|
Config: *createConfig(filterset.Regexp),
|
|
}
|
|
tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateSettings{}, cfg, consumertest.NewNop())
|
|
require.Nil(t, err)
|
|
require.NotNil(t, tp)
|
|
|
|
for _, tt := range testCases {
|
|
runIndividualTestCase(t, tt, tp)
|
|
}
|
|
}
|
|
|
|
func TestAttributes_Hash(t *testing.T) {
|
|
testCases := []testCase{
|
|
{
|
|
name: "String",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"user.email": pdata.NewAttributeValueString("john.doe@example.com"),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"user.email": pdata.NewAttributeValueString("73ec53c4ba1747d485ae2a0d7bfafa6cda80a5a9"),
|
|
},
|
|
},
|
|
{
|
|
name: "Int",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"user.id": pdata.NewAttributeValueInt(10),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"user.id": pdata.NewAttributeValueString("71aa908aff1548c8c6cdecf63545261584738a25"),
|
|
},
|
|
},
|
|
{
|
|
name: "Double",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"user.balance": pdata.NewAttributeValueDouble(99.1),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"user.balance": pdata.NewAttributeValueString("76429edab4855b03073f9429fd5d10313c28655e"),
|
|
},
|
|
},
|
|
{
|
|
name: "Bool",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"user.authenticated": pdata.NewAttributeValueBool(true),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"user.authenticated": pdata.NewAttributeValueString("bf8b4530d8d246dd74ac53a13471bba17941dff7"),
|
|
},
|
|
},
|
|
}
|
|
|
|
factory := NewFactory()
|
|
cfg := factory.CreateDefaultConfig()
|
|
oCfg := cfg.(*Config)
|
|
oCfg.Actions = []processorhelper.ActionKeyValue{
|
|
{Key: "user.email", Action: processorhelper.HASH},
|
|
{Key: "user.id", Action: processorhelper.HASH},
|
|
{Key: "user.balance", Action: processorhelper.HASH},
|
|
{Key: "user.authenticated", Action: processorhelper.HASH},
|
|
}
|
|
|
|
tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateSettings{}, cfg, consumertest.NewNop())
|
|
require.Nil(t, err)
|
|
require.NotNil(t, tp)
|
|
|
|
for _, tt := range testCases {
|
|
runIndividualTestCase(t, tt, tp)
|
|
}
|
|
}
|
|
|
|
func BenchmarkAttributes_FilterSpansByName(b *testing.B) {
|
|
testCases := []testCase{
|
|
{
|
|
name: "apply_to_span_with_no_attrs",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"attribute1": pdata.NewAttributeValueInt(123),
|
|
},
|
|
},
|
|
{
|
|
name: "apply_to_span_with_attr",
|
|
inputAttributes: map[string]pdata.AttributeValue{
|
|
"NoModification": pdata.NewAttributeValueBool(false),
|
|
},
|
|
expectedAttributes: map[string]pdata.AttributeValue{
|
|
"attribute1": pdata.NewAttributeValueInt(123),
|
|
"NoModification": pdata.NewAttributeValueBool(false),
|
|
},
|
|
},
|
|
{
|
|
name: "dont_apply",
|
|
inputAttributes: map[string]pdata.AttributeValue{},
|
|
expectedAttributes: map[string]pdata.AttributeValue{},
|
|
},
|
|
}
|
|
|
|
factory := NewFactory()
|
|
cfg := factory.CreateDefaultConfig()
|
|
oCfg := cfg.(*Config)
|
|
oCfg.Actions = []processorhelper.ActionKeyValue{
|
|
{Key: "attribute1", Action: processorhelper.INSERT, Value: 123},
|
|
}
|
|
oCfg.Include = &filterconfig.MatchProperties{
|
|
SpanNames: []string{"^apply.*"},
|
|
}
|
|
tp, err := factory.CreateTracesProcessor(context.Background(), component.ProcessorCreateSettings{}, cfg, consumertest.NewNop())
|
|
require.Nil(b, err)
|
|
require.NotNil(b, tp)
|
|
|
|
for _, tt := range testCases {
|
|
td := generateTraceData(tt.serviceName, tt.name, tt.inputAttributes)
|
|
|
|
b.Run(tt.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
assert.NoError(b, tp.ConsumeTraces(context.Background(), td))
|
|
}
|
|
})
|
|
|
|
// Ensure that the modified `td` has the attributes sorted:
|
|
sortAttributes(td)
|
|
require.Equal(b, generateTraceData(tt.serviceName, tt.name, tt.expectedAttributes), td)
|
|
}
|
|
}
|
|
|
|
func createConfig(matchType filterset.MatchType) *filterset.Config {
|
|
return &filterset.Config{
|
|
MatchType: matchType,
|
|
}
|
|
}
|