From 79d90e9755b06b4a9fa4b27dd6ca124a3821b068 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Thu, 25 Jun 2026 16:27:57 +0200 Subject: [PATCH 1/5] Allow caller to override threadlocal schema version and add extra attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OTel process-context block emitted by TracerMetadata::to_otel_process_ctx used to hardcode 'threadlocal.schema_version' to 'tlsdesc_v1_dev' and offered no way to add further threadlocal.* attributes. Language-specific writers (e.g. a Node.js writer that publishes its own V8 layout constants under a different schema name) couldn't fully drive the process context through this path. Add two new fields on TracerMetadata, gated on the otel-thread-ctx feature: - threadlocal_schema_version: Option — explicit override of the schema attribute. Falls back to 'tlsdesc_v1_dev' when None and threadlocal_attribute_keys is Some, preserving backward-compatible behavior for existing callers. - threadlocal_extra_attributes: Vec<(String, ProcessContextAttrValue)> — additional KeyValues emitted in the process context. The value type is a small enum (String or Int) covering the cases needed for writer layout / runtime metadata. Emission of each piece is now independent: the schema attribute, the key map, and each extra are emitted iff their source field is set. --- libdd-library-config/src/tracer_metadata.rs | 130 +++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/libdd-library-config/src/tracer_metadata.rs b/libdd-library-config/src/tracer_metadata.rs index 03d0ae9b4c..9b277a912e 100644 --- a/libdd-library-config/src/tracer_metadata.rs +++ b/libdd-library-config/src/tracer_metadata.rs @@ -3,6 +3,18 @@ use libdd_trace_protobuf::opentelemetry::proto as otel_proto; use std::default::Default; +/// Value of an additional OTel process-context attribute. Mirrors the small subset of +/// `opentelemetry::proto::common::v1::AnyValue` variants we support for caller-supplied threadlocal +/// extras — string and 64-bit integer, since the only consumers so far are textual schema +/// identifiers and small numeric layout constants (e.g. struct offsets, pointer widths). +#[cfg(feature = "otel-thread-ctx")] +#[derive(serde::Serialize, Debug, PartialEq, Eq, Hash, Clone)] +#[serde(rename_all = "snake_case", tag = "type", content = "value")] +pub enum ProcessContextAttrValue { + String(String), + Int(i64), +} + /// This struct MUST be backward compatible. #[derive(serde::Serialize, Debug, PartialEq, Eq, Hash)] pub struct TracerMetadata { @@ -48,6 +60,25 @@ pub struct TracerMetadata { #[cfg(feature = "otel-thread-ctx")] #[serde(skip)] pub threadlocal_attribute_keys: Option>, + + /// Value of the `threadlocal.schema_version` attribute emitted in the OTel process context. + /// Identifies the on-the-wire record schema the writer publishes (e.g. `"tlsdesc_v1_dev"` for + /// libdatadog's own TLSDESC-based writer, `"nodejs_v1_dev"` for a Node.js writer). Defaults to + /// `"tlsdesc_v1_dev"` when `None`. + /// + /// Only emitted when `threadlocal_attribute_keys` is `Some`. Ignored for (de)serialization. + #[cfg(feature = "otel-thread-ctx")] + #[serde(skip)] + pub threadlocal_schema_version: Option, + + /// Extra OTel process-context attributes the threadlocal writer wants to publish alongside the + /// key map (e.g. language-runtime layout constants that the reader needs to walk from a + /// discovery TLS symbol into the record). Each entry is emitted verbatim as a KeyValue. + /// + /// Only emitted when `threadlocal_attribute_keys` is `Some`. Ignored for (de)serialization. + #[cfg(feature = "otel-thread-ctx")] + #[serde(skip)] + pub threadlocal_extra_attributes: Vec<(String, ProcessContextAttrValue)>, } impl Default for TracerMetadata { @@ -65,6 +96,10 @@ impl Default for TracerMetadata { container_id: None, #[cfg(feature = "otel-thread-ctx")] threadlocal_attribute_keys: None, + #[cfg(feature = "otel-thread-ctx")] + threadlocal_schema_version: None, + #[cfg(feature = "otel-thread-ctx")] + threadlocal_extra_attributes: Vec::new(), } } } @@ -113,6 +148,10 @@ impl TracerMetadata { container_id, #[cfg(feature = "otel-thread-ctx")] threadlocal_attribute_keys, + #[cfg(feature = "otel-thread-ctx")] + threadlocal_schema_version, + #[cfg(feature = "otel-thread-ctx")] + threadlocal_extra_attributes, } = self; #[cfg_attr(not(feature = "otel-thread-ctx"), allow(unused_mut))] @@ -132,7 +171,9 @@ impl TracerMetadata { if let Some(threadlocal_attribute_keys) = threadlocal_attribute_keys.as_ref() { attributes.push(key_value( "threadlocal.schema_version", - "tlsdesc_v1_dev".to_owned(), + threadlocal_schema_version + .clone() + .unwrap_or_else(|| "tlsdesc_v1_dev".to_owned()), )); attributes.push(KeyValue { @@ -152,6 +193,18 @@ impl TracerMetadata { }), key_ref: 0, }); + + for (k, v) in threadlocal_extra_attributes { + let value = match v { + ProcessContextAttrValue::String(s) => any_value::Value::StringValue(s.clone()), + ProcessContextAttrValue::Int(i) => any_value::Value::IntValue(*i), + }; + attributes.push(KeyValue { + key: k.clone(), + value: Some(AnyValue { value: Some(value) }), + key_ref: 0, + }); + } } ProcessContext { @@ -320,4 +373,79 @@ mod tests { ] ); } + + #[cfg(feature = "otel-thread-ctx")] + #[test] + fn threadlocal_schema_version_override() { + let ctx = TracerMetadata { + threadlocal_attribute_keys: Some(vec![]), + threadlocal_schema_version: Some("nodejs_v1_dev".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("nodejs_v1_dev".to_owned())) + ); + } + + #[cfg(feature = "otel-thread-ctx")] + #[test] + fn threadlocal_extra_attributes_are_emitted() { + let ctx = TracerMetadata { + threadlocal_attribute_keys: Some(vec!["k".to_owned()]), + threadlocal_extra_attributes: vec![ + ( + "threadlocal.wrapped_object_offset".to_owned(), + ProcessContextAttrValue::Int(24), + ), + ( + "threadlocal.tagged_size".to_owned(), + ProcessContextAttrValue::Int(8), + ), + ( + "threadlocal.runtime.name".to_owned(), + ProcessContextAttrValue::String("nodejs".to_owned()), + ), + ], + ..Default::default() + } + .to_otel_process_ctx(); + + assert_eq!( + find_attr(&ctx, "threadlocal.wrapped_object_offset").and_then(|v| v.value.clone()), + Some(any_value::Value::IntValue(24)) + ); + assert_eq!( + find_attr(&ctx, "threadlocal.tagged_size").and_then(|v| v.value.clone()), + Some(any_value::Value::IntValue(8)) + ); + assert_eq!( + find_attr(&ctx, "threadlocal.runtime.name").and_then(|v| v.value.clone()), + Some(any_value::Value::StringValue("nodejs".to_owned())) + ); + } + + #[cfg(feature = "otel-thread-ctx")] + #[test] + fn threadlocal_schema_and_extras_ignored_without_key_map() { + // Without threadlocal_attribute_keys, no threadlocal block is emitted at all — neither the + // schema override nor any extras leak out on their own. + let ctx = TracerMetadata { + threadlocal_attribute_keys: None, + threadlocal_schema_version: Some("nodejs_v1_dev".to_owned()), + threadlocal_extra_attributes: vec![( + "threadlocal.wrapped_object_offset".to_owned(), + ProcessContextAttrValue::Int(24), + )], + ..Default::default() + } + .to_otel_process_ctx(); + + assert!(find_attr(&ctx, "threadlocal.schema_version").is_none()); + assert!(find_attr(&ctx, "threadlocal.wrapped_object_offset").is_none()); + } } From 4a6ddc29114232a1f26ece380744f6776b28fa4c Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Fri, 26 Jun 2026 12:55:20 +0200 Subject: [PATCH 2/5] Apply suggestion from code review Co-authored-by: Yann Hamdaoui --- libdd-library-config/src/tracer_metadata.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libdd-library-config/src/tracer_metadata.rs b/libdd-library-config/src/tracer_metadata.rs index 9b277a912e..1e0ce6c53c 100644 --- a/libdd-library-config/src/tracer_metadata.rs +++ b/libdd-library-config/src/tracer_metadata.rs @@ -61,9 +61,8 @@ pub struct TracerMetadata { #[serde(skip)] pub threadlocal_attribute_keys: Option>, - /// Value of the `threadlocal.schema_version` attribute emitted in the OTel process context. - /// Identifies the on-the-wire record schema the writer publishes (e.g. `"tlsdesc_v1_dev"` for - /// libdatadog's own TLSDESC-based writer, `"nodejs_v1_dev"` for a Node.js writer). Defaults to + /// Identifies the record schema the writer publishes (e.g. `"tlsdesc_v1_dev"` for + /// libdatadog's own writer, `"nodejs_v1_dev"` for a Node.js writer). Defaults to /// `"tlsdesc_v1_dev"` when `None`. /// /// Only emitted when `threadlocal_attribute_keys` is `Some`. Ignored for (de)serialization. From 4811a4466bde1750afb81d62ff0bab6645ccc1bc Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Fri, 26 Jun 2026 12:56:01 +0200 Subject: [PATCH 3/5] Apply suggestion from code review Co-authored-by: Yann Hamdaoui --- libdd-library-config/src/tracer_metadata.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libdd-library-config/src/tracer_metadata.rs b/libdd-library-config/src/tracer_metadata.rs index 1e0ce6c53c..de01647314 100644 --- a/libdd-library-config/src/tracer_metadata.rs +++ b/libdd-library-config/src/tracer_metadata.rs @@ -71,8 +71,7 @@ pub struct TracerMetadata { pub threadlocal_schema_version: Option, /// Extra OTel process-context attributes the threadlocal writer wants to publish alongside the - /// key map (e.g. language-runtime layout constants that the reader needs to walk from a - /// discovery TLS symbol into the record). Each entry is emitted verbatim as a KeyValue. + /// key map (e.g. language-runtime layout constants that the reader needs to know about). Each entry is emitted verbatim as a KeyValue. /// /// Only emitted when `threadlocal_attribute_keys` is `Some`. Ignored for (de)serialization. #[cfg(feature = "otel-thread-ctx")] From a805ec2cd180321fe20ea65b8ea94b9bb8dfa32a Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Fri, 26 Jun 2026 12:57:32 +0200 Subject: [PATCH 4/5] Use any_value::Value directly instead of a custom enum Per review feedback: dropping the ProcessContextAttrValue enum (String + Int) avoids the "what about bool/double/array" extension problem and reuses the otel_proto type that's already part of this file's surface. The struct also has to drop its Eq+Hash derive, since any_value::Value contains f64 (no Eq) and arrays (no Hash); nothing in-tree uses TracerMetadata as a hash-map key, and PartialEq is preserved. --- libdd-library-config/src/tracer_metadata.rs | 32 ++++++--------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/libdd-library-config/src/tracer_metadata.rs b/libdd-library-config/src/tracer_metadata.rs index de01647314..e13d6a3152 100644 --- a/libdd-library-config/src/tracer_metadata.rs +++ b/libdd-library-config/src/tracer_metadata.rs @@ -3,20 +3,8 @@ use libdd_trace_protobuf::opentelemetry::proto as otel_proto; use std::default::Default; -/// Value of an additional OTel process-context attribute. Mirrors the small subset of -/// `opentelemetry::proto::common::v1::AnyValue` variants we support for caller-supplied threadlocal -/// extras — string and 64-bit integer, since the only consumers so far are textual schema -/// identifiers and small numeric layout constants (e.g. struct offsets, pointer widths). -#[cfg(feature = "otel-thread-ctx")] -#[derive(serde::Serialize, Debug, PartialEq, Eq, Hash, Clone)] -#[serde(rename_all = "snake_case", tag = "type", content = "value")] -pub enum ProcessContextAttrValue { - String(String), - Int(i64), -} - /// This struct MUST be backward compatible. -#[derive(serde::Serialize, Debug, PartialEq, Eq, Hash)] +#[derive(serde::Serialize, Debug, PartialEq)] pub struct TracerMetadata { /// Version of the schema. pub schema_version: u8, @@ -76,7 +64,7 @@ pub struct TracerMetadata { /// Only emitted when `threadlocal_attribute_keys` is `Some`. Ignored for (de)serialization. #[cfg(feature = "otel-thread-ctx")] #[serde(skip)] - pub threadlocal_extra_attributes: Vec<(String, ProcessContextAttrValue)>, + pub threadlocal_extra_attributes: Vec<(String, otel_proto::common::v1::any_value::Value)>, } impl Default for TracerMetadata { @@ -193,13 +181,11 @@ impl TracerMetadata { }); for (k, v) in threadlocal_extra_attributes { - let value = match v { - ProcessContextAttrValue::String(s) => any_value::Value::StringValue(s.clone()), - ProcessContextAttrValue::Int(i) => any_value::Value::IntValue(*i), - }; attributes.push(KeyValue { key: k.clone(), - value: Some(AnyValue { value: Some(value) }), + value: Some(AnyValue { + value: Some(v.clone()), + }), key_ref: 0, }); } @@ -398,15 +384,15 @@ mod tests { threadlocal_extra_attributes: vec![ ( "threadlocal.wrapped_object_offset".to_owned(), - ProcessContextAttrValue::Int(24), + any_value::Value::IntValue(24), ), ( "threadlocal.tagged_size".to_owned(), - ProcessContextAttrValue::Int(8), + any_value::Value::IntValue(8), ), ( "threadlocal.runtime.name".to_owned(), - ProcessContextAttrValue::String("nodejs".to_owned()), + any_value::Value::StringValue("nodejs".to_owned()), ), ], ..Default::default() @@ -437,7 +423,7 @@ mod tests { threadlocal_schema_version: Some("nodejs_v1_dev".to_owned()), threadlocal_extra_attributes: vec![( "threadlocal.wrapped_object_offset".to_owned(), - ProcessContextAttrValue::Int(24), + any_value::Value::IntValue(24), )], ..Default::default() } From 6e826b19529b23727eae989adeef010e0781f9d8 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Fri, 26 Jun 2026 13:01:10 +0200 Subject: [PATCH 5/5] Group threadlocal fields into a ThreadLocalMetadata substruct Per review: a single Option field on TracerMetadata replaces the three loose threadlocal_* fields. The 'illegal state' of schema/extras set without a key map is now unrepresentable by construction. Field names inside the substruct drop the threadlocal_ prefix since the namespace lives in the type name. --- libdd-library-config/src/tracer_metadata.rs | 158 +++++++++----------- 1 file changed, 72 insertions(+), 86 deletions(-) diff --git a/libdd-library-config/src/tracer_metadata.rs b/libdd-library-config/src/tracer_metadata.rs index e13d6a3152..a919470027 100644 --- a/libdd-library-config/src/tracer_metadata.rs +++ b/libdd-library-config/src/tracer_metadata.rs @@ -3,6 +3,32 @@ use libdd_trace_protobuf::opentelemetry::proto as otel_proto; use std::default::Default; +/// Thread-level context metadata the tracer wants to publish as part of the OTel process context. +/// Set [TracerMetadata::threadlocal_metadata] to `Some(_)` to enable the threadlocal section; the +/// fields inside then drive its contents. +#[cfg(feature = "otel-thread-ctx")] +#[derive(Debug, PartialEq, Default)] +pub struct ThreadLocalMetadata { + /// Ordered list of attribute key names for thread-level context records. Key indices from + /// thread context records index into this table. + /// + /// The first key is automatically set to `datadog.local_root_span_id` in the OTel process + /// context, because the thread context handling elsewhere in libdatadog relies on this key's + /// index to be zero. Only set additional keys here; the root span id is considered to always + /// be present implicitly. + pub attribute_keys: Vec, + + /// Identifies the record schema the writer publishes (e.g. `"tlsdesc_v1_dev"` for + /// libdatadog's own writer, `"nodejs_v1_dev"` for a Node.js writer). Defaults to + /// `"tlsdesc_v1_dev"` when `None`. + pub schema_version: Option, + + /// Extra OTel process-context attributes the threadlocal writer wants to publish alongside the + /// key map (e.g. language-runtime layout constants that the reader needs to know about). Each + /// entry is emitted verbatim as a KeyValue. + pub extra_attributes: Vec<(String, otel_proto::common::v1::any_value::Value)>, +} + /// This struct MUST be backward compatible. #[derive(serde::Serialize, Debug, PartialEq)] pub struct TracerMetadata { @@ -33,38 +59,13 @@ pub struct TracerMetadata { /// Container id seen by the application. #[serde(skip_serializing_if = "Option::is_none")] pub container_id: Option, - /// Ordered list of attribute key names for thread-level context records. Key indices from - /// thread context records index into this table. Set to `None` to disable thread-level related - /// attributes to the process-level context. - /// - /// If set to `Some`, the first key will be automatically set to `datadog.local_root_span_id` - /// in the OTel process context, because the thread context handling elsewhere in libdatadog - /// relies on this key's index to be zero. Only set additional keys in - /// `threadlocal_attribute_keys`; the root span id is considered to always be here implicitly. - /// - /// This field is specific to OTel process context. It is ignored for (de)serialization, and is - /// only used when converting to an OTel process context in + /// Thread-level context metadata; emit a `threadlocal.*` block in the OTel process context + /// when `Some`. `None` disables the section entirely. See [ThreadLocalMetadata]. Ignored for + /// (de)serialization; only used when converting to an OTel process context in /// [TracerMetadata::to_otel_process_ctx]. #[cfg(feature = "otel-thread-ctx")] #[serde(skip)] - pub threadlocal_attribute_keys: Option>, - - /// Identifies the record schema the writer publishes (e.g. `"tlsdesc_v1_dev"` for - /// libdatadog's own writer, `"nodejs_v1_dev"` for a Node.js writer). Defaults to - /// `"tlsdesc_v1_dev"` when `None`. - /// - /// Only emitted when `threadlocal_attribute_keys` is `Some`. Ignored for (de)serialization. - #[cfg(feature = "otel-thread-ctx")] - #[serde(skip)] - pub threadlocal_schema_version: Option, - - /// Extra OTel process-context attributes the threadlocal writer wants to publish alongside the - /// key map (e.g. language-runtime layout constants that the reader needs to know about). Each entry is emitted verbatim as a KeyValue. - /// - /// Only emitted when `threadlocal_attribute_keys` is `Some`. Ignored for (de)serialization. - #[cfg(feature = "otel-thread-ctx")] - #[serde(skip)] - pub threadlocal_extra_attributes: Vec<(String, otel_proto::common::v1::any_value::Value)>, + pub threadlocal_metadata: Option, } impl Default for TracerMetadata { @@ -81,11 +82,7 @@ impl Default for TracerMetadata { process_tags: None, container_id: None, #[cfg(feature = "otel-thread-ctx")] - threadlocal_attribute_keys: None, - #[cfg(feature = "otel-thread-ctx")] - threadlocal_schema_version: None, - #[cfg(feature = "otel-thread-ctx")] - threadlocal_extra_attributes: Vec::new(), + threadlocal_metadata: None, } } } @@ -133,11 +130,7 @@ impl TracerMetadata { process_tags, container_id, #[cfg(feature = "otel-thread-ctx")] - threadlocal_attribute_keys, - #[cfg(feature = "otel-thread-ctx")] - threadlocal_schema_version, - #[cfg(feature = "otel-thread-ctx")] - threadlocal_extra_attributes, + threadlocal_metadata, } = self; #[cfg_attr(not(feature = "otel-thread-ctx"), allow(unused_mut))] @@ -154,10 +147,15 @@ impl TracerMetadata { ]; #[cfg(feature = "otel-thread-ctx")] - if let Some(threadlocal_attribute_keys) = threadlocal_attribute_keys.as_ref() { + if let Some(ThreadLocalMetadata { + attribute_keys, + schema_version, + extra_attributes, + }) = threadlocal_metadata.as_ref() + { attributes.push(key_value( "threadlocal.schema_version", - threadlocal_schema_version + schema_version .clone() .unwrap_or_else(|| "tlsdesc_v1_dev".to_owned()), )); @@ -171,7 +169,7 @@ impl TracerMetadata { "datadog.local_root_span_id".to_owned(), )), }) - .chain(threadlocal_attribute_keys.iter().map(|k| AnyValue { + .chain(attribute_keys.iter().map(|k| AnyValue { value: Some(any_value::Value::StringValue(k.clone())), })) .collect(), @@ -180,7 +178,7 @@ impl TracerMetadata { key_ref: 0, }); - for (k, v) in threadlocal_extra_attributes { + for (k, v) in extra_attributes { attributes.push(KeyValue { key: k.clone(), value: Some(AnyValue { @@ -304,7 +302,7 @@ mod tests { } #[test] - fn threadlocal_attrs_absent_when_keys_empty() { + fn threadlocal_attrs_absent_when_metadata_none() { let ctx = TracerMetadata::default().to_otel_process_ctx(); assert!(find_attr(&ctx, "threadlocal.schema_version").is_none()); @@ -315,11 +313,14 @@ mod tests { #[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(), - ]), + threadlocal_metadata: Some(ThreadLocalMetadata { + attribute_keys: vec![ + "span.id".to_owned(), + "trace.id".to_owned(), + "custom.key".to_owned(), + ], + ..Default::default() + }), ..Default::default() } .to_otel_process_ctx(); @@ -362,8 +363,10 @@ mod tests { #[test] fn threadlocal_schema_version_override() { let ctx = TracerMetadata { - threadlocal_attribute_keys: Some(vec![]), - threadlocal_schema_version: Some("nodejs_v1_dev".to_owned()), + threadlocal_metadata: Some(ThreadLocalMetadata { + schema_version: Some("nodejs_v1_dev".to_owned()), + ..Default::default() + }), ..Default::default() } .to_otel_process_ctx(); @@ -380,21 +383,24 @@ mod tests { #[test] fn threadlocal_extra_attributes_are_emitted() { let ctx = TracerMetadata { - threadlocal_attribute_keys: Some(vec!["k".to_owned()]), - threadlocal_extra_attributes: vec![ - ( - "threadlocal.wrapped_object_offset".to_owned(), - any_value::Value::IntValue(24), - ), - ( - "threadlocal.tagged_size".to_owned(), - any_value::Value::IntValue(8), - ), - ( - "threadlocal.runtime.name".to_owned(), - any_value::Value::StringValue("nodejs".to_owned()), - ), - ], + threadlocal_metadata: Some(ThreadLocalMetadata { + attribute_keys: vec!["k".to_owned()], + extra_attributes: vec![ + ( + "threadlocal.wrapped_object_offset".to_owned(), + any_value::Value::IntValue(24), + ), + ( + "threadlocal.tagged_size".to_owned(), + any_value::Value::IntValue(8), + ), + ( + "threadlocal.runtime.name".to_owned(), + any_value::Value::StringValue("nodejs".to_owned()), + ), + ], + ..Default::default() + }), ..Default::default() } .to_otel_process_ctx(); @@ -412,24 +418,4 @@ mod tests { Some(any_value::Value::StringValue("nodejs".to_owned())) ); } - - #[cfg(feature = "otel-thread-ctx")] - #[test] - fn threadlocal_schema_and_extras_ignored_without_key_map() { - // Without threadlocal_attribute_keys, no threadlocal block is emitted at all — neither the - // schema override nor any extras leak out on their own. - let ctx = TracerMetadata { - threadlocal_attribute_keys: None, - threadlocal_schema_version: Some("nodejs_v1_dev".to_owned()), - threadlocal_extra_attributes: vec![( - "threadlocal.wrapped_object_offset".to_owned(), - any_value::Value::IntValue(24), - )], - ..Default::default() - } - .to_otel_process_ctx(); - - assert!(find_attr(&ctx, "threadlocal.schema_version").is_none()); - assert!(find_attr(&ctx, "threadlocal.wrapped_object_offset").is_none()); - } }