Skip to content

Commit 147010a

Browse files
authored
Enrich root spans that represent a dependency (#125)
* Enrich root spans that represent a dependency * Merge enrichSpan and enrichExitSpanTransaction methods * Update span.go * Set both `transaction` and `span` on root exit root spans * Remove using processor.event as multi value field * Adapt to main * Review feedback
1 parent 4400949 commit 147010a

File tree

2 files changed

+176
-9
lines changed

2 files changed

+176
-9
lines changed

enrichments/trace/internal/elastic/span.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,17 @@ func (s *spanEnrichmentContext) Enrich(span ptrace.Span, cfg config.Config) {
194194
}
195195

196196
func (s *spanEnrichmentContext) enrich(span ptrace.Span, cfg config.Config) {
197+
198+
// In OTel, a local root span can represent an outgoing call or a producer span.
199+
// In such cases, the span is still mapped into a transaction, but enriched
200+
// with additional attributes that are specific to the outgoing call or producer span.
201+
isExitRootSpan := s.isTransaction && span.Kind() == ptrace.SpanKindClient || span.Kind() == ptrace.SpanKindProducer
202+
197203
if s.isTransaction {
198204
s.enrichTransaction(span, cfg.Transaction)
199-
} else {
200-
s.enrichSpan(span, cfg.Span)
205+
}
206+
if !s.isTransaction || isExitRootSpan {
207+
s.enrichSpan(span, cfg.Span, cfg.Transaction.Type.Enabled, isExitRootSpan)
201208
}
202209
}
203210

@@ -247,22 +254,23 @@ func (s *spanEnrichmentContext) enrichTransaction(
247254
func (s *spanEnrichmentContext) enrichSpan(
248255
span ptrace.Span,
249256
cfg config.ElasticSpanConfig,
257+
transactionTypeEnabled bool,
258+
isExitRootSpan bool,
250259
) {
260+
var spanType, spanSubtype string
261+
251262
if cfg.TimestampUs.Enabled {
252263
span.Attributes().PutInt(elasticattr.TimestampUs, getTimestampUs(span.StartTimestamp()))
253264
}
254265
if cfg.Name.Enabled {
255266
span.Attributes().PutStr(elasticattr.SpanName, span.Name())
256267
}
257-
if cfg.ProcessorEvent.Enabled {
258-
span.Attributes().PutStr(elasticattr.ProcessorEvent, "span")
259-
}
260268
if cfg.RepresentativeCount.Enabled {
261269
repCount := getRepresentativeCount(span.TraceState().AsRaw())
262270
span.Attributes().PutDouble(elasticattr.SpanRepresentativeCount, repCount)
263271
}
264272
if cfg.TypeSubtype.Enabled {
265-
s.setSpanTypeSubtype(span)
273+
spanType, spanSubtype = s.setSpanTypeSubtype(span)
266274
}
267275
if cfg.EventOutcome.Enabled {
268276
s.setEventOutcome(span)
@@ -279,6 +287,19 @@ func (s *spanEnrichmentContext) enrichSpan(
279287
if cfg.InferredSpans.Enabled {
280288
s.setInferredSpans(span)
281289
}
290+
if cfg.ProcessorEvent.Enabled && !isExitRootSpan {
291+
span.Attributes().PutStr(elasticattr.ProcessorEvent, "span")
292+
}
293+
294+
if isExitRootSpan && transactionTypeEnabled {
295+
if spanType != "" {
296+
transactionType := spanType
297+
if spanSubtype != "" {
298+
transactionType += "." + spanSubtype
299+
}
300+
span.Attributes().PutStr(elasticattr.TransactionType, transactionType)
301+
}
302+
}
282303
}
283304

284305
// normalizeAttributes sets any dependent attributes that
@@ -352,9 +373,7 @@ func (s *spanEnrichmentContext) setEventOutcome(span ptrace.Span) {
352373
span.Attributes().PutInt(elasticattr.SuccessCount, int64(successCount))
353374
}
354375

355-
func (s *spanEnrichmentContext) setSpanTypeSubtype(span ptrace.Span) {
356-
var spanType, spanSubtype string
357-
376+
func (s *spanEnrichmentContext) setSpanTypeSubtype(span ptrace.Span) (spanType string, spanSubtype string) {
358377
switch {
359378
case s.isDB:
360379
spanType = "db"
@@ -385,6 +404,8 @@ func (s *spanEnrichmentContext) setSpanTypeSubtype(span ptrace.Span) {
385404
if spanSubtype != "" {
386405
span.Attributes().PutStr(elasticattr.SpanSubtype, spanSubtype)
387406
}
407+
408+
return spanType, spanSubtype
388409
}
389410

390411
func (s *spanEnrichmentContext) setServiceTarget(span ptrace.Span) {

enrichments/trace/internal/elastic/span_test.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,152 @@ func TestElasticTransactionEnrich(t *testing.T) {
388388
}
389389
}
390390

391+
// Tests root spans that represent a dependency and are mapped to a transaction.
392+
func TestRootSpanAsDependencyEnrich(t *testing.T) {
393+
for _, tc := range []struct {
394+
name string
395+
input ptrace.Span
396+
config config.Config
397+
enrichedAttrs map[string]any
398+
expectedSpanLinks *ptrace.SpanLinkSlice
399+
}{
400+
{
401+
name: "outgoing_http_root_span",
402+
input: func() ptrace.Span {
403+
span := ptrace.NewSpan()
404+
span.SetName("rootClientSpan")
405+
span.SetSpanID([8]byte{1})
406+
span.SetKind(ptrace.SpanKindClient)
407+
span.Attributes().PutStr(semconv27.AttributeHTTPRequestMethod, "GET")
408+
span.Attributes().PutStr(semconv27.AttributeURLFull, "http://localhost:8080")
409+
span.Attributes().PutInt(semconv27.AttributeHTTPResponseStatusCode, 200)
410+
span.Attributes().PutStr(semconv27.AttributeNetworkProtocolVersion, "1.1")
411+
return span
412+
}(),
413+
config: config.Enabled(),
414+
enrichedAttrs: map[string]any{
415+
elasticattr.TimestampUs: int64(0),
416+
elasticattr.TransactionName: "rootClientSpan",
417+
elasticattr.ProcessorEvent: "transaction",
418+
elasticattr.SpanType: "external",
419+
elasticattr.SpanSubtype: "http",
420+
elasticattr.SpanDestinationServiceResource: "localhost:8080",
421+
elasticattr.SpanName: "rootClientSpan",
422+
elasticattr.EventOutcome: "success",
423+
elasticattr.SuccessCount: int64(1),
424+
elasticattr.ServiceTargetName: "localhost:8080",
425+
elasticattr.ServiceTargetType: "http",
426+
elasticattr.TransactionID: "0100000000000000",
427+
elasticattr.TransactionDurationUs: int64(0),
428+
elasticattr.TransactionRepresentativeCount: float64(1),
429+
elasticattr.TransactionResult: "HTTP 2xx",
430+
elasticattr.TransactionType: "external.http",
431+
elasticattr.TransactionSampled: true,
432+
elasticattr.TransactionRoot: true,
433+
elasticattr.SpanDurationUs: int64(0),
434+
elasticattr.SpanRepresentativeCount: float64(1),
435+
},
436+
},
437+
{
438+
name: "db_root_span",
439+
input: func() ptrace.Span {
440+
span := ptrace.NewSpan()
441+
span.SetName("rootClientSpan")
442+
span.SetSpanID([8]byte{1})
443+
span.SetKind(ptrace.SpanKindClient)
444+
span.Attributes().PutStr(semconv25.AttributeDBSystem, "mssql")
445+
446+
span.Attributes().PutStr(semconv25.AttributeDBName, "myDb")
447+
span.Attributes().PutStr(semconv25.AttributeDBOperation, "SELECT")
448+
span.Attributes().PutStr(semconv25.AttributeDBStatement, "SELECT * FROM wuser_table")
449+
return span
450+
}(),
451+
config: config.Enabled(),
452+
enrichedAttrs: map[string]any{
453+
elasticattr.TimestampUs: int64(0),
454+
elasticattr.TransactionName: "rootClientSpan",
455+
elasticattr.ProcessorEvent: "transaction",
456+
elasticattr.SpanType: "db",
457+
elasticattr.SpanSubtype: "mssql",
458+
elasticattr.SpanDestinationServiceResource: "mssql",
459+
elasticattr.SpanName: "rootClientSpan",
460+
elasticattr.EventOutcome: "success",
461+
elasticattr.SuccessCount: int64(1),
462+
elasticattr.ServiceTargetName: "myDb",
463+
elasticattr.ServiceTargetType: "mssql",
464+
elasticattr.TransactionID: "0100000000000000",
465+
elasticattr.TransactionDurationUs: int64(0),
466+
elasticattr.TransactionRepresentativeCount: float64(1),
467+
elasticattr.TransactionResult: "Success",
468+
elasticattr.TransactionType: "db.mssql",
469+
elasticattr.TransactionSampled: true,
470+
elasticattr.TransactionRoot: true,
471+
elasticattr.SpanDurationUs: int64(0),
472+
elasticattr.SpanRepresentativeCount: float64(1),
473+
},
474+
},
475+
{
476+
name: "producer_messaging_span",
477+
input: func() ptrace.Span {
478+
span := ptrace.NewSpan()
479+
span.SetName("rootClientSpan")
480+
span.SetSpanID([8]byte{1})
481+
span.SetKind(ptrace.SpanKindProducer)
482+
483+
span.Attributes().PutStr(semconv25.AttributeServerAddress, "myServer")
484+
span.Attributes().PutStr(semconv25.AttributeServerPort, "1234")
485+
span.Attributes().PutStr(semconv25.AttributeMessagingSystem, "rabbitmq")
486+
span.Attributes().PutStr(semconv25.AttributeMessagingDestinationName, "T")
487+
span.Attributes().PutStr(semconv25.AttributeMessagingOperation, "publish")
488+
span.Attributes().PutStr(semconv25.AttributeMessagingClientID, "a")
489+
return span
490+
}(),
491+
config: config.Enabled(),
492+
enrichedAttrs: map[string]any{
493+
elasticattr.TimestampUs: int64(0),
494+
elasticattr.TransactionName: "rootClientSpan",
495+
elasticattr.ProcessorEvent: "transaction",
496+
elasticattr.SpanType: "messaging",
497+
elasticattr.SpanSubtype: "rabbitmq",
498+
elasticattr.SpanDestinationServiceResource: "rabbitmq/T",
499+
elasticattr.SpanName: "rootClientSpan",
500+
elasticattr.EventOutcome: "success",
501+
elasticattr.SuccessCount: int64(1),
502+
elasticattr.ServiceTargetName: "T",
503+
elasticattr.ServiceTargetType: "rabbitmq",
504+
elasticattr.TransactionID: "0100000000000000",
505+
elasticattr.TransactionDurationUs: int64(0),
506+
elasticattr.TransactionRepresentativeCount: float64(1),
507+
elasticattr.TransactionResult: "Success",
508+
elasticattr.TransactionType: "messaging.rabbitmq",
509+
elasticattr.TransactionSampled: true,
510+
elasticattr.TransactionRoot: true,
511+
elasticattr.SpanDurationUs: int64(0),
512+
elasticattr.SpanRepresentativeCount: float64(1),
513+
},
514+
},
515+
} {
516+
t.Run(tc.name, func(t *testing.T) {
517+
expectedSpan := ptrace.NewSpan()
518+
tc.input.CopyTo(expectedSpan)
519+
520+
// Merge with the expected attributes and override the span links.
521+
for k, v := range tc.enrichedAttrs {
522+
expectedSpan.Attributes().PutEmpty(k).FromRaw(v)
523+
}
524+
// Override span links
525+
if tc.expectedSpanLinks != nil {
526+
tc.expectedSpanLinks.CopyTo(expectedSpan.Links())
527+
} else {
528+
expectedSpan.Links().RemoveIf(func(_ ptrace.SpanLink) bool { return true })
529+
}
530+
531+
EnrichSpan(tc.input, tc.config)
532+
assert.NoError(t, ptracetest.CompareSpan(expectedSpan, tc.input))
533+
})
534+
}
535+
}
536+
391537
// Tests the enrichment logic for elastic's span definition.
392538
func TestElasticSpanEnrich(t *testing.T) {
393539
now := time.Unix(3600, 0)

0 commit comments

Comments
 (0)