Skip to content

Commit fa31851

Browse files
Update span enrichment to align with existing APM data logic (#237)
* Add enrichment for `service.instance.id` which follows existing APM logic for `service.node.name` * Update the `elasticattr` package to include supported resource attributes * 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. * Fix faas trigger id attribute name
1 parent 6d03991 commit fa31851

File tree

6 files changed

+315
-19
lines changed

6 files changed

+315
-19
lines changed

elasticattr/attributes.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,40 @@ package elasticattr
1919

2020
const (
2121
// resource s
22-
AgentName = "agent.name"
23-
AgentVersion = "agent.version"
22+
AgentName = "agent.name"
23+
AgentVersion = "agent.version"
24+
AgentEphemeralID = "agent.ephemeral_id"
25+
AgentActivationMethod = "agent.activation_method"
26+
CloudOriginAccountID = "cloud.origin.account.id"
27+
CloudOriginProvider = "cloud.origin.provider"
28+
CloudOriginRegion = "cloud.origin.region"
29+
CloudOriginServiceName = "cloud.origin.service.name"
30+
CloudAccountName = "cloud.account.name"
31+
CloudInstanceID = "cloud.instance.id"
32+
CloudInstanceName = "cloud.instance.name"
33+
CloudMachineType = "cloud.machine.type"
34+
CloudProjectID = "cloud.project.id"
35+
CloudProjectName = "cloud.project.name"
36+
ContainerImageTag = "container.image.tag"
37+
DeviceManufacturer = "device.manufacturer"
38+
DataStreamDataset = "data_stream.dataset"
39+
DataStreamNamespace = "data_stream.namespace"
40+
DestinationIP = "destination.ip"
41+
SourceNATIP = "source.nat.ip"
42+
FaaSExecution = "faas.execution"
43+
FaaSTriggerRequestID = "faas.trigger.request_id"
44+
HostHostName = "host.hostname"
45+
HostOSPlatform = "host.os.platform"
46+
ProcessRuntimeName = "process.runtime.name"
47+
ProcessRuntimeVersion = "process.runtime.version"
48+
ServiceLanguageName = "service.language.name"
49+
ServiceLanguageVersion = "service.language.version"
50+
ServiceRuntimeName = "service.runtime.name"
51+
ServiceRuntimeVersion = "service.runtime.version"
52+
ServiceOriginID = "service.origin.id"
53+
ServiceOriginName = "service.origin.name"
54+
ServiceOriginVersion = "service.origin.version"
55+
UserDomain = "user.domain"
2456

2557
// scope s
2658
ServiceFrameworkName = "service.framework.name"

enrichments/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type ResourceConfig struct {
3434
AgentVersion AttributeConfig `mapstructure:"agent_version"`
3535
OverrideHostName AttributeConfig `mapstructure:"override_host_name"`
3636
DeploymentEnvironment AttributeConfig `mapstructure:"deployment_environment"`
37+
ServiceInstanceID AttributeConfig `mapstructure:"service_instance_id"`
3738
}
3839

3940
// ScopeConfig configures the enrichment of scope attributes.
@@ -122,6 +123,7 @@ func Enabled() Config {
122123
AgentVersion: AttributeConfig{Enabled: true},
123124
OverrideHostName: AttributeConfig{Enabled: true},
124125
DeploymentEnvironment: AttributeConfig{Enabled: true},
126+
ServiceInstanceID: AttributeConfig{Enabled: true},
125127
},
126128
Scope: ScopeConfig{
127129
ServiceFrameworkName: AttributeConfig{Enabled: true},

enrichments/internal/elastic/resource.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ package elastic
2020
import (
2121
"fmt"
2222

23-
"github.com/elastic/opentelemetry-lib/elasticattr"
24-
"github.com/elastic/opentelemetry-lib/enrichments/config"
2523
"go.opentelemetry.io/collector/pdata/pcommon"
2624
semconv25 "go.opentelemetry.io/otel/semconv/v1.25.0"
2725
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
26+
27+
"github.com/elastic/opentelemetry-lib/elasticattr"
28+
"github.com/elastic/opentelemetry-lib/enrichments/config"
2829
)
2930

3031
// EnrichResource derives and adds Elastic specific resource attributes.
@@ -45,6 +46,9 @@ type resourceEnrichmentContext struct {
4546

4647
deploymentEnvironment string
4748
deploymentEnvironmentName string
49+
50+
serviceInstanceID string
51+
containerID string
4852
}
4953

5054
func (s *resourceEnrichmentContext) Enrich(resource pcommon.Resource, cfg config.ResourceConfig) {
@@ -68,6 +72,10 @@ func (s *resourceEnrichmentContext) Enrich(resource pcommon.Resource, cfg config
6872
s.deploymentEnvironment = v.Str()
6973
case string(semconv.DeploymentEnvironmentNameKey):
7074
s.deploymentEnvironmentName = v.Str()
75+
case string(semconv25.ServiceInstanceIDKey):
76+
s.serviceInstanceID = v.Str()
77+
case string(semconv.ContainerIDKey):
78+
s.containerID = v.Str()
7179
}
7280
return true
7381
})
@@ -91,6 +99,10 @@ func (s *resourceEnrichmentContext) Enrich(resource pcommon.Resource, cfg config
9199
if cfg.DeploymentEnvironment.Enabled {
92100
s.setDeploymentEnvironment(resource)
93101
}
102+
103+
if cfg.ServiceInstanceID.Enabled {
104+
s.setServiceInstanceID(resource)
105+
}
94106
}
95107

96108
// SemConv v1.27.0 deprecated `deployment.environment` and added `deployment.environment.name` in favor of it.
@@ -161,3 +173,23 @@ func (s *resourceEnrichmentContext) overrideHostNameWithK8sNodeName(resource pco
161173
s.k8sNodeName,
162174
)
163175
}
176+
177+
// setServiceInstanceID sets service.instance.id from container.id or host.name
178+
// if service.instance.id is not already set. This follows the existing APM logic for
179+
// `service.node.name`.
180+
func (s *resourceEnrichmentContext) setServiceInstanceID(resource pcommon.Resource) {
181+
if s.serviceInstanceID != "" {
182+
return
183+
}
184+
185+
switch {
186+
case s.containerID != "":
187+
s.serviceInstanceID = s.containerID
188+
case s.hostName != "":
189+
s.serviceInstanceID = s.hostName
190+
default:
191+
// no instance id could be derived
192+
return
193+
}
194+
resource.Attributes().PutStr(string(semconv25.ServiceInstanceIDKey), s.serviceInstanceID)
195+
}

enrichments/internal/elastic/resource_test.go

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ package elastic
2020
import (
2121
"testing"
2222

23-
"github.com/elastic/opentelemetry-lib/elasticattr"
24-
"github.com/elastic/opentelemetry-lib/enrichments/config"
2523
"github.com/google/go-cmp/cmp"
2624
"github.com/stretchr/testify/assert"
2725
"go.opentelemetry.io/collector/pdata/pcommon"
2826
semconv25 "go.opentelemetry.io/otel/semconv/v1.25.0"
2927
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
28+
29+
"github.com/elastic/opentelemetry-lib/elasticattr"
30+
"github.com/elastic/opentelemetry-lib/enrichments/config"
3031
)
3132

3233
func TestResourceEnrich(t *testing.T) {
@@ -174,10 +175,11 @@ func TestResourceEnrich(t *testing.T) {
174175
}(),
175176
config: config.Enabled().Resource,
176177
enrichedAttrs: map[string]any{
177-
string(semconv.HostNameKey): "k8s-node",
178-
string(semconv.K8SNodeNameKey): "k8s-node",
179-
elasticattr.AgentName: "otlp",
180-
elasticattr.AgentVersion: "unknown",
178+
string(semconv.HostNameKey): "k8s-node",
179+
string(semconv.K8SNodeNameKey): "k8s-node",
180+
elasticattr.AgentName: "otlp",
181+
elasticattr.AgentVersion: "unknown",
182+
string(semconv25.ServiceInstanceIDKey): string("test-host"),
181183
},
182184
},
183185
{
@@ -245,6 +247,58 @@ func TestResourceEnrich(t *testing.T) {
245247
elasticattr.AgentVersion: "unknown",
246248
},
247249
},
250+
{
251+
name: "service_instance_id_derived_from_container_id",
252+
input: func() pcommon.Resource {
253+
res := pcommon.NewResource()
254+
res.Attributes().PutStr(string(semconv.ServiceInstanceIDKey), "")
255+
res.Attributes().PutStr(string(semconv25.ContainerIDKey), "container-id")
256+
res.Attributes().PutStr(string(semconv25.HostNameKey), "k8s-node")
257+
return res
258+
}(),
259+
config: config.Enabled().Resource,
260+
enrichedAttrs: map[string]any{
261+
string(semconv25.ServiceInstanceIDKey): "container-id",
262+
string(semconv.ContainerIDKey): "container-id",
263+
string(semconv.HostNameKey): "k8s-node",
264+
elasticattr.AgentName: "otlp",
265+
elasticattr.AgentVersion: "unknown",
266+
},
267+
},
268+
{
269+
name: "service_instance_id_derived_from_host_name",
270+
input: func() pcommon.Resource {
271+
res := pcommon.NewResource()
272+
res.Attributes().PutStr(string(semconv.ServiceInstanceIDKey), "")
273+
res.Attributes().PutStr(string(semconv25.HostNameKey), "k8s-node")
274+
return res
275+
}(),
276+
config: config.Enabled().Resource,
277+
enrichedAttrs: map[string]any{
278+
string(semconv25.ServiceInstanceIDKey): "k8s-node",
279+
string(semconv.HostNameKey): "k8s-node",
280+
elasticattr.AgentName: "otlp",
281+
elasticattr.AgentVersion: "unknown",
282+
},
283+
},
284+
{
285+
name: "service_instance_id_already_set",
286+
input: func() pcommon.Resource {
287+
res := pcommon.NewResource()
288+
res.Attributes().PutStr(string(semconv.ServiceInstanceIDKey), "node-name")
289+
res.Attributes().PutStr(string(semconv25.ContainerIDKey), "container-id")
290+
res.Attributes().PutStr(string(semconv25.HostNameKey), "k8s-node")
291+
return res
292+
}(),
293+
config: config.Enabled().Resource,
294+
enrichedAttrs: map[string]any{
295+
string(semconv25.ServiceInstanceIDKey): "node-name",
296+
string(semconv.ContainerIDKey): "container-id",
297+
string(semconv.HostNameKey): "k8s-node",
298+
elasticattr.AgentName: "otlp",
299+
elasticattr.AgentVersion: "unknown",
300+
},
301+
},
248302
} {
249303
t.Run(tc.name, func(t *testing.T) {
250304
// Merge existing resource attrs with the attrs added

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

0 commit comments

Comments
 (0)