diff options
Diffstat (limited to 'runtime/ops/otel.rs')
-rw-r--r-- | runtime/ops/otel.rs | 297 |
1 files changed, 204 insertions, 93 deletions
diff --git a/runtime/ops/otel.rs b/runtime/ops/otel.rs index b32764d7f..61a7b0ef0 100644 --- a/runtime/ops/otel.rs +++ b/runtime/ops/otel.rs @@ -1,8 +1,8 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use crate::tokio_util::create_basic_runtime; +use deno_core::anyhow; use deno_core::anyhow::anyhow; -use deno_core::anyhow::{self}; use deno_core::futures::channel::mpsc; use deno_core::futures::channel::mpsc::UnboundedSender; use deno_core::futures::future::BoxFuture; @@ -23,7 +23,6 @@ use opentelemetry::trace::SpanKind; use opentelemetry::trace::Status as SpanStatus; use opentelemetry::trace::TraceFlags; use opentelemetry::trace::TraceId; -use opentelemetry::InstrumentationScope; use opentelemetry::Key; use opentelemetry::KeyValue; use opentelemetry::StringValue; @@ -63,11 +62,15 @@ deno_core::extension!( deno_otel, ops = [ op_otel_log, + op_otel_instrumentation_scope_create_and_enter, + op_otel_instrumentation_scope_enter, + op_otel_instrumentation_scope_enter_builtin, op_otel_span_start, op_otel_span_continue, op_otel_span_attribute, op_otel_span_attribute2, op_otel_span_attribute3, + op_otel_span_set_dropped, op_otel_span_flush, ], ); @@ -303,6 +306,10 @@ mod hyper_client { static OTEL_PROCESSORS: OnceCell<(SpanProcessor, LogProcessor)> = OnceCell::new(); +static BUILT_IN_INSTRUMENTATION_SCOPE: OnceCell< + opentelemetry::InstrumentationScope, +> = OnceCell::new(); + pub fn init(config: OtelConfig) -> anyhow::Result<()> { // Parse the `OTEL_EXPORTER_OTLP_PROTOCOL` variable. The opentelemetry_* // crates don't do this automatically. @@ -390,6 +397,14 @@ pub fn init(config: OtelConfig) -> anyhow::Result<()> { .set((span_processor, log_processor)) .map_err(|_| anyhow!("failed to init otel"))?; + let builtin_instrumentation_scope = + opentelemetry::InstrumentationScope::builder("deno") + .with_version(config.runtime_version.clone()) + .build(); + BUILT_IN_INSTRUMENTATION_SCOPE + .set(builtin_instrumentation_scope) + .map_err(|_| anyhow!("failed to init otel"))?; + Ok(()) } @@ -458,16 +473,160 @@ pub fn handle_log(record: &log::Record) { log_processor.emit( &mut log_record, - &InstrumentationScope::builder("deno").build(), + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap(), ); } +fn parse_trace_id( + scope: &mut v8::HandleScope<'_>, + trace_id: v8::Local<'_, v8::Value>, +) -> TraceId { + if let Ok(string) = trace_id.try_cast() { + let value_view = v8::ValueView::new(scope, string); + match value_view.data() { + v8::ValueViewData::OneByte(bytes) => { + TraceId::from_hex(&String::from_utf8_lossy(bytes)) + .unwrap_or(TraceId::INVALID) + } + + _ => TraceId::INVALID, + } + } else if let Ok(uint8array) = trace_id.try_cast::<v8::Uint8Array>() { + let data = uint8array.data(); + let byte_length = uint8array.byte_length(); + if byte_length != 16 { + return TraceId::INVALID; + } + // SAFETY: We have ensured that the byte length is 16, so it is safe to + // cast the data to an array of 16 bytes. + let bytes = unsafe { &*(data as *const u8 as *const [u8; 16]) }; + TraceId::from_bytes(*bytes) + } else { + TraceId::INVALID + } +} + +fn parse_span_id( + scope: &mut v8::HandleScope<'_>, + span_id: v8::Local<'_, v8::Value>, +) -> SpanId { + if let Ok(string) = span_id.try_cast() { + let value_view = v8::ValueView::new(scope, string); + match value_view.data() { + v8::ValueViewData::OneByte(bytes) => { + SpanId::from_hex(&String::from_utf8_lossy(bytes)) + .unwrap_or(SpanId::INVALID) + } + _ => SpanId::INVALID, + } + } else if let Ok(uint8array) = span_id.try_cast::<v8::Uint8Array>() { + let data = uint8array.data(); + let byte_length = uint8array.byte_length(); + if byte_length != 8 { + return SpanId::INVALID; + } + // SAFETY: We have ensured that the byte length is 8, so it is safe to + // cast the data to an array of 8 bytes. + let bytes = unsafe { &*(data as *const u8 as *const [u8; 8]) }; + SpanId::from_bytes(*bytes) + } else { + SpanId::INVALID + } +} + +macro_rules! attr { + ($scope:ident, $attributes:expr $(=> $dropped_attributes_count:expr)?, $name:expr, $value:expr) => { + let name = if let Ok(name) = $name.try_cast() { + let view = v8::ValueView::new($scope, name); + match view.data() { + v8::ValueViewData::OneByte(bytes) => { + Some(String::from_utf8_lossy(bytes).into_owned()) + } + v8::ValueViewData::TwoByte(bytes) => { + Some(String::from_utf16_lossy(bytes)) + } + } + } else { + None + }; + let value = if let Ok(string) = $value.try_cast::<v8::String>() { + Some(Value::String(StringValue::from({ + let x = v8::ValueView::new($scope, string); + match x.data() { + v8::ValueViewData::OneByte(bytes) => { + String::from_utf8_lossy(bytes).into_owned() + } + v8::ValueViewData::TwoByte(bytes) => String::from_utf16_lossy(bytes), + } + }))) + } else if let Ok(number) = $value.try_cast::<v8::Number>() { + Some(Value::F64(number.value())) + } else if let Ok(boolean) = $value.try_cast::<v8::Boolean>() { + Some(Value::Bool(boolean.is_true())) + } else if let Ok(bigint) = $value.try_cast::<v8::BigInt>() { + let (i64_value, _lossless) = bigint.i64_value(); + Some(Value::I64(i64_value)) + } else { + None + }; + if let (Some(name), Some(value)) = (name, value) { + $attributes.push(KeyValue::new(name, value)); + } + $( + else { + $dropped_attributes_count += 1; + } + )? + }; +} + +#[derive(Debug, Clone)] +struct InstrumentationScope(opentelemetry::InstrumentationScope); + +impl deno_core::GarbageCollected for InstrumentationScope {} + +#[op2] +#[cppgc] +fn op_otel_instrumentation_scope_create_and_enter( + state: &mut OpState, + #[string] name: String, + #[string] version: Option<String>, + #[string] schema_url: Option<String>, +) -> InstrumentationScope { + let mut builder = opentelemetry::InstrumentationScope::builder(name); + if let Some(version) = version { + builder = builder.with_version(version); + } + if let Some(schema_url) = schema_url { + builder = builder.with_schema_url(schema_url); + } + let scope = InstrumentationScope(builder.build()); + state.put(scope.clone()); + scope +} + +#[op2(fast)] +fn op_otel_instrumentation_scope_enter( + state: &mut OpState, + #[cppgc] scope: &InstrumentationScope, +) { + state.put(scope.clone()); +} + +#[op2(fast)] +fn op_otel_instrumentation_scope_enter_builtin(state: &mut OpState) { + state.put(InstrumentationScope( + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap().clone(), + )); +} + #[op2(fast)] fn op_otel_log( + scope: &mut v8::HandleScope<'_>, #[string] message: String, #[smi] level: i32, - #[string] trace_id: &str, - #[string] span_id: &str, + trace_id: v8::Local<'_, v8::Value>, + span_id: v8::Local<'_, v8::Value>, #[smi] trace_flags: u8, ) { let Some((_, log_processor)) = OTEL_PROCESSORS.get() else { @@ -483,15 +642,16 @@ fn op_otel_log( 3.. => Severity::Error, }; + let trace_id = parse_trace_id(scope, trace_id); + let span_id = parse_span_id(scope, span_id); + let mut log_record = LogRecord::default(); log_record.set_observed_timestamp(SystemTime::now()); log_record.set_body(message.into()); log_record.set_severity_number(severity); log_record.set_severity_text(severity.name()); - if let (Ok(trace_id), Ok(span_id)) = - (TraceId::from_hex(trace_id), SpanId::from_hex(span_id)) - { + if trace_id != TraceId::INVALID && span_id != SpanId::INVALID { log_record.set_trace_context( trace_id, span_id, @@ -501,7 +661,7 @@ fn op_otel_log( log_processor.emit( &mut log_record, - &InstrumentationScope::builder("deno").build(), + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap(), ); } @@ -527,40 +687,23 @@ fn op_otel_span_start<'s>( span_processor.on_end(temporary_span.0); }; - let trace_id = { - let x = v8::ValueView::new(scope, trace_id.try_cast()?); - match x.data() { - v8::ValueViewData::OneByte(bytes) => { - TraceId::from_hex(&String::from_utf8_lossy(bytes))? - } - _ => return Err(anyhow!("invalid trace_id")), - } + let Some(InstrumentationScope(instrumentation_scope)) = + state.try_borrow::<InstrumentationScope>() + else { + return Err(anyhow!("instrumentation scope not available")); }; - let span_id = { - let x = v8::ValueView::new(scope, span_id.try_cast()?); - match x.data() { - v8::ValueViewData::OneByte(bytes) => { - SpanId::from_hex(&String::from_utf8_lossy(bytes))? - } - _ => return Err(anyhow!("invalid span_id")), - } - }; + let trace_id = parse_trace_id(scope, trace_id); + if trace_id == TraceId::INVALID { + return Err(anyhow!("invalid trace_id")); + } - let parent_span_id = { - let x = v8::ValueView::new(scope, parent_span_id.try_cast()?); - match x.data() { - v8::ValueViewData::OneByte(bytes) => { - let s = String::from_utf8_lossy(bytes); - if s.is_empty() { - SpanId::INVALID - } else { - SpanId::from_hex(&s)? - } - } - _ => return Err(anyhow!("invalid parent_span_id")), - } - }; + let span_id = parse_span_id(scope, span_id); + if span_id == SpanId::INVALID { + return Err(anyhow!("invalid span_id")); + } + + let parent_span_id = parse_span_id(scope, parent_span_id); let name = { let x = v8::ValueView::new(scope, name.try_cast()?); @@ -601,7 +744,7 @@ fn op_otel_span_start<'s>( events: Default::default(), links: Default::default(), status: SpanStatus::Unset, - instrumentation_scope: InstrumentationScope::builder("deno").build(), + instrumentation_scope: instrumentation_scope.clone(), }); state.put(temporary_span); @@ -626,52 +769,6 @@ fn op_otel_span_continue( } } -macro_rules! attr { - ($scope:ident, $temporary_span:ident, $name:ident, $value:ident) => { - let name = if let Ok(name) = $name.try_cast() { - let view = v8::ValueView::new($scope, name); - match view.data() { - v8::ValueViewData::OneByte(bytes) => { - Some(String::from_utf8_lossy(bytes).into_owned()) - } - v8::ValueViewData::TwoByte(bytes) => { - Some(String::from_utf16_lossy(bytes)) - } - } - } else { - None - }; - let value = if let Ok(string) = $value.try_cast::<v8::String>() { - Some(Value::String(StringValue::from({ - let x = v8::ValueView::new($scope, string); - match x.data() { - v8::ValueViewData::OneByte(bytes) => { - String::from_utf8_lossy(bytes).into_owned() - } - v8::ValueViewData::TwoByte(bytes) => String::from_utf16_lossy(bytes), - } - }))) - } else if let Ok(number) = $value.try_cast::<v8::Number>() { - Some(Value::F64(number.value())) - } else if let Ok(boolean) = $value.try_cast::<v8::Boolean>() { - Some(Value::Bool(boolean.is_true())) - } else if let Ok(bigint) = $value.try_cast::<v8::BigInt>() { - let (i64_value, _lossless) = bigint.i64_value(); - Some(Value::I64(i64_value)) - } else { - None - }; - if let (Some(name), Some(value)) = (name, value) { - $temporary_span - .0 - .attributes - .push(KeyValue::new(name, value)); - } else { - $temporary_span.0.dropped_attributes_count += 1; - } - }; -} - #[op2(fast)] fn op_otel_span_attribute<'s>( scope: &mut v8::HandleScope<'s>, @@ -684,7 +781,7 @@ fn op_otel_span_attribute<'s>( temporary_span.0.attributes.reserve_exact( (capacity as usize) - temporary_span.0.attributes.capacity(), ); - attr!(scope, temporary_span, key, value); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key, value); } } @@ -702,8 +799,8 @@ fn op_otel_span_attribute2<'s>( temporary_span.0.attributes.reserve_exact( (capacity as usize) - temporary_span.0.attributes.capacity(), ); - attr!(scope, temporary_span, key1, value1); - attr!(scope, temporary_span, key2, value2); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key1, value1); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key2, value2); } } @@ -724,9 +821,23 @@ fn op_otel_span_attribute3<'s>( temporary_span.0.attributes.reserve_exact( (capacity as usize) - temporary_span.0.attributes.capacity(), ); - attr!(scope, temporary_span, key1, value1); - attr!(scope, temporary_span, key2, value2); - attr!(scope, temporary_span, key3, value3); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key1, value1); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key2, value2); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key3, value3); + } +} + +#[op2(fast)] +fn op_otel_span_set_dropped( + state: &mut OpState, + #[smi] dropped_attributes_count: u32, + #[smi] dropped_links_count: u32, + #[smi] dropped_events_count: u32, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.dropped_attributes_count = dropped_attributes_count; + temporary_span.0.links.dropped_count = dropped_links_count; + temporary_span.0.events.dropped_count = dropped_events_count; } } |