Skip to content

Commit c7194b0

Browse files
[receiver/elasticapmintake] Add mapping for missing APM spans field (#790)
* Add apm to otel mapping for remaining span fields/objects: Db, Message, Composite, RepresentativeCount, and Stackframe * Add nil checks to `event.Span` references
1 parent b3491e0 commit c7194b0

12 files changed

+706
-34
lines changed

receiver/elasticapmintakereceiver/internal/attributes.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,30 @@ package attr // import "github.com/elastic/opentelemetry-collector-components/re
2020
// These constants hold attribute names that are defined by the Elastic APM data model and do not match
2121
// any SemConv attribute. These fields are not used by the UI, and store information related to a specific span type
2222
const (
23-
SpanDBLink = "span.db.link"
24-
SpanDBRowsAffected = "span.db.rows_affected"
25-
SpanDBUserName = "span.db.user_name"
23+
SpanDBLink = "span.db.link"
24+
SpanDBRowsAffected = "span.db.rows_affected"
25+
SpanDBUserName = "span.db.user_name"
26+
SpanMessageBody = "span.message.body"
27+
SpanCompositeCompressionStrategy = "span.composite.compression_strategy"
28+
SpanCompositeCount = "span.composite.count"
29+
SpanCompositeSum = "span.composite.sum"
30+
SpanMessageAgeMs = "span.message.age.ms"
31+
SpanMessageHeadersPrefix = "span.message.headers."
32+
SpanRepresentativeCount = "span.representative_count"
33+
34+
SpanStacktrace = "span.stacktrace"
35+
SpanStacktraceFrameAbsPath = "abs_path"
36+
SpanStacktraceFrameClassname = "classname"
37+
SpanStacktraceFrameFilename = "filename"
38+
SpanStacktraceFrameFunction = "function"
39+
SpanStacktraceFrameLineNumber = "line.number"
40+
SpanStacktraceFrameLineColumn = "line.column"
41+
SpanStacktraceFrameLineContext = "line.context"
42+
SpanStacktraceFrameModule = "module"
43+
SpanStacktraceFrameContextPre = "context.pre"
44+
SpanStacktraceFrameContextPost = "context.post"
45+
SpanStacktraceFrameLibraryFrame = "library_frame"
46+
SpanStacktraceFrameVars = "vars"
2647

2748
HTTPRequestBody = "http.request.body"
2849
HTTPRequestID = "http.request.id"
@@ -31,8 +52,6 @@ const (
3152
HTTPResponseEncodedBodySize = "http.response.encoded_body_size"
3253
HTTPResponseTransferSize = "http.response.transfer_size"
3354

34-
SpanMessageBody = "span.message.body"
35-
3655
CloudProjectID = "cloud.project.id"
3756
CloudProjectName = "cloud.project.name"
3857

receiver/elasticapmintakereceiver/internal/mappers/intakeV2ToDerivedFields.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ func SetDerivedFieldsForSpan(event *modelpb.APMEvent, attributes pcommon.Map) {
4848

4949
attributes.PutStr(elasticattr.ProcessorEvent, "span")
5050
attributes.PutInt(elasticattr.SpanDurationUs, int64(event.Event.Duration/1_000))
51+
52+
if event.Span == nil {
53+
return
54+
}
55+
5156
attributes.PutStr("span.id", event.Span.Id)
5257
attributes.PutStr(elasticattr.SpanName, event.Span.Name)
5358
attributes.PutStr(elasticattr.SpanType, event.Span.Type)

receiver/elasticapmintakereceiver/internal/mappers/intakeV2ToElasticSpecificFields.go

Lines changed: 132 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,149 @@ import (
2727
attr "github.com/elastic/opentelemetry-collector-components/receiver/elasticapmintakereceiver/internal"
2828
)
2929

30+
var compressionStrategyText = map[modelpb.CompressionStrategy]string{
31+
modelpb.CompressionStrategy_COMPRESSION_STRATEGY_EXACT_MATCH: "exact_match",
32+
modelpb.CompressionStrategy_COMPRESSION_STRATEGY_SAME_KIND: "same_kind",
33+
}
34+
3035
// SetElasticSpecificFieldsForSpan sets fields on spans that are not defined by OTel.
3136
// Unlike fields from IntakeV2ToDerivedFields.go, these fields are not used by the UI
3237
// and store information about a specific span type
3338
func SetElasticSpecificFieldsForSpan(event *modelpb.APMEvent, attributesMap pcommon.Map) {
34-
if event.Span.Db != nil {
35-
attributesMap.PutStr(attr.SpanDBLink, event.Span.Db.Link)
36-
// SemConv db.response.returned_rows is similar, but not the same
37-
attributesMap.PutInt(attr.SpanDBRowsAffected, int64(*event.Span.Db.RowsAffected))
38-
attributesMap.PutStr(attr.SpanDBUserName, event.Span.Db.UserName)
39+
if event.Http != nil {
40+
if event.Http.Request != nil {
41+
if event.Http.Request.Body != nil {
42+
attributesMap.PutStr(attr.HTTPRequestBody, event.Http.Request.Body.GetStringValue())
43+
}
44+
if event.Http.Request.Id != "" {
45+
attributesMap.PutStr(attr.HTTPRequestID, event.Http.Request.Id)
46+
}
47+
if event.Http.Request.Referrer != "" {
48+
attributesMap.PutStr(attr.HTTPRequestReferrer, event.Http.Request.Referrer)
49+
}
50+
}
51+
52+
if event.Http.Response != nil {
53+
if event.Http.Response.DecodedBodySize != nil {
54+
attributesMap.PutInt(attr.HTTPResponseDecodedBodySize, int64(*event.Http.Response.DecodedBodySize))
55+
}
56+
if event.Http.Response.EncodedBodySize != nil {
57+
attributesMap.PutInt(attr.HTTPResponseEncodedBodySize, int64(*event.Http.Response.EncodedBodySize))
58+
}
59+
if event.Http.Response.TransferSize != nil {
60+
attributesMap.PutInt(attr.HTTPResponseTransferSize, int64(*event.Http.Response.TransferSize))
61+
}
62+
}
3963
}
4064

41-
if event.Http.Request != nil {
42-
attributesMap.PutStr(attr.HTTPRequestBody, event.Http.Request.Body.GetStringValue())
43-
attributesMap.PutStr(attr.HTTPRequestID, event.Http.Request.Id)
44-
attributesMap.PutStr(attr.HTTPRequestReferrer, event.Http.Request.Referrer)
65+
if event.Span == nil {
66+
return
4567
}
4668

47-
if event.Http.Response != nil {
48-
// SemConv http.response.body.size may match one of these.
49-
attributesMap.PutInt(attr.HTTPResponseDecodedBodySize, int64(*event.Http.Response.DecodedBodySize))
50-
attributesMap.PutInt(attr.HTTPResponseEncodedBodySize, int64(*event.Http.Response.EncodedBodySize))
51-
attributesMap.PutInt(attr.HTTPResponseTransferSize, int64(*event.Http.Response.TransferSize))
69+
if event.Span.Db != nil {
70+
if event.Span.Db.Link != "" {
71+
attributesMap.PutStr(attr.SpanDBLink, event.Span.Db.Link)
72+
}
73+
if event.Span.Db.RowsAffected != nil {
74+
// SemConv db.response.returned_rows is similar, but not the same
75+
attributesMap.PutInt(attr.SpanDBRowsAffected, int64(*event.Span.Db.RowsAffected))
76+
}
77+
if event.Span.Db.UserName != "" {
78+
attributesMap.PutStr(attr.SpanDBUserName, event.Span.Db.UserName)
79+
}
5280
}
5381

5482
if event.Span.Message != nil {
55-
attributesMap.PutStr(attr.SpanMessageBody, event.Span.Message.Body)
83+
if event.Span.Message.Body != "" {
84+
attributesMap.PutStr(attr.SpanMessageBody, event.Span.Message.Body)
85+
}
86+
if event.Span.Message.AgeMillis != nil {
87+
attributesMap.PutInt(attr.SpanMessageAgeMs, int64(*event.Span.Message.AgeMillis))
88+
}
89+
for _, header := range event.Span.Message.Headers {
90+
headerKey := attr.SpanMessageHeadersPrefix + header.Key
91+
headerValues := attributesMap.PutEmptySlice(headerKey)
92+
headerValues.EnsureCapacity(len(header.Value))
93+
for _, v := range header.Value {
94+
headerValues.AppendEmpty().SetStr(v)
95+
}
96+
}
97+
}
98+
99+
if event.Span.Composite != nil {
100+
compressionStrategy, ok := compressionStrategyText[event.Span.Composite.CompressionStrategy]
101+
if ok {
102+
attributesMap.PutStr(attr.SpanCompositeCompressionStrategy, compressionStrategy)
103+
}
104+
attributesMap.PutInt(attr.SpanCompositeCount, int64(event.Span.Composite.Count))
105+
attributesMap.PutInt(attr.SpanCompositeSum, int64(event.Span.Composite.Sum))
106+
}
107+
108+
attributesMap.PutDouble(attr.SpanRepresentativeCount, event.Span.RepresentativeCount)
109+
110+
setStackTraceList(attributesMap, event.Span.Stacktrace)
111+
}
112+
113+
// setStackTraceList maps stacktrace frames to attributes map.
114+
// The stacktrace will be a list of objects (maps), each map representing a frame.
115+
func setStackTraceList(attributesMap pcommon.Map, stacktrace []*modelpb.StacktraceFrame) {
116+
if len(stacktrace) == 0 {
117+
return
118+
}
119+
120+
stacktraceSlice := attributesMap.PutEmptySlice(attr.SpanStacktrace)
121+
stacktraceSlice.EnsureCapacity(len(stacktrace))
122+
for _, frame := range stacktrace {
123+
frameMap := stacktraceSlice.AppendEmpty().SetEmptyMap()
124+
125+
if len(frame.Vars) > 0 {
126+
varsMap := frameMap.PutEmptyMap(attr.SpanStacktraceFrameVars)
127+
for _, varKV := range frame.Vars {
128+
varsMap.PutStr(varKV.Key, varKV.Value.GetStringValue())
129+
}
130+
}
131+
132+
if frame.Lineno != nil {
133+
frameMap.PutInt(attr.SpanStacktraceFrameLineNumber, int64(*frame.Lineno))
134+
}
135+
if frame.Colno != nil {
136+
frameMap.PutInt(attr.SpanStacktraceFrameLineColumn, int64(*frame.Colno))
137+
}
138+
if frame.Filename != "" {
139+
frameMap.PutStr(attr.SpanStacktraceFrameFilename, frame.Filename)
140+
}
141+
if frame.Classname != "" {
142+
frameMap.PutStr(attr.SpanStacktraceFrameClassname, frame.Classname)
143+
}
144+
if frame.ContextLine != "" {
145+
frameMap.PutStr(attr.SpanStacktraceFrameLineContext, frame.ContextLine)
146+
}
147+
if frame.Module != "" {
148+
frameMap.PutStr(attr.SpanStacktraceFrameModule, frame.Module)
149+
}
150+
if frame.Function != "" {
151+
frameMap.PutStr(attr.SpanStacktraceFrameFunction, frame.Function)
152+
}
153+
if frame.AbsPath != "" {
154+
frameMap.PutStr(attr.SpanStacktraceFrameAbsPath, frame.AbsPath)
155+
}
156+
157+
if len(frame.PreContext) > 0 {
158+
preSlice := frameMap.PutEmptySlice(attr.SpanStacktraceFrameContextPre)
159+
preSlice.EnsureCapacity(len(frame.PreContext))
160+
for _, pre := range frame.PreContext {
161+
preSlice.AppendEmpty().SetStr(pre)
162+
}
163+
}
164+
if len(frame.PostContext) > 0 {
165+
postSlice := frameMap.PutEmptySlice(attr.SpanStacktraceFrameContextPost)
166+
postSlice.EnsureCapacity(len(frame.PostContext))
167+
for _, post := range frame.PostContext {
168+
postSlice.AppendEmpty().SetStr(post)
169+
}
170+
}
171+
172+
frameMap.PutBool(attr.SpanStacktraceFrameLibraryFrame, frame.LibraryFrame)
56173
}
57174
}
58175

receiver/elasticapmintakereceiver/internal/mappers/intakeV2ToOtlpTopLevelFields.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ import (
2121
"strings"
2222
"time"
2323

24-
"github.com/elastic/apm-data/model/modelpb"
2524
"go.opentelemetry.io/collector/pdata/pcommon"
2625
"go.opentelemetry.io/collector/pdata/plog"
2726
"go.opentelemetry.io/collector/pdata/ptrace"
2827
"go.uber.org/zap"
28+
29+
"github.com/elastic/apm-data/model/modelpb"
2930
)
3031

3132
type TopLevelFieldSetter interface {
@@ -82,9 +83,10 @@ func SetTopLevelFieldsSpan(event *modelpb.APMEvent, timestamp time.Time, s ptrac
8283
}
8384
}
8485

85-
if strings.EqualFold(event.Event.Outcome, "success") {
86+
outcome := event.GetEvent().GetOutcome()
87+
if strings.EqualFold(outcome, "success") {
8688
s.Status().SetCode(ptrace.StatusCodeOk)
87-
} else if strings.EqualFold(event.Event.Outcome, "failure") {
89+
} else if strings.EqualFold(outcome, "failure") {
8890
s.Status().SetCode(ptrace.StatusCodeError)
8991
}
9092

receiver/elasticapmintakereceiver/internal/mappers/intakeV2ToSemConv.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ package mappers // import "github.com/elastic/opentelemetry-collector-components
2222
import (
2323
"strings"
2424

25-
"github.com/elastic/apm-data/model/modelpb"
2625
"go.opentelemetry.io/collector/pdata/pcommon"
2726
semconv22 "go.opentelemetry.io/otel/semconv/v1.22.0"
2827
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
28+
29+
"github.com/elastic/apm-data/model/modelpb"
2930
)
3031

3132
// Translates resource attributes from the Elastic APM model to SemConv resource attributes
@@ -105,6 +106,11 @@ func TranslateIntakeV2SpanToOTelAttributes(event *modelpb.APMEvent, attributes p
105106
attributes.PutStr(string(semconv.URLFullKey), event.Url.Full)
106107
}
107108
}
109+
110+
if event.Span == nil {
111+
return
112+
}
113+
108114
if event.Span.Db != nil {
109115
attributes.PutStr(string(semconv.DBSystemKey), event.Span.Db.Type)
110116
attributes.PutStr(string(semconv.DBNamespaceKey), event.Span.Db.Instance)

receiver/elasticapmintakereceiver/receiver.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -471,13 +471,13 @@ func (r *elasticAPMIntakeReceiver) translateBreakdownMetricsToOtel(rm *pmetric.R
471471
// github.com/open-telemetry/opentelemetry-collector-contrib/exporter/[email protected]/bulkindexer.go:367
472472
sum_metric.SetUnit("us")
473473
sum_dp := createBreakdownMetricsCommon(sum_metric, event, timestamp)
474-
sum_dp.SetIntValue(int64(event.Span.SelfTime.Sum))
474+
sum_dp.SetIntValue(int64(event.GetSpan().GetSelfTime().Sum))
475475

476476
count_metric := sm.Metrics().AppendEmpty()
477477
count_metric.SetName("span.self_time.count")
478478
count_metric.SetUnit("{span}")
479479
count_metric_dp := createBreakdownMetricsCommon(count_metric, event, timestamp)
480-
count_metric_dp.SetDoubleValue(float64(event.Span.SelfTime.Count))
480+
count_metric_dp.SetDoubleValue(float64(event.GetSpan().GetSelfTime().Count))
481481
}
482482

483483
func createBreakdownMetricsCommon(metric pmetric.Metric, event *modelpb.APMEvent, timestamp time.Time) pmetric.NumberDataPoint {
@@ -486,10 +486,15 @@ func createBreakdownMetricsCommon(metric pmetric.Metric, event *modelpb.APMEvent
486486
dp.SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
487487

488488
attr := dp.Attributes()
489-
attr.PutStr("transaction.name", event.Transaction.Name)
490-
attr.PutStr("transaction.type", event.Transaction.Type)
491-
attr.PutStr("span.type", event.Span.Type)
492-
attr.PutStr("span.subtype", event.Span.Subtype)
489+
if event.Transaction != nil {
490+
attr.PutStr("transaction.name", event.Transaction.Name)
491+
attr.PutStr("transaction.type", event.Transaction.Type)
492+
}
493+
if event.Span != nil {
494+
attr.PutStr("span.type", event.Span.Type)
495+
attr.PutStr("span.subtype", event.Span.Subtype)
496+
}
497+
493498
attr.PutStr("processor.event", "metric")
494499

495500
mappers.SetDerivedFieldsForMetrics(dp.Attributes())
@@ -582,6 +587,7 @@ func (r *elasticAPMIntakeReceiver) elasticSpanToOTelSpan(s *ptrace.Span, event *
582587

583588
mappers.SetDerivedFieldsForSpan(event, s.Attributes())
584589
mappers.TranslateIntakeV2SpanToOTelAttributes(event, s.Attributes())
590+
mappers.SetElasticSpecificFieldsForSpan(event, s.Attributes())
585591

586592
if event.Http != nil || event.Message != "" {
587593
s.SetKind(ptrace.SpanKindClient)

receiver/elasticapmintakereceiver/testdata/invalid_ids_expected.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,9 @@ resourceSpans:
299299
- key: span.action
300300
value:
301301
stringValue: query.custom
302+
- key: span.representative_count
303+
value:
304+
doubleValue: 1
302305
endTimeUnixNano: "1532976822422581000"
303306
name: GET /api/types
304307
startTimeUnixNano: "1532976822281000000"

receiver/elasticapmintakereceiver/testdata/span-links_expected.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ resourceSpans:
4747
- key: span.action
4848
value:
4949
stringValue: ""
50+
- key: span.representative_count
51+
value:
52+
doubleValue: 1
5053
endTimeUnixNano: "5409298"
5154
links:
5255
- spanId: 1123456a89012345

0 commit comments

Comments
 (0)