diff --git a/.gitignore b/.gitignore index df769d2..0dd34a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /.idea +/.worktrees diff --git a/Cargo.toml b/Cargo.toml index 0ce470a..1c9f528 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,9 @@ resolver = "2" members = [ "crates/*", ] +exclude = [ + "crates/.claude", +] [workspace.package] edition = "2024" diff --git a/crates/datadog-trace-agent/src/http_utils.rs b/crates/datadog-trace-agent/src/http_utils.rs index 74bc510..c1f265b 100644 --- a/crates/datadog-trace-agent/src/http_utils.rs +++ b/crates/datadog-trace-agent/src/http_utils.rs @@ -113,6 +113,23 @@ pub fn verify_request_content_length( None } +/// Environment variable set by the Lambda runtime to indicate the initialisation type. +/// Lambda Web Adapter (web function / native-http mode) sets this to `"native-http"`; +/// standard on-demand invocations set it to `"on-demand"`. +const ENV_LAMBDA_INIT_TYPE: &str = "AWS_LAMBDA_INITIALIZATION_TYPE"; + +/// Returns true if the current environment is Lambda Lite (web function / native-http mode). +/// +/// Determined by checking [`ENV_LAMBDA_INIT_TYPE`] == `"native-http"`. This is used to gate +/// behaviour specific to long-running web server deployments on Lambda Lite. +pub fn is_lambda_lite() -> bool { + is_lambda_lite_from_env(std::env::var(ENV_LAMBDA_INIT_TYPE).ok().as_deref()) +} + +fn is_lambda_lite_from_env(val: Option<&str>) -> bool { + val == Some("native-http") +} + /// Builds a reqwest client with optional proxy configuration and timeout. /// Uses rustls TLS by default. FIPS-compliant TLS is available via the fips feature pub fn build_client( @@ -134,8 +151,29 @@ mod tests { use hyper::header; use libdd_common::hyper_migration; + use super::is_lambda_lite_from_env; use super::verify_request_content_length; + #[test] + fn test_is_lambda_lite_native_http() { + assert!(is_lambda_lite_from_env(Some("native-http"))); + } + + #[test] + fn test_is_lambda_lite_on_demand() { + assert!(!is_lambda_lite_from_env(Some("on-demand"))); + } + + #[test] + fn test_is_lambda_lite_empty_string() { + assert!(!is_lambda_lite_from_env(Some(""))); + } + + #[test] + fn test_is_lambda_lite_unset() { + assert!(!is_lambda_lite_from_env(None)); + } + fn create_test_headers_with_content_length(val: &str) -> HeaderMap { let mut map = HeaderMap::new(); map.insert(header::CONTENT_LENGTH, val.parse().unwrap()); diff --git a/crates/datadog-trace-agent/src/mini_agent.rs b/crates/datadog-trace-agent/src/mini_agent.rs index 855290c..53500ff 100644 --- a/crates/datadog-trace-agent/src/mini_agent.rs +++ b/crates/datadog-trace-agent/src/mini_agent.rs @@ -11,7 +11,7 @@ use std::net::SocketAddr; use std::sync::Arc; use std::time::Instant; use tokio::sync::mpsc::{self, Receiver, Sender}; -use tracing::{debug, error}; +use tracing::{debug, error, warn}; use crate::http_utils::{log_and_create_http_response, verify_request_content_length}; use crate::proxy_flusher::{ProxyFlusher, ProxyRequest}; @@ -31,6 +31,13 @@ const PROFILING_ENDPOINT_PATH: &str = "/profiling/v1/input"; const TRACER_PAYLOAD_CHANNEL_BUFFER_SIZE: usize = 10; const STATS_PAYLOAD_CHANNEL_BUFFER_SIZE: usize = 10; const PROXY_PAYLOAD_CHANNEL_BUFFER_SIZE: usize = 10; +/// Sentinel file written on startup in Lambda Lite mode. +/// dd-trace (Node.js) checks this path via DATADOG_MINI_AGENT_PATH in constants.js +/// (datadog/dd-trace-js) to decide whether to switch from LogExporter (stdout) to +/// AgentExporter (HTTP :8126). +/// The parent directory `/tmp/datadog/` is created by the serverless-compat JS layer +/// before this binary is spawned. +const LAMBDA_LITE_SENTINEL_PATH: &str = "/tmp/datadog/mini_agent_ready"; pub struct MiniAgent { pub config: Arc, @@ -147,6 +154,27 @@ impl MiniAgent { now.elapsed().as_millis() ); + // Write a sentinel file so that Node.js dd-trace detects a running agent and + // switches from LogExporter (stdout) to AgentExporter (HTTP :8126). + // Only written for Lambda Lite; standard Lambda invocations use the Lambda + // Extension path (/opt/extensions/datadog-agent) instead. + // /opt is read-only in Lambda Lite, so we use /tmp/datadog/ (created by the + // serverless-compat JS layer before spawning this binary). + if crate::http_utils::is_lambda_lite() { + let sentinel = std::path::Path::new(LAMBDA_LITE_SENTINEL_PATH); + if let Some(parent) = sentinel.parent() { + let _ = std::fs::create_dir_all(parent); + } + if let Err(e) = std::fs::write(sentinel, b"") { + warn!( + "Could not write Lambda Lite sentinel file at {}: {}. \ + dd-trace (Node.js) will fall back to LogExporter (stdout), \ + traces may not reach Datadog.", + LAMBDA_LITE_SENTINEL_PATH, e + ); + } + } + if let Some(pipe_name) = pipe_name_opt { // Windows named pipe transport #[cfg(all(windows, feature = "windows-pipes"))]