From 26a9fe2b2b16f150e377fe59fd8d29eaa71a2535 Mon Sep 17 00:00:00 2001 From: Vishal Raj Date: Mon, 23 Sep 2024 19:17:08 +0100 Subject: [PATCH 1/6] [enrichment/trace] Handle exception for span events --- enrichments/trace/config/config.go | 24 +++++- .../trace/internal/elastic/attributes.go | 11 +++ enrichments/trace/internal/elastic/span.go | 82 ++++++++++++++++++- .../trace/internal/elastic/span_test.go | 38 +++++++-- 4 files changed, 146 insertions(+), 9 deletions(-) diff --git a/enrichments/trace/config/config.go b/enrichments/trace/config/config.go index 1eb7bf5..0fc4813 100644 --- a/enrichments/trace/config/config.go +++ b/enrichments/trace/config/config.go @@ -81,6 +81,18 @@ type SpanEventConfig struct { // https://github.com/elastic/opentelemetry-dev/issues/374. TimestampUs AttributeConfig `mapstructure:"timestamp_us"` ProcessorEvent AttributeConfig `mapstructure:"processor_event"` + + // For exceptions/errors + ErrorID AttributeConfig `mapstructure:"error_id"` + ErrorExceptionType AttributeConfig `mapstructure:"error_exception_type"` + ErrorExceptionMessage AttributeConfig `mapstructure:"error_exception_message"` + ErrorExceptionHandled AttributeConfig `mapstructure:"error_exception_handled"` + ErrorStacktrace AttributeConfig `mapstructure:"error_exception_stacktrace"` + ErrorGroupingKey AttributeConfig `mapstructure:"error_grouping_key"` + + // For no exceptions/errors + Message AttributeConfig `mapstructure:"message"` + EventKind AttributeConfig `mapstructure:"event_kind"` } // AttributeConfig is the configuration options for each attribute. @@ -124,8 +136,16 @@ func Enabled() Config { RepresentativeCount: AttributeConfig{Enabled: true}, }, SpanEvent: SpanEventConfig{ - TimestampUs: AttributeConfig{Enabled: true}, - ProcessorEvent: AttributeConfig{Enabled: true}, + TimestampUs: AttributeConfig{Enabled: true}, + ProcessorEvent: AttributeConfig{Enabled: true}, + ErrorID: AttributeConfig{Enabled: true}, + ErrorExceptionType: AttributeConfig{Enabled: true}, + ErrorExceptionMessage: AttributeConfig{Enabled: true}, + ErrorExceptionHandled: AttributeConfig{Enabled: true}, + ErrorStacktrace: AttributeConfig{Enabled: true}, + ErrorGroupingKey: AttributeConfig{Enabled: true}, + Message: AttributeConfig{Enabled: true}, + EventKind: AttributeConfig{Enabled: true}, }, } } diff --git a/enrichments/trace/internal/elastic/attributes.go b/enrichments/trace/internal/elastic/attributes.go index a58a8b7..6dc499d 100644 --- a/enrichments/trace/internal/elastic/attributes.go +++ b/enrichments/trace/internal/elastic/attributes.go @@ -47,4 +47,15 @@ const ( AttributeSpanDestinationServiceResource = "span.destination.service.resource" AttributeSpanDurationUs = "span.duration.us" AttributeSpanRepresentativeCount = "span.representative_count" + + // span event attributes + AttributeParentID = "parent.id" + AttributeErrorID = "error.id" + AttributeErrorExceptionType = "error.exception.type" + AttributeErrorExceptionMessage = "error.exception.message" + AttributeErrorExceptionHandled = "error.exception.handled" + AttributeErrorStacktrace = "error.stack_trace" + AttributeErrorGroupingKey = "error.grouping_key" + AttributeEventKind = "event.kind" + AttributeMessage = "message" ) diff --git a/enrichments/trace/internal/elastic/span.go b/enrichments/trace/internal/elastic/span.go index f4050e7..202264b 100644 --- a/enrichments/trace/internal/elastic/span.go +++ b/enrichments/trace/internal/elastic/span.go @@ -18,7 +18,11 @@ package elastic import ( + "crypto/md5" + "crypto/rand" + "encoding/hex" "fmt" + "io" "math" "net" "net/http" @@ -152,7 +156,8 @@ func (s *spanEnrichmentContext) Enrich(span ptrace.Span, cfg config.Config) { // Ensure all dependent attributes are handled. s.normalizeAttributes() - if isElasticTransaction(span) { + isElasticTxn := isElasticTransaction(span) + if isElasticTxn { s.enrichTransaction(span, cfg.Transaction) } else { s.enrichSpan(span, cfg.Span) @@ -417,15 +422,38 @@ func (s *spanEnrichmentContext) setDestinationService(span ptrace.Span) { } type spanEventEnrichmentContext struct { - exception bool + exception bool + exceptionEscaped bool + + exceptionType string + exceptionMessage string + exceptionStacktrace string } func (s *spanEventEnrichmentContext) enrich( se ptrace.SpanEvent, cfg config.SpanEventConfig, ) { + // TODO(lahsivjar): Is error/log context handling be done in es-exporter? + // Ref: https://github.com/elastic/apm-data/blob/59d33b8113d629f43dc82f9b3931ceda63a19a7a/input/otlp/traces.go#L1243-L1260 + // Extract top level span event information. s.exception = se.Name() == "exception" + if s.exception { + se.Attributes().Range(func(k string, v pcommon.Value) bool { + switch k { + case semconv.AttributeExceptionEscaped: + s.exceptionEscaped = v.Bool() + case semconv.AttributeExceptionType: + s.exceptionType = v.Str() + case semconv.AttributeExceptionMessage: + s.exceptionMessage = v.Str() + case semconv.AttributeExceptionStacktrace: + s.exceptionStacktrace = v.Str() + } + return true + }) + } // Enrich span event attributes. if cfg.TimestampUs.Enabled { @@ -434,6 +462,43 @@ func (s *spanEventEnrichmentContext) enrich( if cfg.ProcessorEvent.Enabled && s.exception { se.Attributes().PutStr(AttributeProcessorEvent, "error") } + if s.exceptionType == "" && s.exceptionMessage == "" { + // Span event does not represent an exception + se.Attributes().PutStr(AttributeEventKind, "event") + se.Attributes().PutStr(AttributeMessage, se.Name()) + return + } + + // Span event represents exception + if cfg.ErrorID.Enabled { + if id, err := newUniqueID(); err == nil { + se.Attributes().PutStr(AttributeErrorID, id) + } + } + if cfg.ErrorExceptionType.Enabled && s.exceptionType != "" { + se.Attributes().PutStr(AttributeErrorExceptionType, s.exceptionType) + } + if cfg.ErrorExceptionMessage.Enabled { + se.Attributes().PutStr(AttributeErrorExceptionMessage, s.exceptionMessage) + } + if cfg.ErrorExceptionHandled.Enabled { + se.Attributes().PutBool(AttributeErrorExceptionHandled, !s.exceptionEscaped) + } + if cfg.ErrorStacktrace.Enabled { + // TODO (lahsivjar): Where to parse stacktraces? + se.Attributes().PutStr(AttributeErrorStacktrace, s.exceptionStacktrace) + } + if cfg.ErrorGroupingKey.Enabled { + // See https://github.com/elastic/apm-data/issues/299 + hash := md5.New() + // ignoring errors in hashing + if s.exceptionType != "" { + io.WriteString(hash, s.exceptionType) + } else if s.exceptionMessage != "" { + io.WriteString(hash, s.exceptionMessage) + } + se.Attributes().PutStr(AttributeErrorGroupingKey, hex.EncodeToString(hash.Sum(nil))) + } } // getRepresentativeCount returns the number of spans represented by an @@ -540,3 +605,16 @@ var standardStatusCodeResults = [...]string{ "HTTP 4xx", "HTTP 5xx", } + +func newUniqueID() (string, error) { + var u [16]byte + if _, err := io.ReadFull(rand.Reader, u[:]); err != nil { + return "", err + } + + // convert to string + buf := make([]byte, 32) + hex.Encode(buf, u[:]) + + return string(buf), nil +} diff --git a/enrichments/trace/internal/elastic/span_test.go b/enrichments/trace/internal/elastic/span_test.go index 110b606..250073d 100644 --- a/enrichments/trace/internal/elastic/span_test.go +++ b/enrichments/trace/internal/elastic/span_test.go @@ -18,6 +18,8 @@ package elastic import ( + "crypto/md5" + "encoding/hex" "net/http" "testing" "time" @@ -764,18 +766,23 @@ func TestSpanEventEnrich(t *testing.T) { name string input ptrace.SpanEvent config config.SpanEventConfig + errorID bool // indicates if the error ID should be present in the result enrichedAttrs map[string]any }{ { name: "not_exception", input: func() ptrace.SpanEvent { event := ptrace.NewSpanEvent() + event.SetName("not_exception") event.SetTimestamp(ts) return event }(), - config: config.Enabled().SpanEvent, + config: config.Enabled().SpanEvent, + errorID: false, // error ID is only present for exceptions enrichedAttrs: map[string]any{ AttributeTimestampUs: ts.AsTime().UnixMicro(), + AttributeEventKind: "event", + AttributeMessage: "not_exception", }, }, { @@ -784,12 +791,25 @@ func TestSpanEventEnrich(t *testing.T) { event := ptrace.NewSpanEvent() event.SetName("exception") event.SetTimestamp(ts) + event.Attributes().PutStr(semconv.AttributeExceptionType, "java.net.ConnectionError") + event.Attributes().PutStr(semconv.AttributeExceptionMessage, "something is wrong") + event.Attributes().PutStr(semconv.AttributeExceptionStacktrace, `Exception in thread "main" java.lang.RuntimeException: Test exception\\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\\n at com.example.GenerateTrace.main(GenerateTrace.java:5)`) return event }(), - config: config.Enabled().SpanEvent, + config: config.Enabled().SpanEvent, + errorID: true, enrichedAttrs: map[string]any{ - AttributeTimestampUs: ts.AsTime().UnixMicro(), - AttributeProcessorEvent: "error", + AttributeTimestampUs: ts.AsTime().UnixMicro(), + AttributeProcessorEvent: "error", + AttributeErrorExceptionHandled: true, + AttributeErrorExceptionType: "java.net.ConnectionError", + AttributeErrorExceptionMessage: "something is wrong", + AttributeErrorStacktrace: `Exception in thread "main" java.lang.RuntimeException: Test exception\\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\\n at com.example.GenerateTrace.main(GenerateTrace.java:5)`, + AttributeErrorGroupingKey: func() string { + hash := md5.New() + hash.Write([]byte("java.net.ConnectionError")) + return hex.EncodeToString(hash.Sum(nil)) + }(), }, }, } { @@ -807,7 +827,15 @@ func TestSpanEventEnrich(t *testing.T) { SpanEvent: tc.config, }) - assert.Empty(t, cmp.Diff(expectedAttrs, span.Events().At(0).Attributes().AsRaw())) + actual := span.Events().At(0).Attributes() + errorID, ok := actual.Get(AttributeErrorID) + assert.Equal(t, tc.errorID, ok, "error_id must be present for exception and must not be present for non-exception") + if tc.errorID { + assert.NotEmpty(t, errorID, "error_id must not be empty") + } + // Ignore error in actual diff since it is randomly generated + actual.Remove(AttributeErrorID) + assert.Empty(t, cmp.Diff(expectedAttrs, actual.AsRaw())) }) } } From 3883a1490072a99ac17ffd90b76d5d03157af10c Mon Sep 17 00:00:00 2001 From: Vishal Raj Date: Mon, 23 Sep 2024 19:41:00 +0100 Subject: [PATCH 2/6] Fix struct padding --- enrichments/trace/internal/elastic/span.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/enrichments/trace/internal/elastic/span.go b/enrichments/trace/internal/elastic/span.go index 202264b..8687610 100644 --- a/enrichments/trace/internal/elastic/span.go +++ b/enrichments/trace/internal/elastic/span.go @@ -422,12 +422,12 @@ func (s *spanEnrichmentContext) setDestinationService(span ptrace.Span) { } type spanEventEnrichmentContext struct { - exception bool - exceptionEscaped bool - exceptionType string exceptionMessage string exceptionStacktrace string + + exception bool + exceptionEscaped bool } func (s *spanEventEnrichmentContext) enrich( From 4361255189c752b09bf2bed60016060031fdf858 Mon Sep 17 00:00:00 2001 From: Vishal Raj Date: Fri, 27 Sep 2024 13:00:32 +0100 Subject: [PATCH 3/6] Remove exception.{type,message} and stacktrace from enrichment --- enrichments/trace/config/config.go | 8 -------- enrichments/trace/internal/elastic/attributes.go | 4 ---- enrichments/trace/internal/elastic/span.go | 11 ----------- enrichments/trace/internal/elastic/span_test.go | 5 ----- 4 files changed, 28 deletions(-) diff --git a/enrichments/trace/config/config.go b/enrichments/trace/config/config.go index 0fc4813..5f5f04c 100644 --- a/enrichments/trace/config/config.go +++ b/enrichments/trace/config/config.go @@ -84,14 +84,10 @@ type SpanEventConfig struct { // For exceptions/errors ErrorID AttributeConfig `mapstructure:"error_id"` - ErrorExceptionType AttributeConfig `mapstructure:"error_exception_type"` - ErrorExceptionMessage AttributeConfig `mapstructure:"error_exception_message"` ErrorExceptionHandled AttributeConfig `mapstructure:"error_exception_handled"` - ErrorStacktrace AttributeConfig `mapstructure:"error_exception_stacktrace"` ErrorGroupingKey AttributeConfig `mapstructure:"error_grouping_key"` // For no exceptions/errors - Message AttributeConfig `mapstructure:"message"` EventKind AttributeConfig `mapstructure:"event_kind"` } @@ -139,12 +135,8 @@ func Enabled() Config { TimestampUs: AttributeConfig{Enabled: true}, ProcessorEvent: AttributeConfig{Enabled: true}, ErrorID: AttributeConfig{Enabled: true}, - ErrorExceptionType: AttributeConfig{Enabled: true}, - ErrorExceptionMessage: AttributeConfig{Enabled: true}, ErrorExceptionHandled: AttributeConfig{Enabled: true}, - ErrorStacktrace: AttributeConfig{Enabled: true}, ErrorGroupingKey: AttributeConfig{Enabled: true}, - Message: AttributeConfig{Enabled: true}, EventKind: AttributeConfig{Enabled: true}, }, } diff --git a/enrichments/trace/internal/elastic/attributes.go b/enrichments/trace/internal/elastic/attributes.go index 6dc499d..5e96fc8 100644 --- a/enrichments/trace/internal/elastic/attributes.go +++ b/enrichments/trace/internal/elastic/attributes.go @@ -51,11 +51,7 @@ const ( // span event attributes AttributeParentID = "parent.id" AttributeErrorID = "error.id" - AttributeErrorExceptionType = "error.exception.type" - AttributeErrorExceptionMessage = "error.exception.message" AttributeErrorExceptionHandled = "error.exception.handled" - AttributeErrorStacktrace = "error.stack_trace" AttributeErrorGroupingKey = "error.grouping_key" AttributeEventKind = "event.kind" - AttributeMessage = "message" ) diff --git a/enrichments/trace/internal/elastic/span.go b/enrichments/trace/internal/elastic/span.go index 8687610..103e0f8 100644 --- a/enrichments/trace/internal/elastic/span.go +++ b/enrichments/trace/internal/elastic/span.go @@ -465,7 +465,6 @@ func (s *spanEventEnrichmentContext) enrich( if s.exceptionType == "" && s.exceptionMessage == "" { // Span event does not represent an exception se.Attributes().PutStr(AttributeEventKind, "event") - se.Attributes().PutStr(AttributeMessage, se.Name()) return } @@ -475,19 +474,9 @@ func (s *spanEventEnrichmentContext) enrich( se.Attributes().PutStr(AttributeErrorID, id) } } - if cfg.ErrorExceptionType.Enabled && s.exceptionType != "" { - se.Attributes().PutStr(AttributeErrorExceptionType, s.exceptionType) - } - if cfg.ErrorExceptionMessage.Enabled { - se.Attributes().PutStr(AttributeErrorExceptionMessage, s.exceptionMessage) - } if cfg.ErrorExceptionHandled.Enabled { se.Attributes().PutBool(AttributeErrorExceptionHandled, !s.exceptionEscaped) } - if cfg.ErrorStacktrace.Enabled { - // TODO (lahsivjar): Where to parse stacktraces? - se.Attributes().PutStr(AttributeErrorStacktrace, s.exceptionStacktrace) - } if cfg.ErrorGroupingKey.Enabled { // See https://github.com/elastic/apm-data/issues/299 hash := md5.New() diff --git a/enrichments/trace/internal/elastic/span_test.go b/enrichments/trace/internal/elastic/span_test.go index 250073d..55aa794 100644 --- a/enrichments/trace/internal/elastic/span_test.go +++ b/enrichments/trace/internal/elastic/span_test.go @@ -773,7 +773,6 @@ func TestSpanEventEnrich(t *testing.T) { name: "not_exception", input: func() ptrace.SpanEvent { event := ptrace.NewSpanEvent() - event.SetName("not_exception") event.SetTimestamp(ts) return event }(), @@ -782,7 +781,6 @@ func TestSpanEventEnrich(t *testing.T) { enrichedAttrs: map[string]any{ AttributeTimestampUs: ts.AsTime().UnixMicro(), AttributeEventKind: "event", - AttributeMessage: "not_exception", }, }, { @@ -802,9 +800,6 @@ func TestSpanEventEnrich(t *testing.T) { AttributeTimestampUs: ts.AsTime().UnixMicro(), AttributeProcessorEvent: "error", AttributeErrorExceptionHandled: true, - AttributeErrorExceptionType: "java.net.ConnectionError", - AttributeErrorExceptionMessage: "something is wrong", - AttributeErrorStacktrace: `Exception in thread "main" java.lang.RuntimeException: Test exception\\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\\n at com.example.GenerateTrace.main(GenerateTrace.java:5)`, AttributeErrorGroupingKey: func() string { hash := md5.New() hash.Write([]byte("java.net.ConnectionError")) From 80c485f094e2200100c376e7436a5ff7c2f62e87 Mon Sep 17 00:00:00 2001 From: Vishal Raj Date: Fri, 27 Sep 2024 18:07:42 +0100 Subject: [PATCH 4/6] error/log context enrichment will be in es-exporter --- enrichments/trace/internal/elastic/span.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/enrichments/trace/internal/elastic/span.go b/enrichments/trace/internal/elastic/span.go index 103e0f8..195f251 100644 --- a/enrichments/trace/internal/elastic/span.go +++ b/enrichments/trace/internal/elastic/span.go @@ -434,9 +434,6 @@ func (s *spanEventEnrichmentContext) enrich( se ptrace.SpanEvent, cfg config.SpanEventConfig, ) { - // TODO(lahsivjar): Is error/log context handling be done in es-exporter? - // Ref: https://github.com/elastic/apm-data/blob/59d33b8113d629f43dc82f9b3931ceda63a19a7a/input/otlp/traces.go#L1243-L1260 - // Extract top level span event information. s.exception = se.Name() == "exception" if s.exception { From 82d649e66ff310c47f1cec5db652adef97be7a4d Mon Sep 17 00:00:00 2001 From: Vishal Raj Date: Fri, 27 Sep 2024 18:30:13 +0100 Subject: [PATCH 5/6] Remove span.kind enrichment --- enrichments/trace/internal/elastic/attributes.go | 1 - enrichments/trace/internal/elastic/span.go | 8 ++------ enrichments/trace/internal/elastic/span_test.go | 1 - 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/enrichments/trace/internal/elastic/attributes.go b/enrichments/trace/internal/elastic/attributes.go index 5e96fc8..b7466df 100644 --- a/enrichments/trace/internal/elastic/attributes.go +++ b/enrichments/trace/internal/elastic/attributes.go @@ -53,5 +53,4 @@ const ( AttributeErrorID = "error.id" AttributeErrorExceptionHandled = "error.exception.handled" AttributeErrorGroupingKey = "error.grouping_key" - AttributeEventKind = "event.kind" ) diff --git a/enrichments/trace/internal/elastic/span.go b/enrichments/trace/internal/elastic/span.go index 195f251..1dd8ecb 100644 --- a/enrichments/trace/internal/elastic/span.go +++ b/enrichments/trace/internal/elastic/span.go @@ -422,9 +422,8 @@ func (s *spanEnrichmentContext) setDestinationService(span ptrace.Span) { } type spanEventEnrichmentContext struct { - exceptionType string - exceptionMessage string - exceptionStacktrace string + exceptionType string + exceptionMessage string exception bool exceptionEscaped bool @@ -445,8 +444,6 @@ func (s *spanEventEnrichmentContext) enrich( s.exceptionType = v.Str() case semconv.AttributeExceptionMessage: s.exceptionMessage = v.Str() - case semconv.AttributeExceptionStacktrace: - s.exceptionStacktrace = v.Str() } return true }) @@ -461,7 +458,6 @@ func (s *spanEventEnrichmentContext) enrich( } if s.exceptionType == "" && s.exceptionMessage == "" { // Span event does not represent an exception - se.Attributes().PutStr(AttributeEventKind, "event") return } diff --git a/enrichments/trace/internal/elastic/span_test.go b/enrichments/trace/internal/elastic/span_test.go index 55aa794..00788a9 100644 --- a/enrichments/trace/internal/elastic/span_test.go +++ b/enrichments/trace/internal/elastic/span_test.go @@ -780,7 +780,6 @@ func TestSpanEventEnrich(t *testing.T) { errorID: false, // error ID is only present for exceptions enrichedAttrs: map[string]any{ AttributeTimestampUs: ts.AsTime().UnixMicro(), - AttributeEventKind: "event", }, }, { From 222386e688fef526a691c98c0eb0569569c9e255 Mon Sep 17 00:00:00 2001 From: Vishal Raj Date: Mon, 30 Sep 2024 23:57:17 +0100 Subject: [PATCH 6/6] Add transaction.{sampled, type} as span event attribute --- enrichments/trace/config/config.go | 8 ++- enrichments/trace/internal/elastic/span.go | 46 ++++++++++++----- .../trace/internal/elastic/span_test.go | 50 ++++++++++++++++--- 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/enrichments/trace/config/config.go b/enrichments/trace/config/config.go index 5f5f04c..96a78fd 100644 --- a/enrichments/trace/config/config.go +++ b/enrichments/trace/config/config.go @@ -79,8 +79,10 @@ type SpanEventConfig struct { // TimestampUs is a temporary attribute to enable higher // resolution timestamps in Elasticsearch. For more details see: // https://github.com/elastic/opentelemetry-dev/issues/374. - TimestampUs AttributeConfig `mapstructure:"timestamp_us"` - ProcessorEvent AttributeConfig `mapstructure:"processor_event"` + TimestampUs AttributeConfig `mapstructure:"timestamp_us"` + TransactionSampled AttributeConfig `mapstructure:"transaction_sampled"` + TransactionType AttributeConfig `mapstructure:"transaction_type"` + ProcessorEvent AttributeConfig `mapstructure:"processor_event"` // For exceptions/errors ErrorID AttributeConfig `mapstructure:"error_id"` @@ -133,6 +135,8 @@ func Enabled() Config { }, SpanEvent: SpanEventConfig{ TimestampUs: AttributeConfig{Enabled: true}, + TransactionSampled: AttributeConfig{Enabled: true}, + TransactionType: AttributeConfig{Enabled: true}, ProcessorEvent: AttributeConfig{Enabled: true}, ErrorID: AttributeConfig{Enabled: true}, ErrorExceptionHandled: AttributeConfig{Enabled: true}, diff --git a/enrichments/trace/internal/elastic/span.go b/enrichments/trace/internal/elastic/span.go index 1dd8ecb..cf17e5d 100644 --- a/enrichments/trace/internal/elastic/span.go +++ b/enrichments/trace/internal/elastic/span.go @@ -73,6 +73,8 @@ type spanEnrichmentContext struct { spanStatusCode ptrace.StatusCode + // TODO (lahsivjar): Refactor span enrichment to better utilize isTransaction + isTransaction bool isMessaging bool isRPC bool isHTTP bool @@ -153,20 +155,22 @@ func (s *spanEnrichmentContext) Enrich(span ptrace.Span, cfg config.Config) { return true }) - // Ensure all dependent attributes are handled. s.normalizeAttributes() - - isElasticTxn := isElasticTransaction(span) - if isElasticTxn { - s.enrichTransaction(span, cfg.Transaction) - } else { - s.enrichSpan(span, cfg.Span) - } + s.isTransaction = isElasticTransaction(span) + s.enrich(span, cfg) spanEvents := span.Events() for i := 0; i < spanEvents.Len(); i++ { var c spanEventEnrichmentContext - c.enrich(spanEvents.At(i), cfg.SpanEvent) + c.enrich(s, spanEvents.At(i), cfg.SpanEvent) + } +} + +func (s *spanEnrichmentContext) enrich(span ptrace.Span, cfg config.Config) { + if s.isTransaction { + s.enrichTransaction(span, cfg.Transaction) + } else { + s.enrichSpan(span, cfg.Span) } } @@ -178,7 +182,7 @@ func (s *spanEnrichmentContext) enrichTransaction( span.Attributes().PutInt(AttributeTimestampUs, getTimestampUs(span.StartTimestamp())) } if cfg.Sampled.Enabled { - span.Attributes().PutBool(AttributeTransactionSampled, true) + span.Attributes().PutBool(AttributeTransactionSampled, s.getSampled()) } if cfg.ID.Enabled { span.Attributes().PutStr(AttributeTransactionID, span.SpanID().String()) @@ -200,7 +204,7 @@ func (s *spanEnrichmentContext) enrichTransaction( span.Attributes().PutInt(AttributeTransactionDurationUs, getDurationUs(span)) } if cfg.Type.Enabled { - s.setTxnType(span) + span.Attributes().PutStr(AttributeTransactionType, s.getTxnType()) } if cfg.Result.Enabled { s.setTxnResult(span) @@ -252,7 +256,12 @@ func (s *spanEnrichmentContext) normalizeAttributes() { } } -func (s *spanEnrichmentContext) setTxnType(span ptrace.Span) { +func (s *spanEnrichmentContext) getSampled() bool { + // Assumes that the method is called only for transaction + return true +} + +func (s *spanEnrichmentContext) getTxnType() string { txnType := "unknown" switch { case s.isMessaging: @@ -260,7 +269,7 @@ func (s *spanEnrichmentContext) setTxnType(span ptrace.Span) { case s.isRPC, s.isHTTP: txnType = "request" } - span.Attributes().PutStr(AttributeTransactionType, txnType) + return txnType } func (s *spanEnrichmentContext) setTxnResult(span ptrace.Span) { @@ -430,6 +439,7 @@ type spanEventEnrichmentContext struct { } func (s *spanEventEnrichmentContext) enrich( + parentCtx *spanEnrichmentContext, se ptrace.SpanEvent, cfg config.SpanEventConfig, ) { @@ -481,6 +491,16 @@ func (s *spanEventEnrichmentContext) enrich( } se.Attributes().PutStr(AttributeErrorGroupingKey, hex.EncodeToString(hash.Sum(nil))) } + + // Transaction type and sampled are added as span event enrichment only for errors + if parentCtx.isTransaction && s.exception { + if cfg.TransactionSampled.Enabled { + se.Attributes().PutBool(AttributeTransactionSampled, parentCtx.getSampled()) + } + if cfg.TransactionType.Enabled { + se.Attributes().PutStr(AttributeTransactionType, parentCtx.getTxnType()) + } + } } // getRepresentativeCount returns the number of spans represented by an diff --git a/enrichments/trace/internal/elastic/span_test.go b/enrichments/trace/internal/elastic/span_test.go index 00788a9..258102e 100644 --- a/enrichments/trace/internal/elastic/span_test.go +++ b/enrichments/trace/internal/elastic/span_test.go @@ -764,13 +764,15 @@ func TestSpanEventEnrich(t *testing.T) { ts := pcommon.NewTimestampFromTime(now) for _, tc := range []struct { name string + parent ptrace.Span input ptrace.SpanEvent config config.SpanEventConfig errorID bool // indicates if the error ID should be present in the result enrichedAttrs map[string]any }{ { - name: "not_exception", + name: "not_exception", + parent: ptrace.NewSpan(), input: func() ptrace.SpanEvent { event := ptrace.NewSpanEvent() event.SetTimestamp(ts) @@ -783,7 +785,44 @@ func TestSpanEventEnrich(t *testing.T) { }, }, { - name: "exception", + name: "exception_with_elastic_txn", + parent: func() ptrace.Span { + // No parent, elastic txn + span := ptrace.NewSpan() + return span + }(), + input: func() ptrace.SpanEvent { + event := ptrace.NewSpanEvent() + event.SetName("exception") + event.SetTimestamp(ts) + event.Attributes().PutStr(semconv.AttributeExceptionType, "java.net.ConnectionError") + event.Attributes().PutStr(semconv.AttributeExceptionMessage, "something is wrong") + event.Attributes().PutStr(semconv.AttributeExceptionStacktrace, `Exception in thread "main" java.lang.RuntimeException: Test exception\\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\\n at com.example.GenerateTrace.main(GenerateTrace.java:5)`) + return event + }(), + config: config.Enabled().SpanEvent, + errorID: true, + enrichedAttrs: map[string]any{ + AttributeTimestampUs: ts.AsTime().UnixMicro(), + AttributeProcessorEvent: "error", + AttributeErrorExceptionHandled: true, + AttributeErrorGroupingKey: func() string { + hash := md5.New() + hash.Write([]byte("java.net.ConnectionError")) + return hex.EncodeToString(hash.Sum(nil)) + }(), + AttributeTransactionSampled: true, + AttributeTransactionType: "unknown", + }, + }, + { + name: "exception_with_elastic_span", + parent: func() ptrace.Span { + // Parent, elastic span + span := ptrace.NewSpan() + span.SetParentSpanID([8]byte{8, 9, 10, 11, 12, 13, 14}) + return span + }(), input: func() ptrace.SpanEvent { event := ptrace.NewSpanEvent() event.SetName("exception") @@ -815,13 +854,12 @@ func TestSpanEventEnrich(t *testing.T) { expectedAttrs[k] = v } - span := ptrace.NewSpan() - tc.input.MoveTo(span.Events().AppendEmpty()) - EnrichSpan(span, config.Config{ + tc.input.MoveTo(tc.parent.Events().AppendEmpty()) + EnrichSpan(tc.parent, config.Config{ SpanEvent: tc.config, }) - actual := span.Events().At(0).Attributes() + actual := tc.parent.Events().At(0).Attributes() errorID, ok := actual.Get(AttributeErrorID) assert.Equal(t, tc.errorID, ok, "error_id must be present for exception and must not be present for non-exception") if tc.errorID {