Skip to main content

libdd_library_config/
tracer_metadata.rs

1// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3use libdd_trace_protobuf::opentelemetry::proto as otel_proto;
4use std::default::Default;
5
6/// This struct MUST be backward compatible.
7#[derive(serde::Serialize, Debug)]
8pub struct TracerMetadata {
9    /// Version of the schema.
10    pub schema_version: u8,
11    /// Runtime UUID.
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub runtime_id: Option<String>,
14    /// Programming language of the tracer library (e.g., “python”). Refers to telemetry
15    /// for valid values.
16    pub tracer_language: String,
17    /// Version of the tracer (e.g., "1.0.0").
18    pub tracer_version: String,
19    /// Identifier of the machine running the process.
20    pub hostname: String,
21    /// Name of the service being instrumented.
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub service_name: Option<String>,
24    /// Environment of the service being instrumented.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub service_env: Option<String>,
27    /// Version of the service being instrumented.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub service_version: Option<String>,
30    /// Process tags of the application being instrumented.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub process_tags: Option<String>,
33    /// Container id seen by the application.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub container_id: Option<String>,
36}
37
38impl Default for TracerMetadata {
39    fn default() -> Self {
40        TracerMetadata {
41            schema_version: 2,
42            runtime_id: None,
43            tracer_language: String::new(),
44            tracer_version: String::new(),
45            hostname: String::new(),
46            service_name: None,
47            service_env: None,
48            service_version: None,
49            process_tags: None,
50            container_id: None,
51        }
52    }
53}
54
55impl TracerMetadata {
56    // The value of the telemetry.sdk.name field to put in the otel context resource.
57    const OTEL_SDK_NAME: &str = "libdatadog";
58
59    pub fn to_otel_process_ctx(&self) -> otel_proto::common::v1::ProcessContext {
60        use otel_proto::common::v1::{any_value, AnyValue, KeyValue};
61
62        fn key_value(key: &'static str, val: String) -> KeyValue {
63            KeyValue {
64                key: key.to_owned(),
65                value: Some(AnyValue {
66                    value: Some(any_value::Value::StringValue(val)),
67                }),
68                key_ref: 0,
69            }
70        }
71
72        // Even if there's no value, we still set the key to let the reader know that we do support
73        // and emit this specific attribute, which happens to be empty in this case.
74        fn key_value_opt(key: &'static str, val: &Option<String>) -> KeyValue {
75            key_value(key, val.as_ref().cloned().unwrap_or_default())
76        }
77
78        // Every field of `self` should get propagated to the otel context.
79        // If you add a new field, please also add it here and as a key/value in the otel context.
80        let TracerMetadata {
81            // This one isn't propagated on purpose
82            schema_version: _,
83            runtime_id,
84            tracer_language,
85            tracer_version,
86            hostname,
87            service_name,
88            service_env,
89            service_version,
90            process_tags,
91            container_id,
92        } = self;
93
94        otel_proto::common::v1::ProcessContext {
95            resource: Some(otel_proto::resource::v1::Resource {
96                attributes: vec![
97                    key_value_opt("service.name", service_name),
98                    key_value_opt("service.instance.id", runtime_id),
99                    key_value_opt("service.version", service_version),
100                    key_value_opt("deployment.environment.name", service_env),
101                    key_value("telemetry.sdk.language", tracer_language.clone()),
102                    key_value("telemetry.sdk.version", tracer_version.clone()),
103                    key_value("telemetry.sdk.name", Self::OTEL_SDK_NAME.to_owned()),
104                    key_value("host.name", hostname.clone()),
105                    key_value_opt("container.id", container_id),
106                ],
107                dropped_attributes_count: 0,
108                entity_refs: vec![],
109            }),
110            extra_attributes: vec![key_value_opt("datadog.process_tags", process_tags)],
111        }
112    }
113}
114
115pub enum AnonymousFileHandle {
116    #[cfg(target_os = "linux")]
117    Linux(memfd::Memfd),
118    #[cfg(not(target_os = "linux"))]
119    Other(()),
120}
121
122#[cfg(target_os = "linux")]
123mod linux {
124    use anyhow::Context;
125    use rand::distributions::Alphanumeric;
126    use rand::Rng;
127    use std::io::Write;
128
129    /// Create a memfd file storing the tracer metadata. This function also attempts to publish the
130    /// tracer metadata as an OTel process context separately, but will ignore resulting errors.
131    pub fn store_tracer_metadata(
132        data: &super::TracerMetadata,
133    ) -> anyhow::Result<super::AnonymousFileHandle> {
134        let _ = crate::otel_process_ctx::linux::publish(&data.to_otel_process_ctx());
135
136        let uid: String = rand::thread_rng()
137            .sample_iter(&Alphanumeric)
138            .take(8)
139            .map(char::from)
140            .collect();
141
142        let mfd_name: String = format!("datadog-tracer-info-{uid}");
143
144        let mfd = memfd::MemfdOptions::default()
145            .close_on_exec(true)
146            .allow_sealing(true)
147            .create::<&str>(mfd_name.as_ref())
148            .context("unable to create memfd")?;
149
150        let buf = rmp_serde::to_vec_named(data).context("failed serialization")?;
151        mfd.as_file()
152            .write_all(&buf)
153            .context("unable to write into memfd")?;
154
155        mfd.add_seals(&[
156            memfd::FileSeal::SealShrink,
157            memfd::FileSeal::SealGrow,
158            memfd::FileSeal::SealSeal,
159        ])
160        .context("unable to seal memfd")?;
161
162        Ok(super::AnonymousFileHandle::Linux(mfd))
163    }
164}
165
166#[cfg(not(target_os = "linux"))]
167mod other {
168    pub fn store_tracer_metadata(
169        _data: &super::TracerMetadata,
170    ) -> anyhow::Result<super::AnonymousFileHandle> {
171        Ok(super::AnonymousFileHandle::Other(()))
172    }
173}
174
175#[cfg(target_os = "linux")]
176pub use linux::*;
177#[cfg(not(target_os = "linux"))]
178pub use other::*;