Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enrich root spans that represent a dependency #125

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
49 changes: 34 additions & 15 deletions enrichments/trace/internal/elastic/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,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)


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

Expand Down Expand Up @@ -239,39 +246,51 @@ func (s *spanEnrichmentContext) enrichTransaction(

func (s *spanEnrichmentContext) enrichSpan(
span ptrace.Span,
cfg config.ElasticSpanConfig,
cfg config.Config,
isExitRootSpan bool,
) {
if cfg.TimestampUs.Enabled {
if cfg.Span.TimestampUs.Enabled {
span.Attributes().PutInt(AttributeTimestampUs, getTimestampUs(span.StartTimestamp()))
}
if cfg.Name.Enabled {
if cfg.Span.Name.Enabled {
span.Attributes().PutStr(AttributeSpanName, span.Name())
}
if cfg.ProcessorEvent.Enabled {
span.Attributes().PutStr(AttributeProcessorEvent, "span")
}
if cfg.RepresentativeCount.Enabled {
if cfg.Span.RepresentativeCount.Enabled {
repCount := getRepresentativeCount(span.TraceState().AsRaw())
span.Attributes().PutDouble(AttributeSpanRepresentativeCount, repCount)
}
if cfg.TypeSubtype.Enabled {
if cfg.Span.TypeSubtype.Enabled {
s.setSpanTypeSubtype(span)
}
if cfg.EventOutcome.Enabled {
if cfg.Span.EventOutcome.Enabled {
s.setEventOutcome(span)
}
if cfg.DurationUs.Enabled {
if cfg.Span.DurationUs.Enabled {
span.Attributes().PutInt(AttributeSpanDurationUs, getDurationUs(span))
}
if cfg.ServiceTarget.Enabled {
if cfg.Span.ServiceTarget.Enabled {
s.setServiceTarget(span)
}
if cfg.DestinationService.Enabled {
if cfg.Span.DestinationService.Enabled {
s.setDestinationService(span)
}
if cfg.InferredSpans.Enabled {
if cfg.Span.InferredSpans.Enabled {
s.setInferredSpans(span)
}
if cfg.Span.ProcessorEvent.Enabled && !isExitRootSpan {
span.Attributes().PutStr(AttributeProcessorEvent, "span")
}

if isExitRootSpan && cfg.Transaction.Type.Enabled {
spanTypeAttr, hasType := span.Attributes().Get(AttributeSpanType)
if hasType {
transactionType := spanTypeAttr.Str()
if spanSubtypeAttr, hasSubType := span.Attributes().Get(AttributeSpanSubtype); hasSubType {
transactionType += "." + spanSubtypeAttr.Str()
}
span.Attributes().PutStr(AttributeTransactionType, transactionType)
}
}
}

// normalizeAttributes sets any dependent attributes that
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 @@ -386,6 +386,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(semconv.AttributeHTTPMethod, "GET")
span.Attributes().PutStr(semconv.AttributeHTTPURL, "http://localhost:8080")
span.Attributes().PutInt(semconv.AttributeHTTPResponseStatusCode, 200)
span.Attributes().PutStr(semconv.AttributeNetworkProtocolVersion, "1.1")
return span
}(),
config: config.Enabled(),
enrichedAttrs: map[string]any{
AttributeTimestampUs: int64(0),
AttributeTransactionName: "rootClientSpan",
AttributeProcessorEvent: "transaction",
AttributeSpanType: "external",
AttributeSpanSubtype: "http",
AttributeSpanDestinationServiceResource: "localhost:8080",
AttributeSpanName: "rootClientSpan",
AttributeEventOutcome: "success",
AttributeSuccessCount: int64(1),
AttributeServiceTargetName: "localhost:8080",
AttributeServiceTargetType: "http",
AttributeTransactionID: "0100000000000000",
AttributeTransactionDurationUs: int64(0),
AttributeTransactionRepresentativeCount: float64(1),
AttributeTransactionResult: "HTTP 2xx",
AttributeTransactionType: "external.http",
AttributeTransactionSampled: true,
AttributeTransactionRoot: true,
AttributeSpanDurationUs: int64(0),
AttributeSpanRepresentativeCount: 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(semconv.AttributeDBSystem, "mssql")

span.Attributes().PutStr(semconv.AttributeDBName, "myDb")
span.Attributes().PutStr(semconv.AttributeDBOperation, "SELECT")
span.Attributes().PutStr(semconv.AttributeDBStatement, "SELECT * FROM wuser_table")
return span
}(),
config: config.Enabled(),
enrichedAttrs: map[string]any{
AttributeTimestampUs: int64(0),
AttributeTransactionName: "rootClientSpan",
AttributeProcessorEvent: "transaction",
AttributeSpanType: "db",
AttributeSpanSubtype: "mssql",
AttributeSpanDestinationServiceResource: "mssql",
AttributeSpanName: "rootClientSpan",
AttributeEventOutcome: "success",
AttributeSuccessCount: int64(1),
AttributeServiceTargetName: "myDb",
AttributeServiceTargetType: "mssql",
AttributeTransactionID: "0100000000000000",
AttributeTransactionDurationUs: int64(0),
AttributeTransactionRepresentativeCount: float64(1),
AttributeTransactionResult: "Success",
AttributeTransactionType: "db.mssql",
AttributeTransactionSampled: true,
AttributeTransactionRoot: true,
AttributeSpanDurationUs: int64(0),
AttributeSpanRepresentativeCount: 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(semconv.AttributeServerAddress, "myServer")
span.Attributes().PutStr(semconv.AttributeServerPort, "1234")
span.Attributes().PutStr(semconv.AttributeMessagingSystem, "rabbitmq")
span.Attributes().PutStr(semconv.AttributeMessagingDestinationName, "T")
span.Attributes().PutStr(semconv.AttributeMessagingOperation, "publish")
span.Attributes().PutStr(semconv.AttributeMessagingClientID, "a")
return span
}(),
config: config.Enabled(),
enrichedAttrs: map[string]any{
AttributeTimestampUs: int64(0),
AttributeTransactionName: "rootClientSpan",
AttributeProcessorEvent: "transaction",
AttributeSpanType: "messaging",
AttributeSpanSubtype: "rabbitmq",
AttributeSpanDestinationServiceResource: "rabbitmq/T",
AttributeSpanName: "rootClientSpan",
AttributeEventOutcome: "success",
AttributeSuccessCount: int64(1),
AttributeServiceTargetName: "T",
AttributeServiceTargetType: "rabbitmq",
AttributeTransactionID: "0100000000000000",
AttributeTransactionDurationUs: int64(0),
AttributeTransactionRepresentativeCount: float64(1),
AttributeTransactionResult: "Success",
AttributeTransactionType: "messaging.rabbitmq",
AttributeTransactionSampled: true,
AttributeTransactionRoot: true,
AttributeSpanDurationUs: int64(0),
AttributeSpanRepresentativeCount: 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