Skip to content

Commit f5a3f43

Browse files
committed
fix(datadog-lambda): track cold start per handler
1 parent ddc0e73 commit f5a3f43

2 files changed

Lines changed: 73 additions & 27 deletions

File tree

integrations/aws/datadog-lambda/src/invocation.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,17 @@
33

44
use crate::attribute_keys as attr;
55
use crate::span_inferrer::{InferredSpanScope, TriggerContext, TriggerExtractor};
6-
use crate::Config;
76

87
use lambda_runtime::LambdaEvent;
98
use opentelemetry::trace::{FutureExt, SpanKind, TraceContextExt, Tracer, TracerProvider as _};
109
use opentelemetry::{Context, KeyValue};
1110
use opentelemetry_sdk::trace::{SdkTracer, SdkTracerProvider};
1211
use serde_json::Value;
1312
use std::future::Future;
14-
use std::sync::atomic::{AtomicBool, Ordering};
1513
use std::time::SystemTime;
1614

17-
static IS_COLD_START: AtomicBool = AtomicBool::new(true);
1815
static TRACER_NAME: &str = "datadog-lambda-rs";
1916

20-
fn detect_cold_start() -> bool {
21-
IS_COLD_START.swap(false, Ordering::Relaxed)
22-
}
23-
2417
/// The Lambda invocation (aws.lambda) root span.
2518
pub(crate) struct LambdaSpan {
2619
cx: Context,
@@ -103,20 +96,22 @@ impl LambdaSpan {
10396
pub(crate) fn start_invocation(
10497
event: &LambdaEvent<Value>,
10598
provider: &SdkTracerProvider,
106-
_config: &Config,
99+
cold_start: bool,
107100
) -> (LambdaSpan, InferredSpanScope) {
108-
let is_cold = detect_cold_start();
109101
let tracer = provider.tracer(TRACER_NAME);
110102
let extraction = TriggerExtractor::extract(&event.payload);
111-
let (parent_cx, inferred_spans) =
112-
InferredSpanScope::start(&tracer, &extraction.upstream_cx, extraction.inferred_span.as_ref());
103+
let (parent_cx, inferred_spans) = InferredSpanScope::start(
104+
&tracer,
105+
&extraction.upstream_cx,
106+
extraction.inferred_span.as_ref(),
107+
);
113108
let trigger = TriggerContext {
114109
parent_cx,
115110
is_async: extraction.is_async,
116111
event_source: extraction.event_source,
117112
event_source_arn: extraction.event_source_arn,
118113
};
119-
let lambda_span = LambdaSpan::start(&tracer, &trigger, &event.context, is_cold);
114+
let lambda_span = LambdaSpan::start(&tracer, &trigger, &event.context, cold_start);
120115

121116
(lambda_span, inferred_spans)
122117
}
@@ -310,7 +305,7 @@ mod tests {
310305
lambda_runtime::Context::default(),
311306
);
312307

313-
let (span, _) = start_invocation(&raw_event, &provider, &Config::default());
308+
let (span, _) = start_invocation(&raw_event, &provider, false);
314309
assert!(span.cx.span().span_context().is_valid());
315310
}
316311

@@ -341,7 +336,7 @@ mod tests {
341336
test_lambda_cx(),
342337
);
343338

344-
let (span, inferred_spans) = start_invocation(&raw_event, &provider, &Config::default());
339+
let (span, inferred_spans) = start_invocation(&raw_event, &provider, false);
345340
assert!(span.cx.span().span_context().is_valid());
346341
assert!(!inferred_spans.is_empty());
347342
}

integrations/aws/datadog-lambda/src/lib.rs

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,23 @@ type BoxFuture<T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send
2121
pub struct Config {
2222
/// Service name. Overrides `DD_SERVICE`. Ignored when [`tracing`](Self::tracing) is `Some`.
2323
pub service: Option<String>,
24-
/// Deployment environment. Overrides `DD_ENV`. Ignored when [`tracing`](Self::tracing) is `Some`.
24+
/// Deployment environment. Overrides `DD_ENV`. Ignored when [`tracing`](Self::tracing) is
25+
/// `Some`.
2526
pub env: Option<String>,
2627
/// Service version. Overrides `DD_VERSION`. Ignored when [`tracing`](Self::tracing) is `Some`.
2728
pub version: Option<String>,
2829
/// Full control over the OTel SDK and Datadog tracer config.
2930
///
30-
/// When `None` (default), Lambda-appropriate defaults are applied and `service`/`env`/`version`
31-
/// above are forwarded. When `Some`, the builder is used as-is; `service`/`env`/`version` are
32-
/// ignored and you are responsible for setting:
33-
/// - `trace_stats_computation_enabled = false` (the Datadog agent handles stats for serverless environments)
34-
/// - `trace_writer_synchronous_write = true` (so `force_flush()` blocks until spans reach agent)
31+
/// When `None` (default), Lambda-appropriate defaults are applied and
32+
/// `service`/`env`/`version` above are forwarded. When `Some`, the builder is used as-is;
33+
/// `service`/`env`/`version` are ignored and you are responsible for setting:
34+
/// - `trace_stats_computation_enabled = false` (the Datadog agent handles stats for serverless
35+
/// environments)
36+
/// - `trace_writer_synchronous_write = true` (so `force_flush()` blocks until spans reach
37+
/// agent)
3538
pub tracing: Option<datadog_opentelemetry::DatadogTracingBuilder>,
3639
}
3740

38-
3941
fn build_tracing(
4042
service: Option<String>,
4143
env: Option<String>,
@@ -98,23 +100,32 @@ fn build_tracing(
98100
pub struct WrappedHandler<F, E, R> {
99101
inner: Arc<F>,
100102
provider: opentelemetry_sdk::trace::SdkTracerProvider,
101-
config: Arc<Config>,
103+
cold_start: bool,
102104
_phantom: PhantomData<fn(E) -> R>,
103105
}
104106

105107
impl<F, E, R> WrappedHandler<F, E, R> {
106108
pub fn new(handler: F, config: Config) -> Self {
107-
let Config { tracing, service, env, version } = config;
109+
let Config {
110+
tracing,
111+
service,
112+
env,
113+
version,
114+
} = config;
108115
let provider = tracing
109116
.unwrap_or_else(|| build_tracing(service, env, version))
110117
.init();
111118
Self {
112119
inner: Arc::new(handler),
113120
provider,
114-
config: Arc::new(Config::default()),
121+
cold_start: true,
115122
_phantom: PhantomData,
116123
}
117124
}
125+
126+
fn take_cold_start(&mut self) -> bool {
127+
std::mem::replace(&mut self.cold_start, false)
128+
}
118129
}
119130

120131
impl<F, Fut, E, R> Service<LambdaEvent<Value>> for WrappedHandler<F, E, R>
@@ -133,18 +144,58 @@ where
133144
}
134145

135146
fn call(&mut self, event: LambdaEvent<Value>) -> Self::Future {
147+
let cold_start = self.take_cold_start();
136148
let inner_handler = Arc::clone(&self.inner);
137149
let provider = self.provider.clone();
138-
let lambda_config = Arc::clone(&self.config);
139-
let (lambda_span, inferred_spans) = start_invocation(&event, &provider, &lambda_config);
150+
let (lambda_span, inferred_spans) = start_invocation(&event, &provider, cold_start);
140151
let typed_payload = match serde_json::from_value::<E>(event.payload) {
141152
Ok(payload) => payload,
142153
Err(err) => {
143-
return Box::pin(run_handler(lambda_span, inferred_spans, provider, std::future::ready(Err(err.into()))));
154+
return Box::pin(run_handler(
155+
lambda_span,
156+
inferred_spans,
157+
provider,
158+
std::future::ready(Err(err.into())),
159+
));
144160
}
145161
};
146162
let typed_event = LambdaEvent::new(typed_payload, event.context);
147163
let fut = inner_handler(typed_event);
148164
Box::pin(async move { run_handler(lambda_span, inferred_spans, provider, fut).await })
149165
}
150166
}
167+
168+
#[cfg(test)]
169+
mod tests {
170+
use super::*;
171+
172+
fn noop_handler(
173+
_: LambdaEvent<Value>,
174+
) -> std::future::Ready<Result<(), lambda_runtime::Error>> {
175+
std::future::ready(Ok(()))
176+
}
177+
178+
fn test_handler() -> WrappedHandler<
179+
fn(LambdaEvent<Value>) -> std::future::Ready<Result<(), lambda_runtime::Error>>,
180+
Value,
181+
(),
182+
> {
183+
WrappedHandler {
184+
inner: Arc::new(noop_handler),
185+
provider: opentelemetry_sdk::trace::SdkTracerProvider::builder().build(),
186+
cold_start: true,
187+
_phantom: PhantomData,
188+
}
189+
}
190+
191+
#[test]
192+
fn cold_start_is_tracked_per_handler() {
193+
let mut first = test_handler();
194+
assert!(first.take_cold_start());
195+
assert!(!first.take_cold_start());
196+
197+
let mut second = test_handler();
198+
assert!(second.take_cold_start());
199+
assert!(!second.take_cold_start());
200+
}
201+
}

0 commit comments

Comments
 (0)