diff --git a/datadog-sidecar-ffi/src/lib.rs b/datadog-sidecar-ffi/src/lib.rs index f3c5c97254..18245ed6f9 100644 --- a/datadog-sidecar-ffi/src/lib.rs +++ b/datadog-sidecar-ffi/src/lib.rs @@ -755,6 +755,43 @@ pub unsafe extern "C" fn ddog_sidecar_session_set_process_tags( MaybeError::None } +/// Records the tracer's auto-resolved default service name for the session +/// (process-bound; sidecar emits `svc.auto:` when `DD_SERVICE` is not +/// currently set for the active request). Pass an empty `CharSlice` to clear. +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn ddog_sidecar_session_set_default_service_name( + transport: &mut Box, + default_service_name: ffi::CharSlice, +) -> MaybeError { + let name = if default_service_name.is_empty() { + None + } else { + Some(default_service_name.to_utf8_lossy().into_owned()) + }; + try_c!(blocking::set_session_default_service_name(transport, name)); + + MaybeError::None +} + +/// Records whether `DD_SERVICE` is currently set for the session (per-request +/// mutable; refresh on each RINIT). When `true` the sidecar emits +/// `svc.user:true`; when `false` it falls back to the previously-recorded +/// `svc.auto:` (if any). +#[no_mangle] +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn ddog_sidecar_session_set_user_service_defined( + transport: &mut Box, + is_user_defined: bool, +) -> MaybeError { + try_c!(blocking::set_session_user_service_defined( + transport, + is_user_defined, + )); + + MaybeError::None +} + #[repr(C)] pub struct TracerHeaderTags<'a> { pub lang: ffi::CharSlice<'a>, diff --git a/datadog-sidecar/src/service/blocking.rs b/datadog-sidecar/src/service/blocking.rs index 80a9257384..9d7eb32cac 100644 --- a/datadog-sidecar/src/service/blocking.rs +++ b/datadog-sidecar/src/service/blocking.rs @@ -271,6 +271,22 @@ pub fn set_session_process_tags( Ok(()) } +pub fn set_session_default_service_name( + transport: &mut SidecarTransport, + name: Option, +) -> io::Result<()> { + lock_sender(transport)?.set_session_default_service_name(name); + Ok(()) +} + +pub fn set_session_user_service_defined( + transport: &mut SidecarTransport, + is_defined: bool, +) -> io::Result<()> { + lock_sender(transport)?.set_session_user_service_defined(is_defined); + Ok(()) +} + /// Sends a trace as bytes. pub fn send_trace_v04_bytes( transport: &mut SidecarTransport, diff --git a/datadog-sidecar/src/service/mod.rs b/datadog-sidecar/src/service/mod.rs index a7019781ff..3f12030916 100644 --- a/datadog-sidecar/src/service/mod.rs +++ b/datadog-sidecar/src/service/mod.rs @@ -101,3 +101,4 @@ pub enum SidecarAction { metrics: Vec, }, } + diff --git a/datadog-sidecar/src/service/runtime_info.rs b/datadog-sidecar/src/service/runtime_info.rs index 25128d1a50..0c95c7d450 100644 --- a/datadog-sidecar/src/service/runtime_info.rs +++ b/datadog-sidecar/src/service/runtime_info.rs @@ -144,6 +144,9 @@ impl ActiveApplication { .expect("Expecting remote config invariants to be set early") .clone(); + // Target is hashed on the sidecar side and on the PHP read side + // (sidecar.c:ddog_remote_configs_service_env_change). PHP passes the + // bare process_tags Vec, so we must too — otherwise SHM lookups miss. let process_tags = session.process_tags.lock_or_panic().clone(); if *session.remote_config_enabled.lock_or_panic() { diff --git a/datadog-sidecar/src/service/sender.rs b/datadog-sidecar/src/service/sender.rs index e88720e23f..bb88a7b85d 100644 --- a/datadog-sidecar/src/service/sender.rs +++ b/datadog-sidecar/src/service/sender.rs @@ -35,6 +35,8 @@ use tracing::trace; struct SidecarOutbox { set_session_config: Option, set_session_process_tags: Option, + set_session_default_service_name: Option, + set_session_user_service_defined: Option, set_universal_service_tags: Option, set_request_config: Option, clear_queue_id: Option, @@ -43,10 +45,12 @@ struct SidecarOutbox { } impl SidecarOutbox { - fn slots_mut(&mut self) -> [&mut Option; 7] { + fn slots_mut(&mut self) -> [&mut Option; 9] { [ &mut self.set_session_config, &mut self.set_session_process_tags, + &mut self.set_session_default_service_name, + &mut self.set_session_user_service_defined, &mut self.set_universal_service_tags, &mut self.set_request_config, &mut self.clear_queue_id, @@ -122,6 +126,12 @@ fn coalesce(outbox: &mut SidecarOutbox, incoming: SidecarInterfaceRequest) { SidecarInterfaceRequest::SetSessionProcessTags { .. } => { outbox.set_session_process_tags = Some(incoming); } + SidecarInterfaceRequest::SetSessionDefaultServiceName { .. } => { + outbox.set_session_default_service_name = Some(incoming); + } + SidecarInterfaceRequest::SetSessionUserServiceDefined { .. } => { + outbox.set_session_user_service_defined = Some(incoming); + } SidecarInterfaceRequest::SetUniversalServiceTags { .. } => { outbox.set_universal_service_tags = Some(incoming); } @@ -236,6 +246,22 @@ impl SidecarSender { self.try_drain_outbox(); } + pub fn set_session_default_service_name(&mut self, name: Option) { + coalesce( + &mut self.outbox, + SidecarInterfaceRequest::SetSessionDefaultServiceName { name }, + ); + self.try_drain_outbox(); + } + + pub fn set_session_user_service_defined(&mut self, is_defined: bool) { + coalesce( + &mut self.outbox, + SidecarInterfaceRequest::SetSessionUserServiceDefined { is_defined }, + ); + self.try_drain_outbox(); + } + #[allow(clippy::too_many_arguments)] pub fn set_universal_service_tags( &mut self, diff --git a/datadog-sidecar/src/service/session_info.rs b/datadog-sidecar/src/service/session_info.rs index 565f516fb9..fbe88b206e 100644 --- a/datadog-sidecar/src/service/session_info.rs +++ b/datadog-sidecar/src/service/session_info.rs @@ -45,6 +45,8 @@ pub(crate) struct SessionInfo { pub(crate) pid: Arc, pub(crate) remote_config_enabled: Arc>, pub(crate) process_tags: Arc>>, + pub(crate) auto_resolved_service_name: Arc>>, + pub(crate) user_service_defined: Arc>, pub(crate) stats_config: Arc>>, otlp_metrics_endpoint: Arc>>, } @@ -131,6 +133,31 @@ impl SessionInfo { self.runtimes.lock_or_panic() } + pub(crate) fn process_tags_with_svc_source(&self) -> Vec { + let mut tags = self.process_tags.lock_or_panic().clone(); + if *self.user_service_defined.lock_or_panic() { + if let Ok(tag) = Tag::new("svc.user", "true") { + tags.push(tag); + } + } else if let Some(name) = self.auto_resolved_service_name.lock_or_panic().as_ref() { + if let Ok(tag) = Tag::new("svc.auto", name.clone()) { + tags.push(tag); + } + } + tags + } + + pub(crate) fn refresh_stats_process_tags(&self) { + if let Some(stats) = self.stats_config.lock_or_panic().as_mut() { + stats.process_tags = self + .process_tags_with_svc_source() + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(","); + } + } + pub(crate) fn get_telemetry_config( &self, ) -> MutexGuard<'_, Option> { diff --git a/datadog-sidecar/src/service/sidecar_interface.rs b/datadog-sidecar/src/service/sidecar_interface.rs index 6f6244b258..67265f203e 100644 --- a/datadog-sidecar/src/service/sidecar_interface.rs +++ b/datadog-sidecar/src/service/sidecar_interface.rs @@ -69,6 +69,15 @@ pub trait SidecarInterface { /// * `process_tags` - The process tags. async fn set_session_process_tags(process_tags: Vec); + /// Records the auto-resolved default service name for the session + /// (process-bound; the tracer's fallback when `DD_SERVICE` is unset). + /// Pass `None` to clear it. + async fn set_session_default_service_name(name: Option); + + /// Records whether `DD_SERVICE` is currently set for the session + /// (per-request mutable; tracer should refresh on RINIT). + async fn set_session_user_service_defined(is_defined: bool); + /// Removes the application entry for the given queue ID from the instance. /// /// # Arguments diff --git a/datadog-sidecar/src/service/sidecar_server.rs b/datadog-sidecar/src/service/sidecar_server.rs index 5b2d27b801..c5121f4b66 100644 --- a/datadog-sidecar/src/service/sidecar_server.rs +++ b/datadog-sidecar/src/service/sidecar_server.rs @@ -478,7 +478,7 @@ impl SidecarInterface for ConnectionSidecarHandler { .unwrap_or("unknown-service"); let env = entry.get().env.as_deref().unwrap_or("none"); - let process_tags = session.process_tags.lock_or_panic().clone(); + let process_tags = session.process_tags_with_svc_source(); // Pre-compute session config so both the primary and retry get_or_create calls // can use it without re-locking the session. @@ -763,8 +763,8 @@ impl SidecarInterface for ConnectionSidecarHandler { } else { config.hostname.clone() }, - process_tags: config - .process_tags + process_tags: session + .process_tags_with_svc_source() .iter() .map(|t| t.to_string()) .collect::>() @@ -828,6 +828,37 @@ impl SidecarInterface for ConnectionSidecarHandler { .unwrap_or_default(); let session = self.server.get_session(session_id); *session.process_tags.lock_or_panic() = process_tags; + session.refresh_stats_process_tags(); + } + + async fn set_session_default_service_name( + &self, + _peer: PeerCredentials, + name: Option, + ) { + let session_id = self + .session_id + .get() + .map(|s| s.as_str()) + .unwrap_or_default(); + let session = self.server.get_session(session_id); + *session.auto_resolved_service_name.lock_or_panic() = name; + session.refresh_stats_process_tags(); + } + + async fn set_session_user_service_defined( + &self, + _peer: PeerCredentials, + is_defined: bool, + ) { + let session_id = self + .session_id + .get() + .map(|s| s.as_str()) + .unwrap_or_default(); + let session = self.server.get_session(session_id); + *session.user_service_defined.lock_or_panic() = is_defined; + session.refresh_stats_process_tags(); } async fn shutdown_runtime(&self, _peer: PeerCredentials, instance_id: InstanceId) { diff --git a/datadog-sidecar/src/service/telemetry.rs b/datadog-sidecar/src/service/telemetry.rs index 9954f6eb41..980d8f127b 100644 --- a/datadog-sidecar/src/service/telemetry.rs +++ b/datadog-sidecar/src/service/telemetry.rs @@ -703,7 +703,7 @@ fn get_telemetry_client( return None; }; - let process_tags = session.process_tags.lock_or_panic().clone(); + let process_tags = session.process_tags_with_svc_source(); Some(sidecar.telemetry_clients.get_or_create( service_name,