diff --git a/examples/ffi/trace_exporter.c b/examples/ffi/trace_exporter.c index 3d646dbc6d..907849d358 100644 --- a/examples/ffi/trace_exporter.c +++ b/examples/ffi/trace_exporter.c @@ -99,7 +99,10 @@ int main(int argc, char** argv) ddog_TelemetryClientConfig telemetry_config = { .interval = 60000, .runtime_id = DDOG_CHARSLICE_C("12345678-1234-1234-1234-123456789abc"), - .debug_enabled = true + .debug_enabled = true, + .session_id = DDOG_CHARSLICE_C("12345678-1234-1234-1234-123456789abc"), + .root_session_id =DDOG_CHARSLICE_C("87654321-1234-1234-1234-123456789abc"), + .parent_session_id = DDOG_CHARSLICE_C(""), }; ret = ddog_trace_exporter_config_enable_telemetry(config, &telemetry_config); diff --git a/libdd-data-pipeline-ffi/src/trace_exporter.rs b/libdd-data-pipeline-ffi/src/trace_exporter.rs index b8d23b01c6..d5fd0c43d3 100644 --- a/libdd-data-pipeline-ffi/src/trace_exporter.rs +++ b/libdd-data-pipeline-ffi/src/trace_exporter.rs @@ -43,6 +43,13 @@ pub struct TelemetryClientConfig<'a> { /// When enabled, sets the DD-Telemetry-Debug-Enabled header to true. /// Defaults to false. pub debug_enabled: bool, + + /// Instrumentation session id (`dd-session-id`), same encoding rules as [`Self::runtime_id`]. + pub session_id: CharSlice<'a>, + /// Root session id (`dd-root-session-id`). + pub root_session_id: CharSlice<'a>, + /// Parent session id (`dd-parent-session-id`). + pub parent_session_id: CharSlice<'a>, } /// The TraceExporterConfig object will hold the configuration properties for the TraceExporter. @@ -301,6 +308,18 @@ pub unsafe extern "C" fn ddog_trace_exporter_config_enable_telemetry( Err(e) => return Some(e), }, debug_enabled: telemetry_cfg.debug_enabled, + session_id: match sanitize_string(telemetry_cfg.session_id) { + Ok(s) => Some(s), + Err(e) => return Some(e), + }, + root_session_id: match sanitize_string(telemetry_cfg.root_session_id) { + Ok(s) => Some(s), + Err(e) => return Some(e), + }, + parent_session_id: match sanitize_string(telemetry_cfg.parent_session_id) { + Ok(s) => Some(s), + Err(e) => return Some(e), + }, }; debug!(telemetry_cfg = ?cfg, "Configuring telemetry"); config.telemetry_cfg = Some(cfg); @@ -831,6 +850,9 @@ mod tests { interval: 1000, runtime_id: CharSlice::from("id"), debug_enabled: false, + session_id: CharSlice::empty(), + root_session_id: CharSlice::empty(), + parent_session_id: CharSlice::empty(), }), ); assert_eq!(error.as_ref().unwrap().code, ErrorCode::InvalidArgument); @@ -848,6 +870,9 @@ mod tests { interval: 1000, runtime_id: CharSlice::from("foo"), debug_enabled: true, + session_id: CharSlice::empty(), + root_session_id: CharSlice::empty(), + parent_session_id: CharSlice::empty(), }), ); assert!(error.is_none()); @@ -863,6 +888,44 @@ mod tests { "foo" ); assert!(cfg.telemetry_cfg.as_ref().unwrap().debug_enabled); + assert_eq!( + cfg.telemetry_cfg.as_ref().unwrap().session_id.as_deref(), + Some("") + ); + assert_eq!( + cfg.telemetry_cfg + .as_ref() + .unwrap() + .root_session_id + .as_deref(), + Some("") + ); + assert_eq!( + cfg.telemetry_cfg + .as_ref() + .unwrap() + .parent_session_id + .as_deref(), + Some("") + ); + + let mut cfg = TraceExporterConfig::default(); + let error = ddog_trace_exporter_config_enable_telemetry( + Some(&mut cfg), + Some(&TelemetryClientConfig { + interval: 500, + runtime_id: CharSlice::from("rid"), + debug_enabled: false, + session_id: CharSlice::from("sess-z"), + root_session_id: CharSlice::from("root-z"), + parent_session_id: CharSlice::from("par-z"), + }), + ); + assert!(error.is_none()); + let t = cfg.telemetry_cfg.as_ref().unwrap(); + assert_eq!(t.session_id.as_deref(), Some("sess-z")); + assert_eq!(t.root_session_id.as_deref(), Some("root-z")); + assert_eq!(t.parent_session_id.as_deref(), Some("par-z")); } } @@ -1143,6 +1206,9 @@ mod tests { heartbeat: 10000, runtime_id: Some("foo".to_string()), debug_enabled: true, + session_id: None, + root_session_id: None, + parent_session_id: None, }), ..Default::default() }; diff --git a/libdd-data-pipeline/src/telemetry/mod.rs b/libdd-data-pipeline/src/telemetry/mod.rs index 9715aa50ae..45e8eaa0af 100644 --- a/libdd-data-pipeline/src/telemetry/mod.rs +++ b/libdd-data-pipeline/src/telemetry/mod.rs @@ -32,6 +32,9 @@ pub struct TelemetryClientBuilder { tracer_version: Option, config: libdd_telemetry::config::Config, runtime_id: Option, + session_id: Option, + root_session_id: Option, + parent_session_id: Option, } impl TelemetryClientBuilder { @@ -93,6 +96,27 @@ impl TelemetryClientBuilder { self } + /// Sets the instrumentation session id sent as the `dd-session-id` header on telemetry + /// requests. + pub fn set_session_id(mut self, id: &str) -> Self { + self.session_id = Some(id.to_string()); + self + } + + /// Sets the root session id sent as the `dd-root-session-id` header (only with a valid session + /// id). + pub fn set_root_session_id(mut self, id: &str) -> Self { + self.root_session_id = Some(id.to_string()); + self + } + + /// Sets the parent session id sent as the `dd-parent-session-id` header (only with a valid + /// session id). + pub fn set_parent_session_id(mut self, id: &str) -> Self { + self.parent_session_id = Some(id.to_string()); + self + } + /// Sets the debug enabled flag for the telemetry client. pub fn set_debug_enabled(mut self, debug: bool) -> Self { self.config.debug_enabled = debug; @@ -117,6 +141,9 @@ impl TelemetryClientBuilder { if let Some(id) = self.runtime_id { builder.runtime_id = Some(id); } + builder.session_id = self.session_id; + builder.root_session_id = self.root_session_id; + builder.parent_session_id = self.parent_session_id; let (worker_handle, worker) = builder.build_worker(runtime); @@ -854,7 +881,10 @@ mod tests { let telemetry_srv = server .mock_async(|when, then| { when.method(POST) - .body_includes(r#""application":{"service_name":"test_service","service_version":"test_version","env":"test_env","language_name":"test_language","language_version":"test_language_version","tracer_version":"test_tracer_version"}"#); + .body_includes(r#""application":{"service_name":"test_service","service_version":"test_version","env":"test_env","language_name":"test_language","language_version":"test_language_version","tracer_version":"test_tracer_version"}"#) + .header("dd-session-id", "sess-e2e") + .header("dd-root-session-id", "root-e2e") + .header("dd-parent-session-id", "parent-e2e"); then.status(200).body(""); }) .await; @@ -869,6 +899,9 @@ mod tests { .set_url(&server.url("/")) .set_heartbeat(100) .set_runtime_id("foo") + .set_session_id("sess-e2e") + .set_root_session_id("root-e2e") + .set_parent_session_id("parent-e2e") .build(Handle::current()); tokio::spawn(async move { worker.run().await }); diff --git a/libdd-data-pipeline/src/trace_exporter/builder.rs b/libdd-data-pipeline/src/trace_exporter/builder.rs index b19e8b06af..a22f32133e 100644 --- a/libdd-data-pipeline/src/trace_exporter/builder.rs +++ b/libdd-data-pipeline/src/trace_exporter/builder.rs @@ -308,6 +308,15 @@ impl TraceExporterBuilder { if let Some(id) = telemetry_config.runtime_id { builder = builder.set_runtime_id(&id); } + if let Some(ref id) = telemetry_config.session_id { + builder = builder.set_session_id(id); + } + if let Some(ref id) = telemetry_config.root_session_id { + builder = builder.set_root_session_id(id); + } + if let Some(ref id) = telemetry_config.parent_session_id { + builder = builder.set_parent_session_id(id); + } builder.build(runtime.handle().clone()) }); @@ -437,6 +446,7 @@ mod tests { heartbeat: 1000, runtime_id: None, debug_enabled: false, + ..Default::default() }); let exporter = builder.build().unwrap(); diff --git a/libdd-data-pipeline/src/trace_exporter/mod.rs b/libdd-data-pipeline/src/trace_exporter/mod.rs index 06a838ed6c..2859a1e823 100644 --- a/libdd-data-pipeline/src/trace_exporter/mod.rs +++ b/libdd-data-pipeline/src/trace_exporter/mod.rs @@ -904,6 +904,12 @@ pub struct TelemetryConfig { pub heartbeat: u64, pub runtime_id: Option, pub debug_enabled: bool, + /// Sent as the `dd-session-id` telemetry header when set. + pub session_id: Option, + /// Sent as `dd-root-session-id` when set (only with a valid session id). + pub root_session_id: Option, + /// Sent as `dd-parent-session-id` when set (only with a valid session id). + pub parent_session_id: Option, } #[allow(missing_docs)] diff --git a/libdd-telemetry-ffi/src/builder/expanded.rs b/libdd-telemetry-ffi/src/builder/expanded.rs index 74daf6e8c0..840eae1c6d 100644 --- a/libdd-telemetry-ffi/src/builder/expanded.rs +++ b/libdd-telemetry-ffi/src/builder/expanded.rs @@ -245,6 +245,69 @@ mod macros { }; ffi::MaybeError::None } + #[no_mangle] + pub unsafe extern "C" fn ddog_telemetry_builder_with_str_session_id( + telemetry_builder: &mut TelemetryWorkerBuilder, + param: ffi::CharSlice, + ) -> ffi::MaybeError { + telemetry_builder.session_id = match (|s: ffi::CharSlice| -> Result<_, String> { + Ok(Some(s.to_utf8_lossy().into_owned())) + })(param) + { + Ok(o) => o, + Err(e) => { + return ffi::MaybeError::Some(libdd_common_ffi::Error::from( + ({ + let res = std::fmt::format(format_args!("{e:?}")); + res + }), + )); + } + }; + ffi::MaybeError::None + } + #[no_mangle] + pub unsafe extern "C" fn ddog_telemetry_builder_with_str_root_session_id( + telemetry_builder: &mut TelemetryWorkerBuilder, + param: ffi::CharSlice, + ) -> ffi::MaybeError { + telemetry_builder.root_session_id = match (|s: ffi::CharSlice| -> Result<_, String> { + Ok(Some(s.to_utf8_lossy().into_owned())) + })(param) + { + Ok(o) => o, + Err(e) => { + return ffi::MaybeError::Some(libdd_common_ffi::Error::from( + ({ + let res = std::fmt::format(format_args!("{e:?}")); + res + }), + )); + } + }; + ffi::MaybeError::None + } + #[no_mangle] + pub unsafe extern "C" fn ddog_telemetry_builder_with_str_parent_session_id( + telemetry_builder: &mut TelemetryWorkerBuilder, + param: ffi::CharSlice, + ) -> ffi::MaybeError { + telemetry_builder.parent_session_id = match (|s: ffi::CharSlice| -> Result<_, String> { + Ok(Some(s.to_utf8_lossy().into_owned())) + })(param) + { + Ok(o) => o, + Err(e) => { + return ffi::MaybeError::Some(libdd_common_ffi::Error::from( + ({ + let res = std::fmt::format(format_args!("{e:?}")); + res + }), + )); + } + }; + ffi::MaybeError::None + } #[repr(C)] #[allow(dead_code)] pub enum TelemetryWorkerBuilderStrProperty { @@ -259,6 +322,9 @@ mod macros { HostKernelRelease, HostKernelVersion, RuntimeId, + SessionId, + RootSessionId, + ParentSessionId, } #[no_mangle] /** @@ -288,6 +354,12 @@ mod macros { * runtime_id + * session_id + + * root_session_id + + * parent_session_id + */ pub unsafe extern "C" fn ddog_telemetry_builder_with_property_str( telemetry_builder: &mut TelemetryWorkerBuilder, @@ -481,6 +553,56 @@ mod macros { } }; } + SessionId => { + telemetry_builder.session_id = match (|s: ffi::CharSlice| -> Result<_, String> { + Ok(Some(s.to_utf8_lossy().into_owned())) + })(param) + { + Ok(o) => o, + Err(e) => { + return ffi::MaybeError::Some(libdd_common_ffi::Error::from( + ({ + let res = std::fmt::format(format_args!("{e:?}")); + res + }), + )); + } + }; + } + RootSessionId => { + telemetry_builder.root_session_id = + match (|s: ffi::CharSlice| -> Result<_, String> { + Ok(Some(s.to_utf8_lossy().into_owned())) + })(param) + { + Ok(o) => o, + Err(e) => { + return ffi::MaybeError::Some(libdd_common_ffi::Error::from( + ({ + let res = std::fmt::format(format_args!("{e:?}")); + res + }), + )); + } + }; + } + ParentSessionId => { + telemetry_builder.parent_session_id = + match (|s: ffi::CharSlice| -> Result<_, String> { + Ok(Some(s.to_utf8_lossy().into_owned())) + })(param) + { + Ok(o) => o, + Err(e) => { + return ffi::MaybeError::Some(libdd_common_ffi::Error::from( + ({ + let res = std::fmt::format(format_args!("{e:?}")); + res + }), + )); + } + }; + } } ffi::MaybeError::None } @@ -512,6 +634,12 @@ mod macros { * runtime_id + * session_id + + * root_session_id + + * parent_session_id + */ pub unsafe extern "C" fn ddog_telemetry_builder_with_str_named_property( telemetry_builder: &mut TelemetryWorkerBuilder, @@ -715,6 +843,56 @@ mod macros { } }; } + "session_id" => { + telemetry_builder.session_id = match (|s: ffi::CharSlice| -> Result<_, String> { + Ok(Some(s.to_utf8_lossy().into_owned())) + })(param) + { + Ok(o) => o, + Err(e) => { + return ffi::MaybeError::Some(libdd_common_ffi::Error::from( + ({ + let res = std::fmt::format(format_args!("{e:?}")); + res + }), + )); + } + }; + } + "root_session_id" => { + telemetry_builder.root_session_id = + match (|s: ffi::CharSlice| -> Result<_, String> { + Ok(Some(s.to_utf8_lossy().into_owned())) + })(param) + { + Ok(o) => o, + Err(e) => { + return ffi::MaybeError::Some(libdd_common_ffi::Error::from( + ({ + let res = std::fmt::format(format_args!("{e:?}")); + res + }), + )); + } + }; + } + "parent_session_id" => { + telemetry_builder.parent_session_id = + match (|s: ffi::CharSlice| -> Result<_, String> { + Ok(Some(s.to_utf8_lossy().into_owned())) + })(param) + { + Ok(o) => o, + Err(e) => { + return ffi::MaybeError::Some(libdd_common_ffi::Error::from( + ({ + let res = std::fmt::format(format_args!("{e:?}")); + res + }), + )); + } + }; + } _ => return ffi::MaybeError::None, } ffi::MaybeError::None diff --git a/libdd-telemetry-ffi/src/builder/macros.rs b/libdd-telemetry-ffi/src/builder/macros.rs index 4c9377b44d..150b23fcb8 100644 --- a/libdd-telemetry-ffi/src/builder/macros.rs +++ b/libdd-telemetry-ffi/src/builder/macros.rs @@ -36,7 +36,10 @@ crate::c_setters!( host.kernel_release, host.kernel_version, - runtime_id + runtime_id, + session_id, + root_session_id, + parent_session_id, } ); diff --git a/libdd-telemetry-ffi/src/lib.rs b/libdd-telemetry-ffi/src/lib.rs index fde98bedaf..f7df611b83 100644 --- a/libdd-telemetry-ffi/src/lib.rs +++ b/libdd-telemetry-ffi/src/lib.rs @@ -222,6 +222,52 @@ mod tests { } } + #[test] + #[cfg_attr(miri, ignore)] + fn test_session_id_and_root_session_id_ffi_setters_match_runtime_id_option_string() { + unsafe { + let mut builder: MaybeUninit> = MaybeUninit::uninit(); + assert_eq!( + ddog_telemetry_builder_instantiate( + NonNull::new(&mut builder).unwrap().cast(), + ffi::CharSlice::from("service_name"), + ffi::CharSlice::from("language_name"), + ffi::CharSlice::from("language_version"), + ffi::CharSlice::from("tracer_version"), + ), + MaybeError::None + ); + let mut builder = builder.assume_init(); + + assert_eq!( + ddog_telemetry_builder_with_str_session_id( + &mut builder, + ffi::CharSlice::from("sess-1"), + ), + MaybeError::None, + ); + assert_eq!(builder.session_id, Some("sess-1".into())); + + assert_eq!( + ddog_telemetry_builder_with_str_root_session_id( + &mut builder, + ffi::CharSlice::from("root-9"), + ), + MaybeError::None, + ); + assert_eq!(builder.root_session_id, Some("root-9".into())); + + assert_eq!( + ddog_telemetry_builder_with_str_parent_session_id( + &mut builder, + ffi::CharSlice::from("parent-2"), + ), + MaybeError::None, + ); + assert_eq!(builder.parent_session_id, Some("parent-2".into())); + } + } + #[test] #[cfg_attr(miri, ignore)] fn test_worker_run() { diff --git a/libdd-telemetry/src/worker/http_client.rs b/libdd-telemetry/src/worker/http_client.rs index 5f00744b12..1c60b19d9c 100644 --- a/libdd-telemetry/src/worker/http_client.rs +++ b/libdd-telemetry/src/worker/http_client.rs @@ -22,8 +22,33 @@ pub mod header { pub const LIBRARY_LANGUAGE: HeaderName = HeaderName::from_static("dd-client-library-language"); pub const LIBRARY_VERSION: HeaderName = HeaderName::from_static("dd-client-library-version"); - /// Header key for whether to enable debug mode of telemetry. pub const DEBUG_ENABLED: HeaderName = HeaderName::from_static("dd-telemetry-debug-enabled"); + + pub const DD_SESSION_ID: HeaderName = HeaderName::from_static("dd-session-id"); + pub const DD_ROOT_SESSION_ID: HeaderName = HeaderName::from_static("dd-root-session-id"); + pub const DD_PARENT_SESSION_ID: HeaderName = HeaderName::from_static("dd-parent-session-id"); +} + +pub(crate) fn add_instrumentation_session_headers( + mut builder: HttpRequestBuilder, + session_id: Option<&str>, + root_session_id: Option<&str>, + parent_session_id: Option<&str>, +) -> HttpRequestBuilder { + let Some(s) = session_id.filter(|id| !id.is_empty()) else { + return builder; + }; + builder = builder.header(header::DD_SESSION_ID, s); + if let Some(r) = root_session_id + .filter(|r| !r.is_empty()) + .filter(|r| *r != s) + { + builder = builder.header(header::DD_ROOT_SESSION_ID, r); + } + if let Some(p) = parent_session_id.filter(|p| !p.is_empty()) { + builder = builder.header(header::DD_PARENT_SESSION_ID, p); + } + builder } pub type ResponseFuture = diff --git a/libdd-telemetry/src/worker/mod.rs b/libdd-telemetry/src/worker/mod.rs index 3bfa1bcccb..dda1ef6685 100644 --- a/libdd-telemetry/src/worker/mod.rs +++ b/libdd-telemetry/src/worker/mod.rs @@ -136,6 +136,9 @@ pub struct TelemetryWorker { cancellation_token: CancellationToken, seq_id: AtomicU64, runtime_id: String, + session_id: Option, + root_session_id: Option, + parent_session_id: Option, client: Box, metrics_flush_interval: Duration, deadlines: scheduler::Scheduler, @@ -150,6 +153,9 @@ impl Debug for TelemetryWorker { .field("cancellation_token", &self.cancellation_token) .field("seq_id", &self.seq_id) .field("runtime_id", &self.runtime_id) + .field("session_id", &self.session_id) + .field("root_session_id", &self.root_session_id) + .field("parent_session_id", &self.parent_session_id) .field("metrics_flush_interval", &self.metrics_flush_interval) .field("deadlines", &self.deadlines) .field("data", &self.data) @@ -758,13 +764,18 @@ impl TelemetryWorker { ) .header( http_client::header::LIBRARY_LANGUAGE, - // Note: passing by ref here just causes the clone to happen underneath tel.application.language_name.clone(), ) .header( http_client::header::LIBRARY_VERSION, tel.application.tracer_version.clone(), ); + let req = http_client::add_instrumentation_session_headers( + req, + self.session_id.as_deref(), + self.root_session_id.as_deref(), + self.parent_session_id.as_deref(), + ); let body = http_common::Body::from(serialize::serialize(&tel)?); Ok(req.body(body)?) @@ -1033,6 +1044,9 @@ pub struct TelemetryWorkerBuilder { pub host: Host, pub application: Application, pub runtime_id: Option, + pub session_id: Option, + pub root_session_id: Option, + pub parent_session_id: Option, pub dependencies: store::Store, pub integrations: store::Store, pub configurations: store::Store, @@ -1084,6 +1098,9 @@ impl TelemetryWorkerBuilder { ..Default::default() }, runtime_id: None, + session_id: None, + root_session_id: None, + parent_session_id: None, dependencies: store::Store::new(MAX_ITEMS), integrations: store::Store::new(MAX_ITEMS), configurations: store::Store::new(MAX_ITEMS), @@ -1134,6 +1151,9 @@ impl TelemetryWorkerBuilder { runtime_id: self .runtime_id .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()), + session_id: self.session_id, + root_session_id: self.root_session_id, + parent_session_id: self.parent_session_id, client, metrics_flush_interval, deadlines: scheduler::Scheduler::new(vec![ @@ -1190,16 +1210,101 @@ impl TelemetryWorkerBuilder { #[cfg(test)] mod tests { - use crate::worker::TelemetryWorkerHandle; + use crate::data::Payload; + use crate::worker::http_client::header::{ + DD_PARENT_SESSION_ID, DD_ROOT_SESSION_ID, DD_SESSION_ID, + }; + use crate::worker::{TelemetryWorker, TelemetryWorkerBuilder}; + use libdd_common::{http_common, Endpoint}; + use tokio::runtime::Runtime; + + fn test_worker( + session_id: Option, + root_session_id: Option, + parent_session_id: Option, + ) -> TelemetryWorker { + let mut b = TelemetryWorkerBuilder::new( + "h".into(), + "svc".into(), + "lang".into(), + "1".into(), + "tv".into(), + ); + b.config + .set_endpoint(Endpoint::from_slice("http://127.0.0.1:1")) + .unwrap(); + b.runtime_id = Some("rid".into()); + b.session_id = session_id; + b.root_session_id = root_session_id; + b.parent_session_id = parent_session_id; + let rt = Runtime::new().unwrap(); + b.build_worker(rt.handle().clone()).1 + } + + #[test] + fn telemetry_http_includes_dd_session_id() { + let req = test_worker(Some("sess".into()), None, None) + .build_request(&Payload::AppHeartbeat(())) + .unwrap(); + assert_eq!( + req.headers().get(DD_SESSION_ID).unwrap().to_str().unwrap(), + "sess" + ); + assert!(req.headers().get(DD_ROOT_SESSION_ID).is_none()); + assert!(req.headers().get(DD_PARENT_SESSION_ID).is_none()); + } + + #[test] + fn telemetry_http_omits_session_family_without_valid_session_id() { + let assert_no_session_headers = |req: &http_common::HttpRequest| { + assert!(req.headers().get(DD_SESSION_ID).is_none()); + assert!(req.headers().get(DD_ROOT_SESSION_ID).is_none()); + assert!(req.headers().get(DD_PARENT_SESSION_ID).is_none()); + }; - fn is_send(_: T) {} - fn is_sync(_: T) {} + let req = test_worker(None, Some("root".into()), Some("parent".into())) + .build_request(&Payload::AppHeartbeat(())) + .unwrap(); + assert_no_session_headers(&req); + + let req = test_worker( + Some(String::new()), + Some("root".into()), + Some("parent".into()), + ) + .build_request(&Payload::AppHeartbeat(())) + .unwrap(); + assert_no_session_headers(&req); + } #[test] - fn test_handle_sync_send() { - #[allow(clippy::redundant_closure)] - let _ = |h: TelemetryWorkerHandle| is_send(h); - #[allow(clippy::redundant_closure)] - let _ = |h: TelemetryWorkerHandle| is_sync(h); + fn telemetry_http_includes_dd_session_root_and_parent_session_ids() { + let req = test_worker( + Some("sess".into()), + Some("root".into()), + Some("parent".into()), + ) + .build_request(&Payload::AppHeartbeat(())) + .unwrap(); + assert_eq!( + req.headers().get(DD_SESSION_ID).unwrap().to_str().unwrap(), + "sess" + ); + assert_eq!( + req.headers() + .get(DD_ROOT_SESSION_ID) + .unwrap() + .to_str() + .unwrap(), + "root" + ); + assert_eq!( + req.headers() + .get(DD_PARENT_SESSION_ID) + .unwrap() + .to_str() + .unwrap(), + "parent" + ); } }