-
Notifications
You must be signed in to change notification settings - Fork 1
feat!: expose ThreadLocalMetadata via process_discovery #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ use napi::{Error, Status}; | |
| use napi_derive::napi; | ||
|
|
||
| use libdd_library_config::tracer_metadata; | ||
| use libdd_trace_protobuf::opentelemetry::proto::common::v1::any_value; | ||
|
|
||
| #[napi] | ||
| pub struct NapiAnonymousFileHandle { | ||
|
|
@@ -11,6 +12,44 @@ pub struct NapiAnonymousFileHandle { | |
| #[napi] | ||
| impl NapiAnonymousFileHandle {} | ||
|
|
||
| /// Additional OTel process-context attribute the threadlocal writer wants to | ||
| /// publish alongside the key map (e.g. language-runtime layout constants). Set | ||
| /// exactly one of `string_value` / `int_value` — the other variants of OTel's | ||
| /// `AnyValue` (bool, double, bytes, array, kvlist) are not yet exposed. | ||
| /// Passing both set or neither set is rejected as invalid input. | ||
| #[derive(Clone)] | ||
| #[napi(object)] | ||
| pub struct ExtraAttribute { | ||
| pub key: String, | ||
| pub string_value: Option<String>, | ||
| pub int_value: Option<i64>, | ||
| } | ||
|
|
||
| /// Thread-level context metadata the tracer wants to publish as part of the | ||
| /// OTel process context. When present on a [`TracerMetadata`], drives the | ||
| /// `threadlocal.*` block in the emitted process context; when absent, no such | ||
| /// block is emitted. | ||
| #[derive(Clone)] | ||
| #[napi(object)] | ||
| pub struct ThreadLocalMetadata { | ||
| /// Ordered list of attribute key names for thread-level OTEP-4947 context | ||
| /// records. Wire key indices index into this list. libdatadog implicitly | ||
| /// prepends `datadog.local_root_span_id` at wire index 0, so entry 0 here | ||
| /// is wire key index 1. | ||
| pub attribute_keys: Vec<String>, | ||
|
|
||
| /// Value of the `threadlocal.schema_version` attribute. Identifies the | ||
| /// on-the-wire record schema (e.g. `"tlsdesc_v1_dev"` for libdatadog's own | ||
| /// TLSDESC writer, `"nodejs_v1_dev"` for a Node.js writer). Defaults to | ||
| /// `"tlsdesc_v1_dev"` when omitted. | ||
| pub schema_version: Option<String>, | ||
|
|
||
| /// Extra `threadlocal.*` attributes to publish alongside the key map (e.g. | ||
| /// V8 layout constants a Node.js reader needs to walk from the discovery | ||
| /// TLS symbol into the record). | ||
| pub extra_attributes: Vec<ExtraAttribute>, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be optional? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would think an empty vec is ok here, unless you really need to differentiate |
||
| } | ||
|
|
||
| #[napi(constructor)] | ||
| pub struct TracerMetadata { | ||
| pub runtime_id: Option<String>, | ||
|
|
@@ -21,16 +60,50 @@ pub struct TracerMetadata { | |
| pub service_version: Option<String>, | ||
| pub process_tags: Option<String>, | ||
| pub container_id: Option<String>, | ||
| /// Ordered list of attribute key names for thread-level OTEP-4947 | ||
| /// context records. Key indices on the wire index into this list. | ||
| /// libdatadog's OTel process-context conversion prepends the | ||
| /// implicit `datadog.local_root_span_id` entry at wire index 0, so | ||
| /// callers should only set their additional keys here — entry 0 in | ||
| /// this list corresponds to wire key index 1. | ||
| /// | ||
| /// `null`/omitted (the default) disables the thread-context-related | ||
| /// attributes in the OTel process context entirely. | ||
| pub threadlocal_attribute_keys: Option<Vec<String>>, | ||
| /// Optional thread-level context metadata; see [`ThreadLocalMetadata`]. | ||
| /// `null`/omitted (the default) disables the `threadlocal.*` block in the | ||
| /// emitted OTel process context entirely. | ||
| pub threadlocal_metadata: Option<ThreadLocalMetadata>, | ||
| } | ||
|
|
||
| fn convert_extra_attribute(ea: &ExtraAttribute) -> napi::Result<(String, any_value::Value)> { | ||
| let value = match (&ea.string_value, ea.int_value) { | ||
| (Some(s), None) => any_value::Value::StringValue(s.clone()), | ||
| (None, Some(i)) => any_value::Value::IntValue(i), | ||
| (Some(_), Some(_)) => { | ||
| return Err(Error::new( | ||
| Status::InvalidArg, | ||
| format!( | ||
| "ExtraAttribute {:?}: exactly one of stringValue / intValue must be set, both are", | ||
| ea.key, | ||
| ), | ||
| )); | ||
| } | ||
| (None, None) => { | ||
| return Err(Error::new( | ||
| Status::InvalidArg, | ||
| format!( | ||
| "ExtraAttribute {:?}: exactly one of stringValue / intValue must be set, neither is", | ||
| ea.key, | ||
| ), | ||
| )); | ||
| } | ||
| }; | ||
| Ok((ea.key.clone(), value)) | ||
| } | ||
|
|
||
| fn convert_threadlocal_metadata( | ||
| tlm: &ThreadLocalMetadata, | ||
| ) -> napi::Result<tracer_metadata::ThreadLocalMetadata> { | ||
| Ok(tracer_metadata::ThreadLocalMetadata { | ||
| attribute_keys: tlm.attribute_keys.clone(), | ||
| schema_version: tlm.schema_version.clone(), | ||
| extra_attributes: tlm | ||
| .extra_attributes | ||
| .iter() | ||
| .map(convert_extra_attribute) | ||
| .collect::<napi::Result<_>>()?, | ||
| }) | ||
| } | ||
|
|
||
| #[napi] | ||
|
|
@@ -46,7 +119,11 @@ pub fn store_metadata(data: &TracerMetadata) -> napi::Result<NapiAnonymousFileHa | |
| service_version: data.service_version.clone(), | ||
| process_tags: data.process_tags.clone(), | ||
| container_id: data.container_id.clone(), | ||
| threadlocal_attribute_keys: data.threadlocal_attribute_keys.clone(), | ||
| threadlocal_metadata: data | ||
| .threadlocal_metadata | ||
| .as_ref() | ||
| .map(convert_threadlocal_metadata) | ||
| .transpose()?, | ||
| }); | ||
|
|
||
| match res { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we should only have one, then this should be an enum.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I suppose because it has to be mapped to a JS object? In that case please ignore my comment.