use libdd_trace_protobuf::opentelemetry::proto as otel_proto;
use std::default::Default;
#[derive(serde::Serialize, Debug, PartialEq, Eq, Hash)]
pub struct TracerMetadata {
pub schema_version: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub runtime_id: Option<String>,
pub tracer_language: String,
pub tracer_version: String,
pub hostname: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_env: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub process_tags: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub container_id: Option<String>,
#[cfg(feature = "otel-thread-ctx")]
#[serde(skip)]
pub threadlocal_attribute_keys: Option<Vec<String>>,
}
impl Default for TracerMetadata {
fn default() -> Self {
TracerMetadata {
schema_version: 2,
runtime_id: None,
tracer_language: String::new(),
tracer_version: String::new(),
hostname: String::new(),
service_name: None,
service_env: None,
service_version: None,
process_tags: None,
container_id: None,
#[cfg(feature = "otel-thread-ctx")]
threadlocal_attribute_keys: None,
}
}
}
impl TracerMetadata {
const OTEL_SDK_NAME: &str = "libdatadog";
pub fn to_otel_process_ctx(&self) -> otel_proto::common::v1::ProcessContext {
#[cfg(feature = "otel-thread-ctx")]
use otel_proto::common::v1::ArrayValue;
use otel_proto::{
common::v1::{any_value, AnyValue, KeyValue, ProcessContext},
resource::v1::Resource,
};
fn key_value(key: &'static str, val: String) -> KeyValue {
KeyValue {
key: key.to_owned(),
value: Some(AnyValue {
value: Some(any_value::Value::StringValue(val)),
}),
key_ref: 0,
}
}
fn key_value_opt(key: &'static str, val: &Option<String>) -> KeyValue {
key_value(key, val.as_ref().cloned().unwrap_or_default())
}
let TracerMetadata {
schema_version: _,
runtime_id,
tracer_language,
tracer_version,
hostname,
service_name,
service_env,
service_version,
process_tags,
container_id,
#[cfg(feature = "otel-thread-ctx")]
threadlocal_attribute_keys,
} = self;
#[cfg_attr(not(feature = "otel-thread-ctx"), allow(unused_mut))]
let mut attributes = vec![
key_value_opt("service.name", service_name),
key_value_opt("service.instance.id", runtime_id),
key_value_opt("service.version", service_version),
key_value_opt("deployment.environment.name", service_env),
key_value("telemetry.sdk.language", tracer_language.clone()),
key_value("telemetry.sdk.version", tracer_version.clone()),
key_value("telemetry.sdk.name", Self::OTEL_SDK_NAME.to_owned()),
key_value("host.name", hostname.clone()),
key_value_opt("container.id", container_id),
];
#[cfg(feature = "otel-thread-ctx")]
if let Some(threadlocal_attribute_keys) = threadlocal_attribute_keys.as_ref() {
attributes.push(key_value(
"threadlocal.schema_version",
"tlsdesc_v1_dev".to_owned(),
));
attributes.push(KeyValue {
key: "threadlocal.attribute_key_map".to_owned(),
value: Some(AnyValue {
value: Some(any_value::Value::ArrayValue(ArrayValue {
values: std::iter::once(AnyValue {
value: Some(any_value::Value::StringValue(
"datadog.local_root_span_id".to_owned(),
)),
})
.chain(threadlocal_attribute_keys.iter().map(|k| AnyValue {
value: Some(any_value::Value::StringValue(k.clone())),
}))
.collect(),
})),
}),
key_ref: 0,
});
}
ProcessContext {
resource: Some(Resource {
attributes,
dropped_attributes_count: 0,
entity_refs: vec![],
}),
extra_attributes: vec![key_value_opt("datadog.process_tags", process_tags)],
}
}
}
pub enum AnonymousFileHandle {
#[cfg(target_os = "linux")]
Linux(memfd::Memfd),
#[cfg(not(target_os = "linux"))]
Other(()),
}
#[cfg(target_os = "linux")]
mod linux {
use anyhow::Context;
use rand::distributions::Alphanumeric;
use rand::Rng;
use std::io::Write;
pub fn store_tracer_metadata(
data: &super::TracerMetadata,
) -> anyhow::Result<super::AnonymousFileHandle> {
let _ = crate::otel_process_ctx::linux::publish(&data.to_otel_process_ctx());
let uid: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(8)
.map(char::from)
.collect();
let mfd_name: String = format!("datadog-tracer-info-{uid}");
let mfd = memfd::MemfdOptions::default()
.close_on_exec(true)
.allow_sealing(true)
.create::<&str>(mfd_name.as_ref())
.context("unable to create memfd")?;
let buf = rmp_serde::to_vec_named(data).context("failed serialization")?;
mfd.as_file()
.write_all(&buf)
.context("unable to write into memfd")?;
mfd.add_seals(&[
memfd::FileSeal::SealShrink,
memfd::FileSeal::SealGrow,
memfd::FileSeal::SealSeal,
])
.context("unable to seal memfd")?;
Ok(super::AnonymousFileHandle::Linux(mfd))
}
}
#[cfg(not(target_os = "linux"))]
mod other {
pub fn store_tracer_metadata(
_data: &super::TracerMetadata,
) -> anyhow::Result<super::AnonymousFileHandle> {
Ok(super::AnonymousFileHandle::Other(()))
}
}
#[cfg(target_os = "linux")]
pub use linux::*;
#[cfg(not(target_os = "linux"))]
pub use other::*;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "otel-thread-ctx")]
use libdd_trace_protobuf::opentelemetry::proto::common::v1::any_value;
use libdd_trace_protobuf::opentelemetry::proto::common::v1::{AnyValue, ProcessContext};
fn find_attr<'a>(ctx: &'a ProcessContext, key: &str) -> Option<&'a AnyValue> {
ctx.resource
.as_ref()?
.attributes
.iter()
.find(|kv| kv.key == key)?
.value
.as_ref()
}
#[test]
fn tracer_metadata_equality() {
let a = TracerMetadata {
tracer_language: "python".into(),
..Default::default()
};
let b = TracerMetadata {
tracer_language: "python".into(),
..Default::default()
};
let c = TracerMetadata {
tracer_language: "ruby".into(),
..Default::default()
};
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn threadlocal_attrs_absent_when_keys_empty() {
let ctx = TracerMetadata::default().to_otel_process_ctx();
assert!(find_attr(&ctx, "threadlocal.schema_version").is_none());
assert!(find_attr(&ctx, "threadlocal.attribute_key_map").is_none());
}
#[cfg(feature = "otel-thread-ctx")]
#[test]
fn threadlocal_attrs_present_with_correct_values() {
let ctx = TracerMetadata {
threadlocal_attribute_keys: Some(vec![
"span.id".to_owned(),
"trace.id".to_owned(),
"custom.key".to_owned(),
]),
..Default::default()
}
.to_otel_process_ctx();
let schema_version = find_attr(&ctx, "threadlocal.schema_version")
.expect("threadlocal.schema_version should be present");
assert_eq!(
schema_version.value,
Some(any_value::Value::StringValue("tlsdesc_v1_dev".to_owned()))
);
let key_map = find_attr(&ctx, "threadlocal.attribute_key_map")
.expect("threadlocal.attribute_key_map should be present");
let array = match &key_map.value {
Some(any_value::Value::ArrayValue(a)) => a,
other => panic!("expected ArrayValue, got {:?}", other),
};
let keys: Vec<&str> = array
.values
.iter()
.map(|v| match &v.value {
Some(any_value::Value::StringValue(s)) => s.as_str(),
other => panic!("expected StringValue, got {:?}", other),
})
.collect();
assert_eq!(
keys,
[
"datadog.local_root_span_id",
"span.id",
"trace.id",
"custom.key"
]
);
}
}