Skip to content
Open
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
34 changes: 27 additions & 7 deletions enrichments/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,21 @@ type ElasticTransactionConfig 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"`
Sampled AttributeConfig `mapstructure:"sampled"`
ID AttributeConfig `mapstructure:"id"`
Root AttributeConfig `mapstructure:"root"`
Name AttributeConfig `mapstructure:"name"`
TimestampUs AttributeConfig `mapstructure:"timestamp_us"`
Sampled AttributeConfig `mapstructure:"sampled"`
ID AttributeConfig `mapstructure:"id"`
// ClearSpanID sets the span ID to an empty value so that the
// ID is only represented by the `span.ID` attribute.
// Applicable only when ID is enabled.
// Disabled by default.
ClearSpanID AttributeConfig `mapstructure:"clear_span_id"`
Root AttributeConfig `mapstructure:"root"`
Name AttributeConfig `mapstructure:"name"`
// ClearSpanName sets the span name to an empty value so that the
// name is only represented by the `span.name` attribute.
// Applicable only when Name is enabled.
// Disabled by default.
ClearSpanName AttributeConfig `mapstructure:"clear_span_name"`
ProcessorEvent AttributeConfig `mapstructure:"processor_event"`
RepresentativeCount AttributeConfig `mapstructure:"representative_count"`
DurationUs AttributeConfig `mapstructure:"duration_us"`
Expand All @@ -72,8 +82,13 @@ type ElasticSpanConfig 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"`
ID AttributeConfig `mapstructure:"id"`
TimestampUs AttributeConfig `mapstructure:"timestamp_us"`
ID AttributeConfig `mapstructure:"id"`
// ClearSpanID sets the span ID to an empty value so that the
// ID is only represented by the `transaction.ID` attribute.
// Applicable only when ID is enabled.
// Disabled by default.
ClearSpanID AttributeConfig `mapstructure:"clear_span_id"`
Name AttributeConfig `mapstructure:"name"`
ProcessorEvent AttributeConfig `mapstructure:"processor_event"`
RepresentativeCount AttributeConfig `mapstructure:"representative_count"`
Expand All @@ -87,6 +102,11 @@ type ElasticSpanConfig struct {
UserAgent AttributeConfig `mapstructure:"user_agent"`
RemoveMessaging AttributeConfig `mapstructure:"remove_messaging"`
MessageQueueName AttributeConfig `mapstructure:"message_queue_name"`
// ClearSpanName sets the span name to an empty value so that the
// name is only represented by the `transaction.name` attribute.
// Applicable only when Name is enabled.
// Disabled by default.
ClearSpanName AttributeConfig `mapstructure:"clear_span_name"`
}

// SpanEventConfig configures enrichment attributes for the span events.
Expand Down
14 changes: 13 additions & 1 deletion enrichments/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,22 @@ func TestEnabled(t *testing.T) {
func assertAllEnabled(t *testing.T, cfg reflect.Value) {
t.Helper()

// Fields that are intentionally disabled by default
disabledByDefault := map[string]bool{
"ClearSpanID": true,
"ClearSpanName": true,
}

for i := 0; i < cfg.NumField(); i++ {
fieldName := cfg.Type().Field(i).Name
rAttrCfg := cfg.Field(i).Interface()
attrCfg, ok := rAttrCfg.(AttributeConfig)
require.True(t, ok, "must be a type of AttributeConfig")
require.True(t, attrCfg.Enabled, "must be enabled")

if disabledByDefault[fieldName] {
require.False(t, attrCfg.Enabled, "%s must be disabled by default", fieldName)
} else {
require.True(t, attrCfg.Enabled, "%s must be enabled", fieldName)
}
}
}
12 changes: 12 additions & 0 deletions enrichments/internal/elastic/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,18 @@ func (s *spanEnrichmentContext) enrichTransaction(
// https://github.com/elastic/apm-data/blob/v1.19.5/input/elasticapm/internal/modeldecoder/v2/decoder.go#L1377-L1379
// https://github.com/elastic/apm-data/blob/v1.19.5/input/otlp/traces.go#L185-L194
attribute.PutStr(span.Attributes(), elasticattr.SpanID, transactionID)
if cfg.ClearSpanID.Enabled {
span.SetSpanID(pcommon.SpanID{})
}
}
if cfg.Root.Enabled {
attribute.PutBool(span.Attributes(), elasticattr.TransactionRoot, isTraceRoot(span))
}
if cfg.Name.Enabled {
attribute.PutStr(span.Attributes(), elasticattr.TransactionName, span.Name())
if cfg.ClearSpanName.Enabled {
span.SetName("")
}
}
if cfg.ProcessorEvent.Enabled {
attribute.PutStr(span.Attributes(), elasticattr.ProcessorEvent, "transaction")
Expand Down Expand Up @@ -317,9 +323,15 @@ func (s *spanEnrichmentContext) enrichSpan(
}
if cfg.ID.Enabled {
attribute.PutStr(span.Attributes(), elasticattr.SpanID, span.SpanID().String())
if cfg.ClearSpanID.Enabled {
span.SetSpanID(pcommon.SpanID{})
}
}
if cfg.Name.Enabled {
attribute.PutStr(span.Attributes(), elasticattr.SpanName, span.Name())
if cfg.ClearSpanName.Enabled {
span.SetName("")
}
}
if cfg.RepresentativeCount.Enabled {
repCount := getRepresentativeCount(span.TraceState().AsRaw())
Expand Down
177 changes: 177 additions & 0 deletions enrichments/internal/elastic/span_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,91 @@ func TestElasticTransactionEnrich(t *testing.T) {
elasticattr.TransactionType: "mobile",
},
},
{
name: "clear_span_id_enabled",
input: func() ptrace.Span {
span := getElasticTxn()
span.SetName("testtxn")
return span
}(),
config: func() config.ElasticTransactionConfig {
cfg := config.Enabled().Transaction
cfg.ClearSpanID = config.AttributeConfig{Enabled: true}
return cfg
}(),
enrichedAttrs: map[string]any{
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
elasticattr.TransactionSampled: true,
elasticattr.TransactionRoot: true,
elasticattr.TransactionID: "0100000000000000",
elasticattr.SpanID: "0100000000000000",
elasticattr.TransactionName: "testtxn",
elasticattr.ProcessorEvent: "transaction",
elasticattr.TransactionRepresentativeCount: float64(1),
elasticattr.TransactionDurationUs: expectedDuration.Microseconds(),
elasticattr.EventOutcome: "success",
elasticattr.SuccessCount: int64(1),
elasticattr.TransactionResult: "Success",
elasticattr.TransactionType: "unknown",
},
},
{
name: "clear_span_name_enabled",
input: func() ptrace.Span {
span := getElasticTxn()
span.SetName("testtxn")
return span
}(),
config: func() config.ElasticTransactionConfig {
cfg := config.Enabled().Transaction
cfg.ClearSpanName = config.AttributeConfig{Enabled: true}
return cfg
}(),
enrichedAttrs: map[string]any{
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
elasticattr.TransactionSampled: true,
elasticattr.TransactionRoot: true,
elasticattr.TransactionID: "0100000000000000",
elasticattr.SpanID: "0100000000000000",
elasticattr.TransactionName: "testtxn",
elasticattr.ProcessorEvent: "transaction",
elasticattr.TransactionRepresentativeCount: float64(1),
elasticattr.TransactionDurationUs: expectedDuration.Microseconds(),
elasticattr.EventOutcome: "success",
elasticattr.SuccessCount: int64(1),
elasticattr.TransactionResult: "Success",
elasticattr.TransactionType: "unknown",
},
},
{
name: "clear_span_id_and_name_enabled",
input: func() ptrace.Span {
span := getElasticTxn()
span.SetName("testtxn")
return span
}(),
config: func() config.ElasticTransactionConfig {
cfg := config.Enabled().Transaction
cfg.ClearSpanID = config.AttributeConfig{Enabled: true}
cfg.ClearSpanName = config.AttributeConfig{Enabled: true}
return cfg
}(),
enrichedAttrs: map[string]any{
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
elasticattr.TransactionSampled: true,
elasticattr.TransactionRoot: true,
elasticattr.TransactionID: "0100000000000000",
elasticattr.SpanID: "0100000000000000",
elasticattr.TransactionName: "testtxn",
elasticattr.ProcessorEvent: "transaction",
elasticattr.TransactionRepresentativeCount: float64(1),
elasticattr.TransactionDurationUs: expectedDuration.Microseconds(),
elasticattr.EventOutcome: "success",
elasticattr.SuccessCount: int64(1),
elasticattr.TransactionResult: "Success",
elasticattr.TransactionType: "unknown",
},
},
} {
t.Run(tc.name, func(t *testing.T) {
expectedSpan := ptrace.NewSpan()
Expand All @@ -697,6 +782,14 @@ func TestElasticTransactionEnrich(t *testing.T) {
} else {
expectedSpan.Links().RemoveIf(func(_ ptrace.SpanLink) bool { return true })
}
// Clear span ID if ID and ClearSpanID are both enabled
if tc.config.ID.Enabled && tc.config.ClearSpanID.Enabled {
expectedSpan.SetSpanID(pcommon.SpanID{})
}
// Clear span name if Name and ClearSpanName are both enabled
if tc.config.Name.Enabled && tc.config.ClearSpanName.Enabled {
expectedSpan.SetName("")
}

EnrichSpan(tc.input, config.Config{
Transaction: tc.config,
Expand Down Expand Up @@ -2030,6 +2123,82 @@ func TestElasticSpanEnrich(t *testing.T) {
elasticattr.SuccessCount: int64(99),
},
},
{
name: "clear_span_id_enabled",
input: func() ptrace.Span {
span := getElasticSpan()
span.SetName("testspan")
span.SetSpanID([8]byte{1})
return span
}(),
config: func() config.ElasticSpanConfig {
cfg := config.Enabled().Span
cfg.ClearSpanID = config.AttributeConfig{Enabled: true}
return cfg
}(),
enrichedAttrs: map[string]any{
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
elasticattr.SpanName: "testspan",
elasticattr.ProcessorEvent: "span",
elasticattr.SpanRepresentativeCount: float64(1),
elasticattr.SpanType: "unknown",
elasticattr.SpanDurationUs: expectedDuration.Microseconds(),
elasticattr.EventOutcome: "success",
elasticattr.SuccessCount: int64(1),
elasticattr.SpanID: "0100000000000000",
},
},
{
name: "clear_span_name_enabled",
input: func() ptrace.Span {
span := getElasticSpan()
span.SetName("testspan")
span.SetSpanID([8]byte{1})
return span
}(),
config: func() config.ElasticSpanConfig {
cfg := config.Enabled().Span
cfg.ClearSpanName = config.AttributeConfig{Enabled: true}
return cfg
}(),
enrichedAttrs: map[string]any{
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
elasticattr.SpanName: "testspan",
elasticattr.ProcessorEvent: "span",
elasticattr.SpanRepresentativeCount: float64(1),
elasticattr.SpanType: "unknown",
elasticattr.SpanDurationUs: expectedDuration.Microseconds(),
elasticattr.EventOutcome: "success",
elasticattr.SuccessCount: int64(1),
elasticattr.SpanID: "0100000000000000",
},
},
{
name: "clear_span_id_and_name_enabled",
input: func() ptrace.Span {
span := getElasticSpan()
span.SetName("testspan")
span.SetSpanID([8]byte{1})
return span
}(),
config: func() config.ElasticSpanConfig {
cfg := config.Enabled().Span
cfg.ClearSpanID = config.AttributeConfig{Enabled: true}
cfg.ClearSpanName = config.AttributeConfig{Enabled: true}
return cfg
}(),
enrichedAttrs: map[string]any{
elasticattr.TimestampUs: startTs.AsTime().UnixMicro(),
elasticattr.SpanName: "testspan",
elasticattr.ProcessorEvent: "span",
elasticattr.SpanRepresentativeCount: float64(1),
elasticattr.SpanType: "unknown",
elasticattr.SpanDurationUs: expectedDuration.Microseconds(),
elasticattr.EventOutcome: "success",
elasticattr.SuccessCount: int64(1),
elasticattr.SpanID: "0100000000000000",
},
},
} {
t.Run(tc.name, func(t *testing.T) {
expectedSpan := ptrace.NewSpan()
Expand Down Expand Up @@ -2061,6 +2230,14 @@ func TestElasticSpanEnrich(t *testing.T) {
} else {
expectedSpan.Links().RemoveIf(func(_ ptrace.SpanLink) bool { return true })
}
// Clear span ID if ID and ClearSpanID are both enabled
if tc.config.ID.Enabled && tc.config.ClearSpanID.Enabled {
expectedSpan.SetSpanID(pcommon.SpanID{})
}
// Clear span name if Name and ClearSpanName are both enabled
if tc.config.Name.Enabled && tc.config.ClearSpanName.Enabled {
expectedSpan.SetName("")
}

EnrichSpan(tc.input, enrichConfig, uaparser.NewFromSaved())
assert.NoError(t, ptracetest.CompareSpan(expectedSpan, tc.input))
Expand Down