Skip to content

Commit f566739

Browse files
authored
fix(apm): Enhance Synthetic Span Service Representation (#751)
<!--- Please remember to review the [contribution guidelines](https://github.com/DataDog/datadog-lambda-python/blob/main/CONTRIBUTING.md) if you have not yet done so._ ---> ### What does this PR do? <!--- A brief description of the change being made with this pull request. ---> Rollout of span naming changes to align serverless product with tracer to create streamlined Service Representation for Serverless Key Changes: - Change service name to match instance name for all managed services (aws.lambda -> lambda name, etc) (breaking) - Opt out via `DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED` - Add `span.kind:server` on synthetic spans made via span-inferrer, cold start and lambda invocation spans - Remove `_dd.base_service` tags on synthetic spans to avoid unintentional service override ### Motivation <!--- What inspired you to submit this pull request? ---> Improve Service Map for Serverless. This allows for synthetic spans to have their own service on the map which connects with the inferred spans from the tracer side.
1 parent 64eb6ef commit f566739

22 files changed

+1300
-149
lines changed

bottlecap/src/config/env.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ pub struct EnvConfig {
178178
/// <https://docs.datadoghq.com/agent/configuration/dual-shipping/?tab=helm#environment-variable-configuration-1>
179179
#[serde(deserialize_with = "deserialize_additional_endpoints")]
180180
pub apm_additional_endpoints: HashMap<String, Vec<String>>,
181+
/// @env `DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED`
182+
///
183+
/// Enable the new AWS-resource naming logic in the tracer.
184+
#[serde(deserialize_with = "deserialize_optional_bool_from_anything")]
185+
pub trace_aws_service_representation_enabled: Option<bool>,
181186
//
182187
// Trace Propagation
183188
/// @env `DD_TRACE_PROPAGATION_STYLE`
@@ -362,6 +367,7 @@ fn merge_config(config: &mut Config, env_config: &EnvConfig) {
362367
merge_option_to_value!(config, env_config, apm_config_compression_level);
363368
merge_vec!(config, env_config, apm_features);
364369
merge_hashmap!(config, env_config, apm_additional_endpoints);
370+
merge_option_to_value!(config, env_config, trace_aws_service_representation_enabled);
365371

366372
// Trace Propagation
367373
merge_vec!(config, env_config, trace_propagation_style);
@@ -560,6 +566,7 @@ mod tests {
560566
jail.set_env("DD_TRACE_PROPAGATION_STYLE_EXTRACT", "b3");
561567
jail.set_env("DD_TRACE_PROPAGATION_EXTRACT_FIRST", "true");
562568
jail.set_env("DD_TRACE_PROPAGATION_HTTP_BAGGAGE_ENABLED", "true");
569+
jail.set_env("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", "true");
563570

564571
// OTLP
565572
jail.set_env("DD_OTLP_CONFIG_TRACES_ENABLED", "false");
@@ -709,6 +716,7 @@ mod tests {
709716
trace_propagation_style_extract: vec![TracePropagationStyle::B3],
710717
trace_propagation_extract_first: true,
711718
trace_propagation_http_baggage_enabled: true,
719+
trace_aws_service_representation_enabled: true,
712720
otlp_config_traces_enabled: false,
713721
otlp_config_traces_span_name_as_resource_name: true,
714722
otlp_config_traces_span_name_remappings: HashMap::from([(

bottlecap/src/config/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ pub struct Config {
283283
pub trace_propagation_style_extract: Vec<TracePropagationStyle>,
284284
pub trace_propagation_extract_first: bool,
285285
pub trace_propagation_http_baggage_enabled: bool,
286+
pub trace_aws_service_representation_enabled: bool,
286287

287288
// OTLP
288289
//
@@ -371,6 +372,7 @@ impl Default for Config {
371372
apm_config_compression_level: 6,
372373
apm_features: vec![],
373374
apm_additional_endpoints: HashMap::new(),
375+
trace_aws_service_representation_enabled: true,
374376
trace_propagation_style: vec![
375377
TracePropagationStyle::Datadog,
376378
TracePropagationStyle::TraceContext,

bottlecap/src/config/yaml.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ pub struct YamlConfig {
6464
pub apm_config: ApmConfig,
6565
#[serde(deserialize_with = "deserialize_service_mapping")]
6666
pub service_mapping: HashMap<String, String>,
67+
#[serde(deserialize_with = "deserialize_optional_bool_from_anything")]
68+
pub trace_aws_service_representation_enabled: Option<bool>,
6769
// Trace Propagation
6870
#[serde(deserialize_with = "deserialize_trace_propagation_style")]
6971
pub trace_propagation_style: Vec<TracePropagationStyle>,
@@ -451,6 +453,11 @@ fn merge_config(config: &mut Config, yaml_config: &YamlConfig) {
451453
merge_vec!(config, yaml_config, trace_propagation_style_extract);
452454
merge_option_to_value!(config, yaml_config, trace_propagation_extract_first);
453455
merge_option_to_value!(config, yaml_config, trace_propagation_http_baggage_enabled);
456+
merge_option_to_value!(
457+
config,
458+
yaml_config,
459+
trace_aws_service_representation_enabled
460+
);
454461

455462
// OTLP
456463
if let Some(otlp_config) = &yaml_config.otlp_config {
@@ -711,6 +718,7 @@ trace_propagation_style: "datadog"
711718
trace_propagation_style_extract: "b3"
712719
trace_propagation_extract_first: true
713720
trace_propagation_http_baggage_enabled: true
721+
trace_aws_service_representation_enabled: true
714722
715723
# OTLP
716724
otlp_config:
@@ -839,6 +847,7 @@ extension_version: "compatibility"
839847
trace_propagation_style_extract: vec![TracePropagationStyle::B3],
840848
trace_propagation_extract_first: true,
841849
trace_propagation_http_baggage_enabled: true,
850+
trace_aws_service_representation_enabled: true,
842851
otlp_config_traces_enabled: false,
843852
otlp_config_traces_span_name_as_resource_name: true,
844853
otlp_config_traces_span_name_remappings: HashMap::from([(

bottlecap/src/lifecycle/invocation/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,19 @@ pub fn base64_to_string(base64_string: &str) -> Result<String, DecodeError> {
3232
}
3333

3434
fn create_empty_span(name: String, resource: &str, service: &str) -> Span {
35-
Span {
35+
let mut span = Span {
3636
name,
3737
resource: resource.to_string(),
3838
service: service.to_string(),
3939
r#type: String::from("serverless"),
4040
..Default::default()
41-
}
41+
};
42+
43+
// Add span.kind to the span to enable other server based features for serverless
44+
span.meta
45+
.insert("span.kind".to_string(), "server".to_string());
46+
47+
span
4248
}
4349

4450
#[must_use]

bottlecap/src/lifecycle/invocation/processor.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ use crate::{
3939
},
4040
};
4141

42+
use crate::lifecycle::invocation::triggers::get_default_service_name;
43+
4244
pub const MS_TO_NS: f64 = 1_000_000.0;
4345
pub const S_TO_MS: u64 = 1_000;
4446
pub const S_TO_NS: f64 = 1_000_000_000.0;
@@ -89,16 +91,23 @@ impl Processor {
8991
aws_config: Arc<AwsConfig>,
9092
metrics_aggregator: Arc<Mutex<MetricsAggregator>>,
9193
) -> Self {
92-
let service = config.service.clone().unwrap_or(String::from("aws.lambda"));
9394
let resource = tags_provider
9495
.get_canonical_resource_name()
9596
.unwrap_or(String::from("aws.lambda"));
9697

98+
let service = get_default_service_name(
99+
&config.service.clone().unwrap_or(resource.clone()),
100+
"aws.lambda",
101+
config.trace_aws_service_representation_enabled,
102+
);
97103
let propagator = DatadogCompositePropagator::new(Arc::clone(&config));
98104

99105
Processor {
100106
context_buffer: ContextBuffer::default(),
101-
inferrer: SpanInferrer::new(config.service_mapping.clone()),
107+
inferrer: SpanInferrer::new(
108+
config.service_mapping.clone(),
109+
config.trace_aws_service_representation_enabled,
110+
),
102111
propagator,
103112
enhanced_metrics: EnhancedMetrics::new(metrics_aggregator, Arc::clone(&config)),
104113
aws_config,
@@ -195,6 +204,7 @@ impl Processor {
195204

196205
self.dynamic_tags
197206
.insert(String::from("cold_start"), cold_start.to_string());
207+
198208
if proactive_initialization {
199209
self.dynamic_tags.insert(
200210
String::from("proactive_initialization"),
@@ -238,7 +248,6 @@ impl Processor {
238248
);
239249
cold_start_span.span_id = generate_span_id();
240250
cold_start_span.start = start_time;
241-
242251
context.cold_start_span = Some(cold_start_span);
243252
}
244253

bottlecap/src/lifecycle/invocation/span_inferrer.rs

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::{
3232
#[derive(Default)]
3333
pub struct SpanInferrer {
3434
service_mapping: HashMap<String, String>,
35+
aws_service_representation_enabled: bool,
3536
// Span inferred from the Lambda incoming request payload
3637
pub inferred_span: Option<Span>,
3738
// Nested span inferred from the Lambda incoming request payload
@@ -50,9 +51,13 @@ pub struct SpanInferrer {
5051

5152
impl SpanInferrer {
5253
#[must_use]
53-
pub fn new(service_mapping: HashMap<String, String>) -> Self {
54+
pub fn new(
55+
service_mapping: HashMap<String, String>,
56+
aws_service_representation_enabled: bool,
57+
) -> Self {
5458
Self {
5559
service_mapping,
60+
aws_service_representation_enabled,
5661
inferred_span: None,
5762
wrapped_inferred_span: None,
5863
is_async_span: false,
@@ -87,19 +92,31 @@ impl SpanInferrer {
8792

8893
if APIGatewayHttpEvent::is_match(payload_value) {
8994
if let Some(t) = APIGatewayHttpEvent::new(payload_value.clone()) {
90-
t.enrich_span(&mut inferred_span, &self.service_mapping);
95+
t.enrich_span(
96+
&mut inferred_span,
97+
&self.service_mapping,
98+
self.aws_service_representation_enabled,
99+
);
91100

92101
trigger = Some(Box::new(t));
93102
}
94103
} else if APIGatewayRestEvent::is_match(payload_value) {
95104
if let Some(t) = APIGatewayRestEvent::new(payload_value.clone()) {
96-
t.enrich_span(&mut inferred_span, &self.service_mapping);
105+
t.enrich_span(
106+
&mut inferred_span,
107+
&self.service_mapping,
108+
self.aws_service_representation_enabled,
109+
);
97110

98111
trigger = Some(Box::new(t));
99112
}
100113
} else if APIGatewayWebSocketEvent::is_match(payload_value) {
101114
if let Some(t) = APIGatewayWebSocketEvent::new(payload_value.clone()) {
102-
t.enrich_span(&mut inferred_span, &self.service_mapping);
115+
t.enrich_span(
116+
&mut inferred_span,
117+
&self.service_mapping,
118+
self.aws_service_representation_enabled,
119+
);
103120

104121
trigger = Some(Box::new(t));
105122
}
@@ -110,19 +127,31 @@ impl SpanInferrer {
110127
}
111128
} else if LambdaFunctionUrlEvent::is_match(payload_value) {
112129
if let Some(t) = LambdaFunctionUrlEvent::new(payload_value.clone()) {
113-
t.enrich_span(&mut inferred_span, &self.service_mapping);
130+
t.enrich_span(
131+
&mut inferred_span,
132+
&self.service_mapping,
133+
self.aws_service_representation_enabled,
134+
);
114135

115136
trigger = Some(Box::new(t));
116137
}
117138
} else if MSKEvent::is_match(payload_value) {
118139
if let Some(t) = MSKEvent::new(payload_value.clone()) {
119-
t.enrich_span(&mut inferred_span, &self.service_mapping);
140+
t.enrich_span(
141+
&mut inferred_span,
142+
&self.service_mapping,
143+
self.aws_service_representation_enabled,
144+
);
120145

121146
trigger = Some(Box::new(t));
122147
}
123148
} else if SqsRecord::is_match(payload_value) {
124149
if let Some(t) = SqsRecord::new(payload_value.clone()) {
125-
t.enrich_span(&mut inferred_span, &self.service_mapping);
150+
t.enrich_span(
151+
&mut inferred_span,
152+
&self.service_mapping,
153+
self.aws_service_representation_enabled,
154+
);
126155

127156
self.generated_span_context = extract_trace_context_from_aws_trace_header(
128157
t.attributes.aws_trace_header.clone(),
@@ -140,7 +169,11 @@ impl SpanInferrer {
140169
sns: sns_entity,
141170
event_subscription_arn: None,
142171
};
143-
wt.enrich_span(&mut wrapped_inferred_span, &self.service_mapping);
172+
wt.enrich_span(
173+
&mut wrapped_inferred_span,
174+
&self.service_mapping,
175+
self.aws_service_representation_enabled,
176+
);
144177
inferred_span.meta.extend(wt.get_tags());
145178

146179
wrapped_inferred_span.duration =
@@ -155,8 +188,11 @@ impl SpanInferrer {
155188
..Default::default()
156189
};
157190

158-
event_bridge_entity
159-
.enrich_span(&mut wrapped_inferred_span, &self.service_mapping);
191+
event_bridge_entity.enrich_span(
192+
&mut wrapped_inferred_span,
193+
&self.service_mapping,
194+
self.aws_service_representation_enabled,
195+
);
160196
inferred_span.meta.extend(event_bridge_entity.get_tags());
161197

162198
wrapped_inferred_span.duration =
@@ -169,7 +205,11 @@ impl SpanInferrer {
169205
}
170206
} else if SnsRecord::is_match(payload_value) {
171207
if let Some(t) = SnsRecord::new(payload_value.clone()) {
172-
t.enrich_span(&mut inferred_span, &self.service_mapping);
208+
t.enrich_span(
209+
&mut inferred_span,
210+
&self.service_mapping,
211+
self.aws_service_representation_enabled,
212+
);
173213

174214
if let Some(message) = &t.sns.message {
175215
if let Ok(event_bridge_wrapper_message) =
@@ -180,8 +220,11 @@ impl SpanInferrer {
180220
..Default::default()
181221
};
182222

183-
event_bridge_wrapper_message
184-
.enrich_span(&mut wrapped_inferred_span, &self.service_mapping);
223+
event_bridge_wrapper_message.enrich_span(
224+
&mut wrapped_inferred_span,
225+
&self.service_mapping,
226+
self.aws_service_representation_enabled,
227+
);
185228
inferred_span
186229
.meta
187230
.extend(event_bridge_wrapper_message.get_tags());
@@ -197,27 +240,43 @@ impl SpanInferrer {
197240
}
198241
} else if DynamoDbRecord::is_match(payload_value) {
199242
if let Some(t) = DynamoDbRecord::new(payload_value.clone()) {
200-
t.enrich_span(&mut inferred_span, &self.service_mapping);
243+
t.enrich_span(
244+
&mut inferred_span,
245+
&self.service_mapping,
246+
self.aws_service_representation_enabled,
247+
);
201248
self.span_pointers = t.get_span_pointers();
202249

203250
trigger = Some(Box::new(t));
204251
}
205252
} else if S3Record::is_match(payload_value) {
206253
if let Some(t) = S3Record::new(payload_value.clone()) {
207-
t.enrich_span(&mut inferred_span, &self.service_mapping);
254+
t.enrich_span(
255+
&mut inferred_span,
256+
&self.service_mapping,
257+
self.aws_service_representation_enabled,
258+
);
208259
self.span_pointers = t.get_span_pointers();
209260

210261
trigger = Some(Box::new(t));
211262
}
212263
} else if EventBridgeEvent::is_match(payload_value) {
213264
if let Some(t) = EventBridgeEvent::new(payload_value.clone()) {
214-
t.enrich_span(&mut inferred_span, &self.service_mapping);
265+
t.enrich_span(
266+
&mut inferred_span,
267+
&self.service_mapping,
268+
self.aws_service_representation_enabled,
269+
);
215270

216271
trigger = Some(Box::new(t));
217272
}
218273
} else if KinesisRecord::is_match(payload_value) {
219274
if let Some(t) = KinesisRecord::new(payload_value.clone()) {
220-
t.enrich_span(&mut inferred_span, &self.service_mapping);
275+
t.enrich_span(
276+
&mut inferred_span,
277+
&self.service_mapping,
278+
self.aws_service_representation_enabled,
279+
);
221280

222281
trigger = Some(Box::new(t));
223282
}
@@ -282,6 +341,7 @@ impl SpanInferrer {
282341
String::from("peer.service"),
283342
invocation_span.service.clone(),
284343
);
344+
s.meta.insert("span.kind".to_string(), "server".to_string());
285345

286346
if let Some(ws) = &mut self.wrapped_inferred_span {
287347
ws.trace_id = invocation_span.trace_id;

0 commit comments

Comments
 (0)