Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 30 additions & 9 deletions enrichments/trace/internal/elastic/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,17 @@ func (s *spanEnrichmentContext) Enrich(span ptrace.Span, cfg config.Config) {
}

func (s *spanEnrichmentContext) enrich(span ptrace.Span, cfg config.Config) {

// In OTel, a local root span can represent an outgoing call or a producer span.
// In such cases, the span is still mapped into a transaction, but enriched
// with additional attributes that are specific to the outgoing call or producer span.
isExitRootSpan := s.isTransaction && span.Kind() == ptrace.SpanKindClient || span.Kind() == ptrace.SpanKindProducer
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is still up for discussion: #125 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#125 (comment) makes sense to me too


if s.isTransaction {
s.enrichTransaction(span, cfg.Transaction)
} else {
s.enrichSpan(span, cfg.Span)
}
if !s.isTransaction || isExitRootSpan {
s.enrichSpan(span, cfg.Span, cfg.Transaction.Type.Enabled, isExitRootSpan)
}
}

Expand Down Expand Up @@ -247,22 +254,23 @@ func (s *spanEnrichmentContext) enrichTransaction(
func (s *spanEnrichmentContext) enrichSpan(
span ptrace.Span,
cfg config.ElasticSpanConfig,
transactionTypeEnabled bool,
isExitRootSpan bool,
) {
var spanType, spanSubtype string

if cfg.TimestampUs.Enabled {
span.Attributes().PutInt(elasticattr.TimestampUs, getTimestampUs(span.StartTimestamp()))
}
if cfg.Name.Enabled {
span.Attributes().PutStr(elasticattr.SpanName, span.Name())
}
if cfg.ProcessorEvent.Enabled {
span.Attributes().PutStr(elasticattr.ProcessorEvent, "span")
}
if cfg.RepresentativeCount.Enabled {
repCount := getRepresentativeCount(span.TraceState().AsRaw())
span.Attributes().PutDouble(elasticattr.SpanRepresentativeCount, repCount)
}
if cfg.TypeSubtype.Enabled {
s.setSpanTypeSubtype(span)
spanType, spanSubtype = s.setSpanTypeSubtype(span)
}
if cfg.EventOutcome.Enabled {
s.setEventOutcome(span)
Expand All @@ -279,6 +287,19 @@ func (s *spanEnrichmentContext) enrichSpan(
if cfg.InferredSpans.Enabled {
s.setInferredSpans(span)
}
if cfg.ProcessorEvent.Enabled && !isExitRootSpan {
span.Attributes().PutStr(elasticattr.ProcessorEvent, "span")
}

if isExitRootSpan && transactionTypeEnabled {
if spanType != "" {
transactionType := spanType
if spanSubtype != "" {
transactionType += "." + spanSubtype
}
span.Attributes().PutStr(elasticattr.TransactionType, transactionType)
}
}
}

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

func (s *spanEnrichmentContext) setSpanTypeSubtype(span ptrace.Span) {
var spanType, spanSubtype string

func (s *spanEnrichmentContext) setSpanTypeSubtype(span ptrace.Span) (spanType string, spanSubtype string) {
switch {
case s.isDB:
spanType = "db"
Expand Down Expand Up @@ -385,6 +404,8 @@ func (s *spanEnrichmentContext) setSpanTypeSubtype(span ptrace.Span) {
if spanSubtype != "" {
span.Attributes().PutStr(elasticattr.SpanSubtype, spanSubtype)
}

return spanType, spanSubtype
}

func (s *spanEnrichmentContext) setServiceTarget(span ptrace.Span) {
Expand Down
146 changes: 146 additions & 0 deletions enrichments/trace/internal/elastic/span_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,152 @@ func TestElasticTransactionEnrich(t *testing.T) {
}
}

// Tests root spans that represent a dependency and are mapped to a transaction.
func TestRootSpanAsDependencyEnrich(t *testing.T) {
for _, tc := range []struct {
name string
input ptrace.Span
config config.Config
enrichedAttrs map[string]any
expectedSpanLinks *ptrace.SpanLinkSlice
}{
{
name: "outgoing_http_root_span",
input: func() ptrace.Span {
span := ptrace.NewSpan()
span.SetName("rootClientSpan")
span.SetSpanID([8]byte{1})
span.SetKind(ptrace.SpanKindClient)
span.Attributes().PutStr(semconv27.AttributeHTTPRequestMethod, "GET")
span.Attributes().PutStr(semconv27.AttributeURLFull, "http://localhost:8080")
span.Attributes().PutInt(semconv27.AttributeHTTPResponseStatusCode, 200)
span.Attributes().PutStr(semconv27.AttributeNetworkProtocolVersion, "1.1")
return span
}(),
config: config.Enabled(),
enrichedAttrs: map[string]any{
elasticattr.TimestampUs: int64(0),
elasticattr.TransactionName: "rootClientSpan",
elasticattr.ProcessorEvent: "transaction",
elasticattr.SpanType: "external",
elasticattr.SpanSubtype: "http",
elasticattr.SpanDestinationServiceResource: "localhost:8080",
elasticattr.SpanName: "rootClientSpan",
elasticattr.EventOutcome: "success",
elasticattr.SuccessCount: int64(1),
elasticattr.ServiceTargetName: "localhost:8080",
elasticattr.ServiceTargetType: "http",
elasticattr.TransactionID: "0100000000000000",
elasticattr.TransactionDurationUs: int64(0),
elasticattr.TransactionRepresentativeCount: float64(1),
elasticattr.TransactionResult: "HTTP 2xx",
elasticattr.TransactionType: "external.http",
elasticattr.TransactionSampled: true,
elasticattr.TransactionRoot: true,
elasticattr.SpanDurationUs: int64(0),
elasticattr.SpanRepresentativeCount: float64(1),
},
},
{
name: "db_root_span",
input: func() ptrace.Span {
span := ptrace.NewSpan()
span.SetName("rootClientSpan")
span.SetSpanID([8]byte{1})
span.SetKind(ptrace.SpanKindClient)
span.Attributes().PutStr(semconv25.AttributeDBSystem, "mssql")

span.Attributes().PutStr(semconv25.AttributeDBName, "myDb")
span.Attributes().PutStr(semconv25.AttributeDBOperation, "SELECT")
span.Attributes().PutStr(semconv25.AttributeDBStatement, "SELECT * FROM wuser_table")
return span
}(),
config: config.Enabled(),
enrichedAttrs: map[string]any{
elasticattr.TimestampUs: int64(0),
elasticattr.TransactionName: "rootClientSpan",
elasticattr.ProcessorEvent: "transaction",
elasticattr.SpanType: "db",
elasticattr.SpanSubtype: "mssql",
elasticattr.SpanDestinationServiceResource: "mssql",
elasticattr.SpanName: "rootClientSpan",
elasticattr.EventOutcome: "success",
elasticattr.SuccessCount: int64(1),
elasticattr.ServiceTargetName: "myDb",
elasticattr.ServiceTargetType: "mssql",
elasticattr.TransactionID: "0100000000000000",
elasticattr.TransactionDurationUs: int64(0),
elasticattr.TransactionRepresentativeCount: float64(1),
elasticattr.TransactionResult: "Success",
elasticattr.TransactionType: "db.mssql",
elasticattr.TransactionSampled: true,
elasticattr.TransactionRoot: true,
elasticattr.SpanDurationUs: int64(0),
elasticattr.SpanRepresentativeCount: float64(1),
},
},
{
name: "producer_messaging_span",
input: func() ptrace.Span {
span := ptrace.NewSpan()
span.SetName("rootClientSpan")
span.SetSpanID([8]byte{1})
span.SetKind(ptrace.SpanKindProducer)

span.Attributes().PutStr(semconv25.AttributeServerAddress, "myServer")
span.Attributes().PutStr(semconv25.AttributeServerPort, "1234")
span.Attributes().PutStr(semconv25.AttributeMessagingSystem, "rabbitmq")
span.Attributes().PutStr(semconv25.AttributeMessagingDestinationName, "T")
span.Attributes().PutStr(semconv25.AttributeMessagingOperation, "publish")
span.Attributes().PutStr(semconv25.AttributeMessagingClientID, "a")
return span
}(),
config: config.Enabled(),
enrichedAttrs: map[string]any{
elasticattr.TimestampUs: int64(0),
elasticattr.TransactionName: "rootClientSpan",
elasticattr.ProcessorEvent: "transaction",
elasticattr.SpanType: "messaging",
elasticattr.SpanSubtype: "rabbitmq",
elasticattr.SpanDestinationServiceResource: "rabbitmq/T",
elasticattr.SpanName: "rootClientSpan",
elasticattr.EventOutcome: "success",
elasticattr.SuccessCount: int64(1),
elasticattr.ServiceTargetName: "T",
elasticattr.ServiceTargetType: "rabbitmq",
elasticattr.TransactionID: "0100000000000000",
elasticattr.TransactionDurationUs: int64(0),
elasticattr.TransactionRepresentativeCount: float64(1),
elasticattr.TransactionResult: "Success",
elasticattr.TransactionType: "messaging.rabbitmq",
elasticattr.TransactionSampled: true,
elasticattr.TransactionRoot: true,
elasticattr.SpanDurationUs: int64(0),
elasticattr.SpanRepresentativeCount: float64(1),
},
},
} {
t.Run(tc.name, func(t *testing.T) {
expectedSpan := ptrace.NewSpan()
tc.input.CopyTo(expectedSpan)

// Merge with the expected attributes and override the span links.
for k, v := range tc.enrichedAttrs {
expectedSpan.Attributes().PutEmpty(k).FromRaw(v)
}
// Override span links
if tc.expectedSpanLinks != nil {
tc.expectedSpanLinks.CopyTo(expectedSpan.Links())
} else {
expectedSpan.Links().RemoveIf(func(_ ptrace.SpanLink) bool { return true })
}

EnrichSpan(tc.input, tc.config)
assert.NoError(t, ptracetest.CompareSpan(expectedSpan, tc.input))
})
}
}

// Tests the enrichment logic for elastic's span definition.
func TestElasticSpanEnrich(t *testing.T) {
now := time.Unix(3600, 0)
Expand Down