Skip to content

Commit deceaa7

Browse files
committed
init commit
1 parent 8c7d8ed commit deceaa7

2 files changed

Lines changed: 244 additions & 2 deletions

File tree

bottlecap/src/lifecycle/invocation/span_inferrer.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ impl SpanInferrer {
112112

113113
wrapped_inferred_span.duration =
114114
inferred_span.start - wrapped_inferred_span.start;
115+
wrapped_inferred_span.meta.insert(
116+
"_inferred_span.tag_source".to_string(),
117+
"self".to_string(),
118+
);
119+
wrapped_inferred_span.meta.insert(
120+
"_inferred_span.synchronicity".to_string(),
121+
"async".to_string(),
122+
);
115123

116124
return Some(wrapped_inferred_span);
117125
} else if let Ok(event_bridge_entity) =
@@ -131,6 +139,14 @@ impl SpanInferrer {
131139

132140
wrapped_inferred_span.duration =
133141
inferred_span.start - wrapped_inferred_span.start;
142+
wrapped_inferred_span.meta.insert(
143+
"_inferred_span.tag_source".to_string(),
144+
"self".to_string(),
145+
);
146+
wrapped_inferred_span.meta.insert(
147+
"_inferred_span.synchronicity".to_string(),
148+
"async".to_string(),
149+
);
134150

135151
return Some(wrapped_inferred_span);
136152
}
@@ -158,6 +174,14 @@ impl SpanInferrer {
158174

159175
wrapped_inferred_span.duration =
160176
inferred_span.start - wrapped_inferred_span.start;
177+
wrapped_inferred_span.meta.insert(
178+
"_inferred_span.tag_source".to_string(),
179+
"self".to_string(),
180+
);
181+
wrapped_inferred_span.meta.insert(
182+
"_inferred_span.synchronicity".to_string(),
183+
"async".to_string(),
184+
);
161185

162186
return Some(wrapped_inferred_span);
163187
}
@@ -248,6 +272,15 @@ impl SpanInferrer {
248272
if should_skip_inferred_span {
249273
self.inferred_span = None;
250274
} else {
275+
let synchronicity = if t.is_async() { "async" } else { "sync" };
276+
inferred_span.meta.insert(
277+
"_inferred_span.tag_source".to_string(),
278+
"self".to_string(),
279+
);
280+
inferred_span.meta.insert(
281+
"_inferred_span.synchronicity".to_string(),
282+
synchronicity.to_string(),
283+
);
251284
self.inferred_span = Some(inferred_span);
252285
}
253286
}

bottlecap/src/traces/trace_processor.rs

Lines changed: 211 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::appsec::processor::Processor as AppSecProcessor;
55
use crate::appsec::processor::context::HoldArguments;
66
use crate::config;
77
use crate::lifecycle::invocation::processor::S_TO_MS;
8+
use crate::lifecycle::invocation::triggers::get_default_service_name;
89
use crate::tags::lambda::tags::COMPUTE_STATS_KEY;
910
use crate::tags::provider;
1011
use crate::traces::span_pointers::{SpanPointer, attach_span_pointers_to_meta};
@@ -70,8 +71,25 @@ impl TraceChunkProcessor for ChunkProcessor {
7071
span.service.clone_from(service);
7172
}
7273

73-
// Remove the _dd.base_service tag for unintentional service name override
74-
span.meta.remove("_dd.base_service");
74+
// For inferred spans, set _dd.base_service to the resolved execution
75+
// span service (same get_default_service_name used in processor.rs).
76+
// This covers extension-only languages (Go, .NET, Java, Ruby).
77+
if span.meta.contains_key("_inferred_span.tag_source")
78+
&& !span.meta.contains_key("_dd.base_service")
79+
{
80+
let base_service = get_default_service_name(
81+
&self
82+
.config
83+
.service
84+
.clone()
85+
.or_else(|| self.tags_provider.get_canonical_resource_name())
86+
.unwrap_or_else(|| "aws.lambda".to_string()),
87+
"aws.lambda",
88+
self.config.trace_aws_service_representation_enabled,
89+
);
90+
span.meta
91+
.insert("_dd.base_service".to_string(), base_service);
92+
}
7593

7694
self.tags_provider.get_tags_map().iter().for_each(|(k, v)| {
7795
span.meta.insert(k.clone(), v.clone());
@@ -1546,4 +1564,195 @@ mod tests {
15461564
// Both extension and tracer compute stats → tracer takes precedence, tag "0", no double-count.
15471565
check_compute_stats_behavior(true, true).await;
15481566
}
1567+
1568+
fn create_inferred_span() -> pb::Span {
1569+
let mut meta = HashMap::new();
1570+
meta.insert(
1571+
"_inferred_span.tag_source".to_string(),
1572+
"self".to_string(),
1573+
);
1574+
pb::Span {
1575+
name: "aws.sqs".to_string(),
1576+
service: "sqs".to_string(),
1577+
resource: "my-queue".to_string(),
1578+
trace_id: 1,
1579+
span_id: 2,
1580+
parent_id: 0,
1581+
start: 1000,
1582+
duration: 500,
1583+
error: 0,
1584+
meta,
1585+
metrics: HashMap::new(),
1586+
r#type: "web".to_string(),
1587+
span_links: vec![],
1588+
meta_struct: HashMap::new(),
1589+
span_events: vec![],
1590+
}
1591+
}
1592+
1593+
fn create_chunk_processor(config: Arc<Config>) -> ChunkProcessor {
1594+
let tags_provider = create_tags_provider(config.clone());
1595+
ChunkProcessor {
1596+
config,
1597+
obfuscation_config: Arc::new(
1598+
ObfuscationConfig::new().expect("Failed to create ObfuscationConfig"),
1599+
),
1600+
tags_provider,
1601+
span_pointers: None,
1602+
}
1603+
}
1604+
1605+
#[test]
1606+
fn test_base_service_uses_function_name_when_no_dd_service() {
1607+
let config = Arc::new(Config {
1608+
service: None,
1609+
trace_aws_service_representation_enabled: true,
1610+
..Config::default()
1611+
});
1612+
let mut processor = create_chunk_processor(config);
1613+
1614+
let inferred_span = create_inferred_span();
1615+
let mut chunk = pb::TraceChunk {
1616+
priority: 1,
1617+
origin: "lambda".to_string(),
1618+
spans: vec![inferred_span],
1619+
tags: HashMap::new(),
1620+
dropped_trace: false,
1621+
};
1622+
1623+
processor.process(&mut chunk, 0);
1624+
1625+
assert_eq!(
1626+
chunk.spans[0].meta.get("_dd.base_service").unwrap(),
1627+
"my-function",
1628+
"base_service should be the function name when DD_SERVICE is not set"
1629+
);
1630+
}
1631+
1632+
#[test]
1633+
fn test_base_service_uses_dd_service_when_set() {
1634+
let config = Arc::new(Config {
1635+
service: Some("my-payments-api".to_string()),
1636+
trace_aws_service_representation_enabled: true,
1637+
..Config::default()
1638+
});
1639+
let mut processor = create_chunk_processor(config);
1640+
1641+
let inferred_span = create_inferred_span();
1642+
let mut chunk = pb::TraceChunk {
1643+
priority: 1,
1644+
origin: "lambda".to_string(),
1645+
spans: vec![inferred_span],
1646+
tags: HashMap::new(),
1647+
dropped_trace: false,
1648+
};
1649+
1650+
processor.process(&mut chunk, 0);
1651+
1652+
assert_eq!(
1653+
chunk.spans[0].meta.get("_dd.base_service").unwrap(),
1654+
"my-payments-api",
1655+
"base_service should be DD_SERVICE when set"
1656+
);
1657+
}
1658+
1659+
#[test]
1660+
fn test_base_service_is_aws_lambda_when_representation_disabled() {
1661+
let config = Arc::new(Config {
1662+
service: Some("my-payments-api".to_string()),
1663+
trace_aws_service_representation_enabled: false,
1664+
..Config::default()
1665+
});
1666+
let mut processor = create_chunk_processor(config);
1667+
1668+
let inferred_span = create_inferred_span();
1669+
let mut chunk = pb::TraceChunk {
1670+
priority: 1,
1671+
origin: "lambda".to_string(),
1672+
spans: vec![inferred_span],
1673+
tags: HashMap::new(),
1674+
dropped_trace: false,
1675+
};
1676+
1677+
processor.process(&mut chunk, 0);
1678+
1679+
assert_eq!(
1680+
chunk.spans[0].meta.get("_dd.base_service").unwrap(),
1681+
"aws.lambda",
1682+
"base_service should be 'aws.lambda' when representation is disabled"
1683+
);
1684+
}
1685+
1686+
#[test]
1687+
fn test_base_service_not_set_on_non_inferred_spans() {
1688+
let config = Arc::new(Config {
1689+
service: Some("my-service".to_string()),
1690+
trace_aws_service_representation_enabled: true,
1691+
..Config::default()
1692+
});
1693+
let mut processor = create_chunk_processor(config);
1694+
1695+
let regular_span = pb::Span {
1696+
name: "http.request".to_string(),
1697+
service: "my-service".to_string(),
1698+
resource: "GET /users".to_string(),
1699+
trace_id: 1,
1700+
span_id: 3,
1701+
parent_id: 0,
1702+
start: 1000,
1703+
duration: 500,
1704+
error: 0,
1705+
meta: HashMap::new(),
1706+
metrics: HashMap::new(),
1707+
r#type: "web".to_string(),
1708+
span_links: vec![],
1709+
meta_struct: HashMap::new(),
1710+
span_events: vec![],
1711+
};
1712+
let mut chunk = pb::TraceChunk {
1713+
priority: 1,
1714+
origin: "lambda".to_string(),
1715+
spans: vec![regular_span],
1716+
tags: HashMap::new(),
1717+
dropped_trace: false,
1718+
};
1719+
1720+
processor.process(&mut chunk, 0);
1721+
1722+
assert!(
1723+
!chunk.spans[0].meta.contains_key("_dd.base_service"),
1724+
"base_service should not be set on non-inferred spans"
1725+
);
1726+
}
1727+
1728+
#[test]
1729+
fn test_base_service_not_overwritten_when_already_set() {
1730+
let config = Arc::new(Config {
1731+
service: Some("my-service".to_string()),
1732+
trace_aws_service_representation_enabled: true,
1733+
..Config::default()
1734+
});
1735+
let mut processor = create_chunk_processor(config);
1736+
1737+
let mut inferred_span = create_inferred_span();
1738+
inferred_span.meta.insert(
1739+
"_dd.base_service".to_string(),
1740+
"tracer-set-value".to_string(),
1741+
);
1742+
let mut chunk = pb::TraceChunk {
1743+
priority: 1,
1744+
origin: "lambda".to_string(),
1745+
spans: vec![inferred_span],
1746+
tags: HashMap::new(),
1747+
dropped_trace: false,
1748+
};
1749+
1750+
processor.process(&mut chunk, 0);
1751+
1752+
assert_eq!(
1753+
chunk.spans[0].meta.get("_dd.base_service").unwrap(),
1754+
"tracer-set-value",
1755+
"base_service should not be overwritten when already set by the tracer"
1756+
);
1757+
}
15491758
}

0 commit comments

Comments
 (0)