Skip to content

Commit 1df26ab

Browse files
committed
Update span enrichment to preserve existing values for: span.type, span.subtype, service.target.type, service.target.name, span.destination.service.resource.
This allows any events that have been received using the `elasticapmintake receiver` to retain their original values.
1 parent dd3ab6a commit 1df26ab

File tree

2 files changed

+185
-9
lines changed

2 files changed

+185
-9
lines changed

enrichments/internal/elastic/span.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ import (
2929
"net/url"
3030
"strconv"
3131

32-
"github.com/elastic/opentelemetry-lib/elasticattr"
33-
"github.com/elastic/opentelemetry-lib/enrichments/config"
3432
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
3533
"github.com/ua-parser/uap-go/uaparser"
3634
"go.opentelemetry.io/collector/pdata/pcommon"
@@ -40,6 +38,9 @@ import (
4038
semconv37 "go.opentelemetry.io/otel/semconv/v1.37.0"
4139
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
4240
"google.golang.org/grpc/codes"
41+
42+
"github.com/elastic/opentelemetry-lib/elasticattr"
43+
"github.com/elastic/opentelemetry-lib/enrichments/config"
4344
)
4445

4546
// defaultRepresentativeCount is the representative count to use for adjusting
@@ -444,9 +445,14 @@ func (s *spanEnrichmentContext) setSpanTypeSubtype(span ptrace.Span) (spanType s
444445
}
445446
}
446447

447-
span.Attributes().PutStr(elasticattr.SpanType, spanType)
448+
// do not overwrite existing span.type and span.subtype attributes
449+
if existingSpanType, _ := span.Attributes().Get(elasticattr.SpanType); existingSpanType.Str() == "" {
450+
span.Attributes().PutStr(elasticattr.SpanType, spanType)
451+
}
448452
if spanSubtype != "" {
449-
span.Attributes().PutStr(elasticattr.SpanSubtype, spanSubtype)
453+
if existingSpanSubtype, _ := span.Attributes().Get(elasticattr.SpanSubtype); existingSpanSubtype.Str() == "" {
454+
span.Attributes().PutStr(elasticattr.SpanSubtype, spanSubtype)
455+
}
450456
}
451457

452458
return spanType, spanSubtype
@@ -494,9 +500,15 @@ func (s *spanEnrichmentContext) setServiceTarget(span ptrace.Span) {
494500
}
495501
}
496502

503+
// set either target.type or target.name if at least one is available
497504
if targetType != "" || targetName != "" {
498-
span.Attributes().PutStr(elasticattr.ServiceTargetType, targetType)
499-
span.Attributes().PutStr(elasticattr.ServiceTargetName, targetName)
505+
// do not overwrite existing target.type and target.name attributes
506+
if existingTargetType, _ := span.Attributes().Get(elasticattr.ServiceTargetType); existingTargetType.Str() == "" {
507+
span.Attributes().PutStr(elasticattr.ServiceTargetType, targetType)
508+
}
509+
if existingTargetName, _ := span.Attributes().Get(elasticattr.ServiceTargetName); existingTargetName.Str() == "" {
510+
span.Attributes().PutStr(elasticattr.ServiceTargetName, targetName)
511+
}
500512
}
501513
}
502514

@@ -536,7 +548,10 @@ func (s *spanEnrichmentContext) setDestinationService(span ptrace.Span) {
536548
}
537549

538550
if destnResource != "" {
539-
span.Attributes().PutStr(elasticattr.SpanDestinationServiceResource, destnResource)
551+
// do not overwrite existing span.destination.service.resource attribute
552+
if existingDestnResource, _ := span.Attributes().Get(elasticattr.SpanDestinationServiceResource); existingDestnResource.Str() == "" {
553+
span.Attributes().PutStr(elasticattr.SpanDestinationServiceResource, destnResource)
554+
}
540555
}
541556
}
542557

enrichments/internal/elastic/span_test.go

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ import (
2424
"testing"
2525
"time"
2626

27-
"github.com/elastic/opentelemetry-lib/elasticattr"
28-
"github.com/elastic/opentelemetry-lib/enrichments/config"
2927
"github.com/google/go-cmp/cmp"
3028
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/ptracetest"
3129
"github.com/stretchr/testify/assert"
@@ -37,6 +35,9 @@ import (
3735
semconv37 "go.opentelemetry.io/otel/semconv/v1.37.0"
3836
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
3937
"google.golang.org/grpc/codes"
38+
39+
"github.com/elastic/opentelemetry-lib/elasticattr"
40+
"github.com/elastic/opentelemetry-lib/enrichments/config"
4041
)
4142

4243
// Tests the enrichment logic for elastic's transaction definition.
@@ -931,6 +932,39 @@ func TestElasticSpanEnrich(t *testing.T) {
931932
elasticattr.SpanDestinationServiceResource: "testsvc",
932933
},
933934
},
935+
{
936+
name: "http_span_with_existing_attributes_are_preserved",
937+
input: func() ptrace.Span {
938+
span := getElasticSpan()
939+
span.SetName("testspan")
940+
span.Attributes().PutStr(string(semconv25.PeerServiceKey), "testsvc")
941+
span.Attributes().PutInt(
942+
string(semconv25.HTTPResponseStatusCodeKey),
943+
http.StatusOK,
944+
)
945+
span.Attributes().PutStr(elasticattr.SpanType, "external-test")
946+
span.Attributes().PutStr(elasticattr.SpanSubtype, "http-test")
947+
span.Attributes().PutStr(elasticattr.ServiceTargetName, "api.example.com")
948+
span.Attributes().PutStr(elasticattr.ServiceTargetType, "http-test")
949+
span.Attributes().PutStr(elasticattr.SpanDestinationServiceResource, "api.example.com:443")
950+
return span
951+
}(),
952+
config: config.Enabled().Span,
953+
enrichedAttrs: map[string]any{
954+
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
955+
elasticattr.SpanName: "testspan",
956+
elasticattr.ProcessorEvent: "span",
957+
elasticattr.SpanRepresentativeCount: float64(1),
958+
elasticattr.SpanType: "external-test",
959+
elasticattr.SpanSubtype: "http-test",
960+
elasticattr.SpanDurationUs: expectedDuration.Microseconds(),
961+
elasticattr.EventOutcome: "success",
962+
elasticattr.SuccessCount: int64(1),
963+
elasticattr.ServiceTargetType: "http-test",
964+
elasticattr.ServiceTargetName: "api.example.com",
965+
elasticattr.SpanDestinationServiceResource: "api.example.com:443",
966+
},
967+
},
934968
{
935969
name: "http_span_full_url",
936970
input: func() ptrace.Span {
@@ -1059,6 +1093,39 @@ func TestElasticSpanEnrich(t *testing.T) {
10591093
elasticattr.SpanDestinationServiceResource: "testsvc",
10601094
},
10611095
},
1096+
{
1097+
name: "rpc_span_grpc_with_existing_attributes_are_preserved",
1098+
input: func() ptrace.Span {
1099+
span := getElasticSpan()
1100+
span.SetName("testspan")
1101+
span.Attributes().PutStr(string(semconv25.PeerServiceKey), "testsvc")
1102+
span.Attributes().PutInt(
1103+
string(semconv25.RPCGRPCStatusCodeKey),
1104+
int64(codes.OK),
1105+
)
1106+
span.Attributes().PutStr(elasticattr.SpanType, "external-test")
1107+
span.Attributes().PutStr(elasticattr.SpanSubtype, "grpc-test")
1108+
span.Attributes().PutStr(elasticattr.ServiceTargetName, "myservice.EchoService")
1109+
span.Attributes().PutStr(elasticattr.ServiceTargetType, "grpc-test")
1110+
span.Attributes().PutStr(elasticattr.SpanDestinationServiceResource, "api.example.com:443")
1111+
return span
1112+
}(),
1113+
config: config.Enabled().Span,
1114+
enrichedAttrs: map[string]any{
1115+
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
1116+
elasticattr.SpanName: "testspan",
1117+
elasticattr.ProcessorEvent: "span",
1118+
elasticattr.SpanRepresentativeCount: float64(1),
1119+
elasticattr.SpanType: "external-test",
1120+
elasticattr.SpanSubtype: "grpc-test",
1121+
elasticattr.SpanDurationUs: expectedDuration.Microseconds(),
1122+
elasticattr.EventOutcome: "success",
1123+
elasticattr.SuccessCount: int64(1),
1124+
elasticattr.ServiceTargetType: "grpc-test",
1125+
elasticattr.ServiceTargetName: "myservice.EchoService",
1126+
elasticattr.SpanDestinationServiceResource: "api.example.com:443",
1127+
},
1128+
},
10621129
{
10631130
name: "rpc_span_system",
10641131
input: func() ptrace.Span {
@@ -1187,6 +1254,36 @@ func TestElasticSpanEnrich(t *testing.T) {
11871254
elasticattr.SpanDestinationServiceResource: "testsvc",
11881255
},
11891256
},
1257+
{
1258+
name: "messaging_with_existing_attributes_are_preserved",
1259+
input: func() ptrace.Span {
1260+
span := getElasticSpan()
1261+
span.SetName("testspan")
1262+
span.Attributes().PutStr(string(semconv25.PeerServiceKey), "testsvc")
1263+
span.Attributes().PutStr(string(semconv25.MessagingSystemKey), "kafka")
1264+
span.Attributes().PutStr(elasticattr.SpanType, "messaging-test")
1265+
span.Attributes().PutStr(elasticattr.SpanSubtype, "kafka-test")
1266+
span.Attributes().PutStr(elasticattr.ServiceTargetName, "user-events")
1267+
span.Attributes().PutStr(elasticattr.ServiceTargetType, "kafka-test")
1268+
span.Attributes().PutStr(elasticattr.SpanDestinationServiceResource, "broker.com:443")
1269+
return span
1270+
}(),
1271+
config: config.Enabled().Span,
1272+
enrichedAttrs: map[string]any{
1273+
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
1274+
elasticattr.SpanName: "testspan",
1275+
elasticattr.ProcessorEvent: "span",
1276+
elasticattr.SpanRepresentativeCount: float64(1),
1277+
elasticattr.SpanType: "messaging-test",
1278+
elasticattr.SpanSubtype: "kafka-test",
1279+
elasticattr.SpanDurationUs: expectedDuration.Microseconds(),
1280+
elasticattr.EventOutcome: "success",
1281+
elasticattr.SuccessCount: int64(1),
1282+
elasticattr.ServiceTargetType: "kafka-test",
1283+
elasticattr.ServiceTargetName: "user-events",
1284+
elasticattr.SpanDestinationServiceResource: "broker.com:443",
1285+
},
1286+
},
11901287
{
11911288
name: "messaging_destination",
11921289
input: func() ptrace.Span {
@@ -1265,6 +1362,40 @@ func TestElasticSpanEnrich(t *testing.T) {
12651362
elasticattr.SpanDestinationServiceResource: "testsvc",
12661363
},
12671364
},
1365+
{
1366+
name: "db_with_existing_attributes_are_preserved",
1367+
input: func() ptrace.Span {
1368+
span := getElasticSpan()
1369+
span.SetName("testspan")
1370+
span.Attributes().PutStr(string(semconv25.PeerServiceKey), "testsvc")
1371+
span.Attributes().PutStr(
1372+
string(semconv25.URLFullKey),
1373+
"https://localhost:5432",
1374+
)
1375+
span.Attributes().PutStr(string(semconv25.DBSystemKey), "postgresql")
1376+
span.Attributes().PutStr(elasticattr.SpanType, "db")
1377+
span.Attributes().PutStr(elasticattr.SpanSubtype, "postgresql")
1378+
span.Attributes().PutStr(elasticattr.ServiceTargetName, "customers")
1379+
span.Attributes().PutStr(elasticattr.ServiceTargetType, "postgresql")
1380+
span.Attributes().PutStr(elasticattr.SpanDestinationServiceResource, "postgresql/testdb")
1381+
return span
1382+
}(),
1383+
config: config.Enabled().Span,
1384+
enrichedAttrs: map[string]any{
1385+
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
1386+
elasticattr.SpanName: "testspan",
1387+
elasticattr.ProcessorEvent: "span",
1388+
elasticattr.SpanRepresentativeCount: float64(1),
1389+
elasticattr.SpanType: "db",
1390+
elasticattr.SpanSubtype: "postgresql",
1391+
elasticattr.SpanDurationUs: expectedDuration.Microseconds(),
1392+
elasticattr.EventOutcome: "success",
1393+
elasticattr.SuccessCount: int64(1),
1394+
elasticattr.ServiceTargetType: "postgresql",
1395+
elasticattr.ServiceTargetName: "customers",
1396+
elasticattr.SpanDestinationServiceResource: "postgresql/testdb",
1397+
},
1398+
},
12681399
{
12691400
name: "db_over_rpc",
12701401
input: func() ptrace.Span {
@@ -1374,6 +1505,36 @@ func TestElasticSpanEnrich(t *testing.T) {
13741505
elasticattr.SuccessCount: int64(1),
13751506
},
13761507
},
1508+
{
1509+
name: "genai_with_existing_attributes_are_preserved",
1510+
input: func() ptrace.Span {
1511+
span := getElasticSpan()
1512+
span.SetName("testspan")
1513+
span.SetSpanID([8]byte{1})
1514+
span.Attributes().PutStr(string(semconv37.GenAIProviderNameKey), "openai")
1515+
span.Attributes().PutStr(elasticattr.SpanType, "genai")
1516+
span.Attributes().PutStr(elasticattr.SpanSubtype, "openai-test")
1517+
span.Attributes().PutStr(elasticattr.ServiceTargetName, "openai-api")
1518+
span.Attributes().PutStr(elasticattr.ServiceTargetType, "genai")
1519+
span.Attributes().PutStr(elasticattr.SpanDestinationServiceResource, "api.openai.com:443")
1520+
return span
1521+
}(),
1522+
config: config.Enabled().Span,
1523+
enrichedAttrs: map[string]any{
1524+
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
1525+
elasticattr.SpanName: "testspan",
1526+
elasticattr.ProcessorEvent: "span",
1527+
elasticattr.SpanRepresentativeCount: float64(1),
1528+
elasticattr.SpanType: "genai",
1529+
elasticattr.SpanSubtype: "openai-test",
1530+
elasticattr.SpanDurationUs: expectedDuration.Microseconds(),
1531+
elasticattr.EventOutcome: "success",
1532+
elasticattr.SuccessCount: int64(1),
1533+
elasticattr.ServiceTargetType: "genai",
1534+
elasticattr.ServiceTargetName: "openai-api",
1535+
elasticattr.SpanDestinationServiceResource: "api.openai.com:443",
1536+
},
1537+
},
13771538
{
13781539
name: "rpc_span_with_only_rpc_sevice_attr",
13791540
input: func() ptrace.Span {

0 commit comments

Comments
 (0)