1use crate::Telemetry;
2use logger_core::log_warn;
3use once_cell::sync::OnceCell;
4use opentelemetry::trace::{
5 Span, SpanContext, SpanId, SpanKind, TraceContextExt, TraceFlags, TraceId, TraceState,
6};
7use opentelemetry::{global, trace::Tracer};
8use opentelemetry_otlp::{MetricExporter, Protocol, WithExportConfig};
9use opentelemetry_sdk::error::OTelSdkError;
10use opentelemetry_sdk::metrics::SdkMeterProvider;
11use opentelemetry_sdk::propagation::TraceContextPropagator;
12use opentelemetry_sdk::runtime::Tokio;
13use opentelemetry_sdk::trace::{
14 BatchConfig, BatchSpanProcessor, SdkTracerProvider, SpanExporter,
15 span_processor_with_async_runtime::BatchSpanProcessor as AsyncBatchSpanProcessor,
16};
17use std::io::{Error, ErrorKind};
18use std::path::PathBuf;
19use std::str::FromStr;
20#[cfg(test)]
21use std::sync::atomic::{AtomicUsize, Ordering};
22use std::sync::{Arc, OnceLock, RwLock};
23use std::time::Duration;
24use thiserror::Error;
25use url::Url;
26
27const SPAN_WRITE_LOCK_ERR: &str = "Failed to acquire span write lock";
28const SPAN_READ_LOCK_ERR: &str = "Failed to acquire span read lock";
29const TRACE_SCOPE: &str = "valkey_glide";
30
31const TIMEOUT_ERROR_METRIC: &str = "glide.timeout_errors";
33const RETRIES_METRIC: &str = "glide.retry_attempts";
34const MOVED_ERROR_METRIC: &str = "glide.moved_errors";
35const SUBSCRIPTION_OUT_OF_SYNC_METRIC: &str = "glide.subscription_out_of_sync_count";
36const SUBSCRIPTION_LAST_SYNC_TIMESTAMP_METRIC: &str = "glide.subscription_last_sync_timestamp";
37
38#[derive(Debug, Error)]
40pub enum GlideOTELError {
41 #[error("Glide OpenTelemetry exporter build error: {0}")]
42 ExporterBuildError(#[from] opentelemetry_otlp::ExporterBuildError),
43
44 #[error("Glide OpenTelemetry SDK error: {0}")]
45 SdkError(#[from] OTelSdkError),
46
47 #[error("Glide OpenTelemetry trace error: {0}")]
48 TraceError(String),
49
50 #[error("Glide OpenTelemetry error: Failed to acquire read lock")]
51 ReadLockError,
52
53 #[error("Glide OpenTelemetry error: Failed to acquire write lock")]
54 WriteLockError,
55
56 #[error("Other error: {0}")]
57 Other(String),
58}
59
60pub const DEFAULT_FLUSH_SIGNAL_INTERVAL_MS: u32 = 5000;
62
63pub const DEFAULT_TRACE_SAMPLE_PERCENTAGE: u32 = 1;
65
66pub const DEFAULT_SIGNAL_FILENAME: &str = "signals.json";
68
69pub enum GlideSpanStatus {
70 Ok,
71 Error(String),
72}
73
74#[derive(Clone, Debug)]
75pub enum GlideOpenTelemetrySignalsExporter {
79 Grpc(String),
81 Http(String),
83 File(PathBuf),
86}
87
88#[derive(Clone, Copy)]
91enum OtelSignal {
92 Traces,
93 Metrics,
94}
95
96fn protocol_from_env(signal: OtelSignal) -> Option<Protocol> {
100 fn parse(value: &str) -> Option<Protocol> {
101 match value.to_ascii_lowercase().as_str() {
102 "grpc" => Some(Protocol::Grpc),
103 "http/protobuf" | "http/binary" | "http" | "http_proto" => Some(Protocol::HttpBinary),
104 "http/json" => Some(Protocol::HttpJson),
105 _ => None,
106 }
107 }
108
109 let specific = match signal {
110 OtelSignal::Traces => "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL",
111 OtelSignal::Metrics => "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL",
112 };
113
114 if let Ok(val) = std::env::var(specific) {
115 parse(&val)
116 } else if let Ok(val) = std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL") {
117 parse(&val)
118 } else {
119 None
120 }
121}
122
123impl std::str::FromStr for GlideOpenTelemetrySignalsExporter {
124 type Err = Error;
125 fn from_str(s: &str) -> Result<Self, Self::Err> {
126 parse_endpoint(s)
127 }
128}
129
130fn parse_endpoint(endpoint: &str) -> Result<GlideOpenTelemetrySignalsExporter, Error> {
131 let url = Url::parse(endpoint)
133 .map_err(|_| Error::new(ErrorKind::InvalidInput, format!("Parse error. {endpoint}")))?;
134
135 match url.scheme() {
136 "http" | "https" => Ok(GlideOpenTelemetrySignalsExporter::Http(
137 endpoint.to_string(),
138 )), "grpc" => Ok(GlideOpenTelemetrySignalsExporter::Grpc(
140 endpoint.to_string(),
141 )), "file" => {
143 let file_prefix = "file://";
145 if !endpoint.starts_with(file_prefix) {
146 return Err(Error::new(
147 ErrorKind::InvalidInput,
148 "File path must start with 'file://'",
149 ));
150 }
151
152 let path = endpoint.strip_prefix(file_prefix).ok_or_else(|| {
154 Error::new(
155 ErrorKind::InvalidInput,
156 "Failed to extract path from file URL",
157 )
158 })?;
159
160 let path_buf = PathBuf::from(path);
161
162 let final_path = if path_buf.is_dir() || path_buf.extension().is_none() {
164 path_buf.join(DEFAULT_SIGNAL_FILENAME)
167 } else {
168 path_buf
169 };
170
171 if let Some(parent_dir) = final_path.parent() {
173 match parent_dir.try_exists() {
174 Ok(exists) => {
175 if !exists || !parent_dir.is_dir() {
176 return Err(Error::new(
177 ErrorKind::InvalidInput,
178 format!(
179 "The directory does not exist or is not a directory: {}",
180 parent_dir.display()
181 ),
182 ));
183 }
184 }
185 Err(e) => {
186 return Err(Error::new(
187 ErrorKind::InvalidInput,
188 format!("Error checking if parent directory exists: {}", e),
189 ));
190 }
191 }
192 }
193
194 Ok(GlideOpenTelemetrySignalsExporter::File(final_path))
195 } _ => Err(Error::new(ErrorKind::InvalidInput, endpoint)),
197 }
198}
199
200#[derive(Clone, Debug)]
201struct GlideSpanInner {
202 span: Arc<RwLock<opentelemetry::global::BoxedSpan>>,
203 #[cfg(test)]
204 reference_count: Arc<AtomicUsize>,
205}
206
207impl GlideSpanInner {
208 pub fn new(name: &str) -> Self {
210 let tracer = global::tracer(TRACE_SCOPE);
211 let span = Arc::new(RwLock::new(
212 tracer
213 .span_builder(name.to_string())
214 .with_kind(SpanKind::Client)
215 .start(&tracer),
216 ));
217
218 GlideSpanInner {
219 span,
220 #[cfg(test)]
221 reference_count: Arc::new(AtomicUsize::new(1)),
222 }
223 }
224
225 pub fn new_with_parent(name: &str, parent: &GlideSpanInner) -> Result<Self, GlideOTELError> {
227 let parent_span_ctx = parent
228 .span
229 .read()
230 .map_err(|_| GlideOTELError::TraceError(SPAN_READ_LOCK_ERR.to_string()))?
231 .span_context()
232 .clone();
233
234 let parent_context =
235 opentelemetry::Context::new().with_remote_span_context(parent_span_ctx);
236
237 let tracer = global::tracer(TRACE_SCOPE);
238 let span = Arc::new(RwLock::new(
239 tracer
240 .span_builder(name.to_string())
241 .with_kind(SpanKind::Client)
242 .start_with_context(&tracer, &parent_context),
243 ));
244 Ok(GlideSpanInner {
245 span,
246 #[cfg(test)]
247 reference_count: Arc::new(AtomicUsize::new(1)),
248 })
249 }
250
251 pub fn new_with_remote_context(
256 name: &str,
257 trace_id_hex: &str,
258 span_id_hex: &str,
259 trace_flags: u8,
260 trace_state: Option<&str>,
261 ) -> Result<Self, GlideOTELError> {
262 let trace_id = TraceId::from_hex(trace_id_hex).map_err(|_| {
263 GlideOTELError::TraceError(format!("Invalid trace_id hex: {trace_id_hex}"))
264 })?;
265 let span_id = SpanId::from_hex(span_id_hex).map_err(|_| {
266 GlideOTELError::TraceError(format!("Invalid span_id hex: {span_id_hex}"))
267 })?;
268
269 let trace_state = match trace_state {
270 Some(s) => TraceState::from_str(s)
271 .map_err(|_| GlideOTELError::TraceError(format!("Invalid trace_state: {s}")))?,
272 None => TraceState::default(),
273 };
274
275 let remote_span_context = SpanContext::new(
276 trace_id,
277 span_id,
278 TraceFlags::new(trace_flags),
279 true, trace_state,
281 );
282
283 let parent_context =
284 opentelemetry::Context::new().with_remote_span_context(remote_span_context);
285
286 let tracer = global::tracer(TRACE_SCOPE);
287 let span = Arc::new(RwLock::new(
288 tracer
289 .span_builder(name.to_string())
290 .with_kind(SpanKind::Client)
291 .start_with_context(&tracer, &parent_context),
292 ));
293 Ok(GlideSpanInner {
294 span,
295 #[cfg(test)]
296 reference_count: Arc::new(AtomicUsize::new(1)),
297 })
298 }
299
300 pub fn add_event(&self, name: &str, attributes: Option<&Vec<(&str, &str)>>) {
302 let attributes: Vec<opentelemetry::KeyValue> = if let Some(attributes) = attributes {
303 attributes
304 .iter()
305 .map(|(k, v)| opentelemetry::KeyValue::new(k.to_string(), v.to_string()))
306 .collect()
307 } else {
308 Vec::<opentelemetry::KeyValue>::default()
309 };
310 self.span
311 .write()
312 .expect(SPAN_WRITE_LOCK_ERR)
313 .add_event_with_timestamp(name.to_string(), std::time::SystemTime::now(), attributes);
314 }
315
316 pub fn set_status(&self, status: GlideSpanStatus) {
317 match status {
318 GlideSpanStatus::Ok => self
319 .span
320 .write()
321 .expect(SPAN_WRITE_LOCK_ERR)
322 .set_status(opentelemetry::trace::Status::Ok),
323 GlideSpanStatus::Error(error_message) => {
324 self.span.write().expect(SPAN_WRITE_LOCK_ERR).set_status(
325 opentelemetry::trace::Status::Error {
326 description: error_message.into(),
327 },
328 )
329 }
330 }
331 }
332
333 pub fn set_attribute(&self, key: &str, value: impl Into<opentelemetry::Value>) {
335 self.span
336 .write()
337 .expect(SPAN_WRITE_LOCK_ERR)
338 .set_attribute(opentelemetry::KeyValue::new(key.to_string(), value.into()));
339 }
340
341 pub fn set_attribute_i64(&self, key: &str, value: i64) {
343 self.span
344 .write()
345 .expect(SPAN_WRITE_LOCK_ERR)
346 .set_attribute(opentelemetry::KeyValue::new(key.to_string(), value));
347 }
348
349 pub fn add_span(&self, name: &str) -> Result<GlideSpanInner, GlideOTELError> {
352 let child = GlideSpanInner::new_with_parent(name, self)?;
353 {
354 let child_span = child
355 .span
356 .read()
357 .map_err(|_| GlideOTELError::TraceError(SPAN_READ_LOCK_ERR.to_string()))?;
358 self.span
359 .write()
360 .expect(SPAN_WRITE_LOCK_ERR)
361 .add_link(child_span.span_context().clone(), Vec::default());
362 }
363 Ok(child)
364 }
365
366 pub fn id(&self) -> String {
368 self.span
369 .read()
370 .expect(SPAN_READ_LOCK_ERR)
371 .span_context()
372 .span_id()
373 .to_string()
374 }
375
376 pub fn end(&self) {
378 self.span.write().expect(SPAN_READ_LOCK_ERR).end()
379 }
380
381 #[cfg(test)]
382 pub fn get_reference_count(&self) -> usize {
383 self.reference_count.load(Ordering::SeqCst)
384 }
385
386 #[cfg(test)]
387 pub fn increment_reference_count(&self) {
388 self.reference_count.fetch_add(1, Ordering::SeqCst);
389 }
390
391 #[cfg(test)]
392 pub fn decrement_reference_count(&self) {
393 self.reference_count.fetch_sub(1, Ordering::SeqCst);
394 }
395}
396
397#[cfg(test)]
398impl Drop for GlideSpanInner {
399 fn drop(&mut self) {
400 let current_count = self.reference_count.load(Ordering::SeqCst);
402 if current_count > 0 {
403 self.decrement_reference_count();
404 } else {
405 panic!("Span reference count is 0");
406 }
407 }
408}
409
410#[derive(Clone, Debug)]
411pub struct GlideSpan {
412 inner: GlideSpanInner,
413}
414
415impl GlideSpan {
416 pub fn new(name: &str) -> Self {
417 GlideSpan {
418 inner: GlideSpanInner::new(name),
419 }
420 }
421
422 pub fn new_with_remote_context(
424 name: &str,
425 trace_id_hex: &str,
426 span_id_hex: &str,
427 trace_flags: u8,
428 trace_state: Option<&str>,
429 ) -> Result<Self, GlideOTELError> {
430 Ok(GlideSpan {
431 inner: GlideSpanInner::new_with_remote_context(
432 name,
433 trace_id_hex,
434 span_id_hex,
435 trace_flags,
436 trace_state,
437 )?,
438 })
439 }
440
441 pub fn add_event(&self, name: &str) {
443 self.inner.add_event(name, None)
444 }
445
446 pub fn add_event_with_attributes(&self, name: &str, attributes: &Vec<(&str, &str)>) {
448 self.inner.add_event(name, Some(attributes))
449 }
450
451 pub fn set_status(&self, status: GlideSpanStatus) {
452 self.inner.set_status(status)
453 }
454
455 pub fn set_attribute(&self, key: &str, value: impl Into<opentelemetry::Value>) {
457 self.inner.set_attribute(key, value)
458 }
459
460 pub fn set_attribute_i64(&self, key: &str, value: i64) {
462 self.inner.set_attribute_i64(key, value)
463 }
464
465 pub fn add_span(&self, name: &str) -> Result<GlideSpan, GlideOTELError> {
467 let inner_span = self.inner.add_span(name).map_err(|err| {
468 GlideOTELError::TraceError(format!("Failed to create child span '{}': {}", name, err))
469 })?;
470
471 Ok(GlideSpan { inner: inner_span })
472 }
473
474 pub fn id(&self) -> String {
475 self.inner.id()
476 }
477
478 pub fn end(&self) {
480 self.inner.end()
481 }
482
483 #[cfg(test)]
484 pub fn add_reference(&self) {
485 self.inner.increment_reference_count();
486 }
487
488 #[cfg(test)]
489 pub fn get_reference_count(&self) -> usize {
490 self.inner.get_reference_count()
491 }
492}
493
494#[derive(Clone, Debug)]
503pub struct GlideOpenTelemetryConfig {
504 flush_interval_ms: Duration,
506 traces: Option<GlideOpenTelemetryTracesConfig>,
507 metrics: Option<GlideOpenTelemetryMetricsConfig>,
508}
509
510#[derive(Clone, Debug)]
511pub struct GlideOpenTelemetryTracesConfig {
512 trace_exporter: GlideOpenTelemetrySignalsExporter,
514 trace_sample_percentage: u32,
516}
517
518#[derive(Clone, Debug)]
519pub struct GlideOpenTelemetryMetricsConfig {
520 metrics_exporter: GlideOpenTelemetrySignalsExporter,
522}
523
524#[derive(Clone, Debug)]
533pub struct GlideOpenTelemetryConfigBuilder {
534 flush_interval_ms: Duration,
536 traces_config: Option<GlideOpenTelemetryTracesConfig>,
538 metrics_config: Option<GlideOpenTelemetryMetricsConfig>,
540}
541
542impl Default for GlideOpenTelemetryConfigBuilder {
543 fn default() -> Self {
544 GlideOpenTelemetryConfigBuilder {
545 flush_interval_ms: Duration::from_millis(DEFAULT_FLUSH_SIGNAL_INTERVAL_MS as u64),
546 traces_config: None,
547 metrics_config: None,
548 }
549 }
550}
551
552impl GlideOpenTelemetryConfigBuilder {
553 pub fn with_flush_interval(mut self, duration: Duration) -> Self {
557 self.flush_interval_ms = duration;
558 self
559 }
560
561 pub fn with_trace_exporter(
567 mut self,
568 exporter: GlideOpenTelemetrySignalsExporter,
569 sample_percentage: Option<u32>,
570 ) -> Self {
571 self.traces_config = Some(GlideOpenTelemetryTracesConfig {
572 trace_exporter: exporter,
573 trace_sample_percentage: sample_percentage.unwrap_or(DEFAULT_TRACE_SAMPLE_PERCENTAGE),
574 });
575 self
576 }
577
578 pub fn with_metrics_exporter(mut self, exporter: GlideOpenTelemetrySignalsExporter) -> Self {
582 self.metrics_config = Some(GlideOpenTelemetryMetricsConfig {
583 metrics_exporter: exporter,
584 });
585 self
586 }
587
588 pub fn build(self) -> GlideOpenTelemetryConfig {
589 GlideOpenTelemetryConfig {
590 flush_interval_ms: self.flush_interval_ms,
591 traces: self.traces_config,
592 metrics: self.metrics_config,
593 }
594 }
595}
596
597fn build_file_span_processor(
601 batch_config: BatchConfig,
602 exporter: impl SpanExporter + 'static,
603) -> BatchSpanProcessor {
604 BatchSpanProcessor::builder(exporter)
605 .with_batch_config(batch_config)
606 .build()
607}
608
609fn build_otlp_span_processor(
614 batch_config: BatchConfig,
615 exporter: impl SpanExporter + 'static,
616) -> AsyncBatchSpanProcessor<Tokio> {
617 AsyncBatchSpanProcessor::builder(exporter, Tokio)
618 .with_batch_config(batch_config)
619 .build()
620}
621
622#[derive(Clone)]
623pub struct GlideOpenTelemetry {}
624
625static TIMEOUT_COUNTER: OnceLock<opentelemetry::metrics::Counter<u64>> = OnceLock::new();
626static RETRIES_COUNTER: OnceLock<opentelemetry::metrics::Counter<u64>> = OnceLock::new();
627static MOVED_COUNTER: OnceLock<opentelemetry::metrics::Counter<u64>> = OnceLock::new();
628static SUBSCRIPTION_OUT_OF_SYNC_COUNTER: OnceLock<opentelemetry::metrics::Counter<u64>> =
629 OnceLock::new();
630static SUBSCRIPTION_LAST_SYNC_GAUGE: OnceLock<opentelemetry::metrics::Gauge<u64>> = OnceLock::new();
631
632static TRACER_PROVIDER: OnceLock<SdkTracerProvider> = OnceLock::new();
636
637static OTEL: OnceCell<RwLock<GlideOpenTelemetry>> = OnceCell::new();
639
640impl GlideOpenTelemetry {
642 pub unsafe fn is_span_pointer_valid(span_ptr: u64) -> bool {
655 if span_ptr == 0 {
657 logger_core::log_warn("OpenTelemetry", "Invalid span pointer - null pointer (0)");
658 return false;
659 }
660
661 if !span_ptr.is_multiple_of(8) {
664 logger_core::log_warn(
665 "OpenTelemetry",
666 format!(
667 "Invalid span pointer - misaligned pointer: 0x{:x}",
668 span_ptr
669 ),
670 );
671 return false;
672 }
673
674 const MIN_VALID_ADDRESS: u64 = 0x1000; if span_ptr < MIN_VALID_ADDRESS {
678 logger_core::log_warn(
679 "OpenTelemetry",
680 format!("Invalid span pointer - address too low: 0x{:x}", span_ptr),
681 );
682 return false;
683 }
684
685 const MAX_VALID_ADDRESS: u64 = 0x7FFF_FFFF_FFFF_FFF8; if span_ptr > MAX_VALID_ADDRESS {
690 logger_core::log_warn(
691 "OpenTelemetry",
692 format!("Invalid span pointer - address too high: 0x{:x}", span_ptr),
693 );
694 return false;
695 }
696
697 true
698 }
699
700 pub unsafe fn span_from_pointer(span_ptr: u64) -> Result<GlideSpan, GlideOTELError> {
712 if !unsafe { Self::is_span_pointer_valid(span_ptr) } {
714 return Err(GlideOTELError::TraceError(format!(
715 "Invalid span pointer: 0x{:x} failed validation checks",
716 span_ptr
717 )));
718 }
719
720 let span = unsafe {
723 std::sync::Arc::increment_strong_count(span_ptr as *const GlideSpan);
725
726 (*std::sync::Arc::from_raw(span_ptr as *const GlideSpan)).clone()
728 };
729
730 Ok(span)
731 }
732
733 pub fn initialise(config: GlideOpenTelemetryConfig) -> Result<(), GlideOTELError> {
738 OTEL.get_or_try_init(|| {
739 Self::validate_config(config.clone())?;
740
741 if let Some(traces_config) = config.traces.as_ref() {
742 Self::initialise_trace_exporter(
743 config.flush_interval_ms,
744 &traces_config.trace_exporter,
745 )?;
746 }
747
748 if let Some(metrics_config) = config.metrics.as_ref() {
749 Self::initialise_metrics_exporter(
750 config.flush_interval_ms,
751 &metrics_config.metrics_exporter,
752 )?;
753 Self::init_metrics()?;
754 }
755
756 Ok::<RwLock<GlideOpenTelemetry>, GlideOTELError>(RwLock::new(GlideOpenTelemetry {}))
757 })?;
758
759 Ok(())
760 }
761
762 fn validate_config(config: GlideOpenTelemetryConfig) -> Result<(), GlideOTELError> {
770 if config.flush_interval_ms.is_zero() {
772 return Err(GlideOTELError::Other(
773 "InvalidInput: flushIntervalMs cannot be zero".to_string(),
774 ));
775 }
776
777 if let Some(traces_config) = config.traces.as_ref()
779 && traces_config.trace_sample_percentage > 100
780 {
781 return Err(GlideOTELError::Other(
782 "Trace sample percentage must be between 0 and 100".into(),
783 ));
784 }
785 Ok(())
786 }
787
788 fn initialise_trace_exporter(
790 flush_interval_ms: Duration,
791 trace_exporter: &GlideOpenTelemetrySignalsExporter,
792 ) -> Result<(), GlideOTELError> {
793 let batch_config = opentelemetry_sdk::trace::BatchConfigBuilder::default()
794 .with_scheduled_delay(flush_interval_ms)
795 .build();
796
797 let env_protocol = protocol_from_env(OtelSignal::Traces);
798 let provider = match trace_exporter {
803 GlideOpenTelemetrySignalsExporter::File(p) => {
804 let exporter = crate::SpanExporterFile::new(p.clone()).map_err(|e| {
805 GlideOTELError::Other(format!("Failed to create traces exporter: {}", e))
806 })?;
807 SdkTracerProvider::builder()
808 .with_span_processor(build_file_span_processor(batch_config, exporter))
809 .build()
810 }
811 GlideOpenTelemetrySignalsExporter::Http(url) => {
812 let processor = match env_protocol.unwrap_or(Protocol::HttpBinary) {
813 Protocol::Grpc => {
814 let exporter = opentelemetry_otlp::SpanExporter::builder()
815 .with_tonic()
816 .with_endpoint(url)
817 .with_protocol(Protocol::Grpc)
818 .build()?;
819 build_otlp_span_processor(batch_config, exporter)
820 }
821 protocol => {
822 let exporter = opentelemetry_otlp::SpanExporter::builder()
823 .with_http()
824 .with_endpoint(url)
825 .with_protocol(protocol)
826 .build()?;
827 build_otlp_span_processor(batch_config, exporter)
828 }
829 };
830 SdkTracerProvider::builder()
831 .with_span_processor(processor)
832 .build()
833 }
834 GlideOpenTelemetrySignalsExporter::Grpc(url) => {
835 let protocol = env_protocol.unwrap_or(Protocol::Grpc);
836 if protocol != Protocol::Grpc {
837 log_warn(
838 "opentelemetry",
839 format!(
840 "Inconsistent configuration: The endpoint URL '{url}' suggests gRPC, but the protocol is set to '{protocol:?}' via environment variables. The environment variable setting will be used."
841 ),
842 );
843 }
844 let processor = match protocol {
845 Protocol::Grpc => {
846 let exporter = opentelemetry_otlp::SpanExporter::builder()
847 .with_tonic()
848 .with_endpoint(url)
849 .with_protocol(Protocol::Grpc)
850 .build()?;
851 build_otlp_span_processor(batch_config, exporter)
852 }
853 protocol => {
854 let exporter = opentelemetry_otlp::SpanExporter::builder()
855 .with_http()
856 .with_endpoint(url)
857 .with_protocol(protocol)
858 .build()?;
859 build_otlp_span_processor(batch_config, exporter)
860 }
861 };
862 SdkTracerProvider::builder()
863 .with_span_processor(processor)
864 .build()
865 }
866 };
867
868 global::set_text_map_propagator(TraceContextPropagator::new());
869 let _ = TRACER_PROVIDER.set(provider.clone());
872 global::set_tracer_provider(provider);
873
874 Ok(())
875 }
876
877 fn initialise_metrics_exporter(
879 flush_interval_ms: Duration,
880 metrics_exporter: &GlideOpenTelemetrySignalsExporter,
881 ) -> Result<(), GlideOTELError> {
882 let env_protocol = protocol_from_env(OtelSignal::Metrics);
883 let meter_provider = match metrics_exporter {
887 GlideOpenTelemetrySignalsExporter::File(p) => {
888 let exporter = crate::FileMetricExporter::new(p.clone()).map_err(|e| {
889 GlideOTELError::Other(format!("Failed to create metrics exporter: {}", e))
890 })?;
891 let reader = opentelemetry_sdk::metrics::PeriodicReader::builder(exporter)
896 .with_interval(flush_interval_ms)
897 .build();
898 SdkMeterProvider::builder().with_reader(reader).build()
899 }
900 GlideOpenTelemetrySignalsExporter::Http(url) => {
901 let protocol = env_protocol.unwrap_or(Protocol::HttpBinary);
902 let exporter = match protocol {
903 Protocol::Grpc => MetricExporter::builder()
904 .with_tonic()
905 .with_endpoint(url)
906 .with_protocol(Protocol::Grpc)
907 .build()?,
908 p => MetricExporter::builder()
909 .with_http()
910 .with_endpoint(url)
911 .with_protocol(p)
912 .build()?,
913 };
914 let reader = opentelemetry_sdk::metrics::periodic_reader_with_async_runtime::PeriodicReader::builder(exporter, Tokio)
915 .with_interval(flush_interval_ms)
916 .build();
917 SdkMeterProvider::builder().with_reader(reader).build()
918 }
919 GlideOpenTelemetrySignalsExporter::Grpc(url) => {
920 let protocol = env_protocol.unwrap_or(Protocol::Grpc);
921 if protocol != Protocol::Grpc {
922 log_warn(
923 "opentelemetry",
924 format!(
925 "Inconsistent configuration: The endpoint URL '{url}' suggests gRPC, but the protocol is set to '{protocol:?}' via environment variables. The environment variable setting will be used."
926 ),
927 );
928 }
929 let exporter = match protocol {
930 Protocol::Grpc => MetricExporter::builder()
931 .with_tonic()
932 .with_endpoint(url)
933 .with_protocol(Protocol::Grpc)
934 .build()?,
935 p => MetricExporter::builder()
936 .with_http()
937 .with_endpoint(url)
938 .with_protocol(p)
939 .build()?,
940 };
941 let reader = opentelemetry_sdk::metrics::periodic_reader_with_async_runtime::PeriodicReader::builder(exporter, Tokio)
942 .with_interval(flush_interval_ms)
943 .build();
944 SdkMeterProvider::builder().with_reader(reader).build()
945 }
946 };
947
948 global::set_meter_provider(meter_provider);
949
950 Ok(())
951 }
952
953 fn init_metrics() -> Result<(), GlideOTELError> {
955 let meter = global::meter(TRACE_SCOPE);
956
957 TIMEOUT_COUNTER
959 .set(
960 meter
961 .u64_counter(TIMEOUT_ERROR_METRIC)
962 .with_description("Number of timeout errors encountered")
963 .with_unit("1")
964 .build(),
965 )
966 .map_err(|_| {
967 GlideOTELError::Other(
968 "OpenTelemetry error: Failed to initialize timeout counter".to_owned(),
969 )
970 })?;
971
972 RETRIES_COUNTER
974 .set(
975 meter
976 .u64_counter(RETRIES_METRIC)
977 .with_description("Number of retry attempts made")
978 .with_unit("1")
979 .build(),
980 )
981 .map_err(|_| {
982 GlideOTELError::Other(
983 "OpenTelemetry error: Failed to initialize retries counter".to_owned(),
984 )
985 })?;
986
987 MOVED_COUNTER
989 .set(
990 meter
991 .u64_counter(MOVED_ERROR_METRIC)
992 .with_description("Number of moved errors encountered")
993 .with_unit("1")
994 .build(),
995 )
996 .map_err(|_| {
997 GlideOTELError::Other(
998 "OpenTelemetry error: Failed to initialize moved counter".to_owned(),
999 )
1000 })?;
1001 SUBSCRIPTION_OUT_OF_SYNC_COUNTER
1003 .set(
1004 meter
1005 .u64_counter(SUBSCRIPTION_OUT_OF_SYNC_METRIC)
1006 .with_description("Number of times subscriptions were detected as out of sync")
1007 .with_unit("1")
1008 .build(),
1009 )
1010 .map_err(|_| {
1011 GlideOTELError::Other(
1012 "OpenTelemetry error: Failed to initialize subscription out of sync counter"
1013 .to_owned(),
1014 )
1015 })?;
1016
1017 SUBSCRIPTION_LAST_SYNC_GAUGE
1019 .set(
1020 meter
1021 .u64_gauge(SUBSCRIPTION_LAST_SYNC_TIMESTAMP_METRIC)
1022 .with_description("Unix timestamp (in milliseconds) of the last time subscriptions were in sync")
1023 .with_unit("ms")
1024 .build(),
1025 )
1026 .map_err(|_| {
1027 GlideOTELError::Other(
1028 "OpenTelemetry error: Failed to initialize subscription last sync gauge".to_owned(),
1029 )
1030 })?;
1031
1032 Ok(())
1033 }
1034
1035 pub fn record_timeout_error() -> Result<(), GlideOTELError> {
1039 if GlideOpenTelemetry::is_initialized() {
1040 TIMEOUT_COUNTER
1041 .get()
1042 .ok_or_else(|| {
1043 GlideOTELError::Other(
1044 "OpenTelemetry error: Timeout counter not initialized".to_owned(),
1045 )
1046 })?
1047 .add(1, &[]);
1048 }
1049 Ok(())
1050 }
1051
1052 pub fn record_retry_attempt() -> Result<(), GlideOTELError> {
1056 if GlideOpenTelemetry::is_initialized() {
1057 RETRIES_COUNTER
1058 .get()
1059 .ok_or_else(|| {
1060 GlideOTELError::Other(
1061 "OpenTelemetry error: Retries counter not initialized".to_string(),
1062 )
1063 })?
1064 .add(1, &[]);
1065 }
1066 Ok(())
1067 }
1068
1069 pub fn record_moved_error() -> Result<(), GlideOTELError> {
1073 if GlideOpenTelemetry::is_initialized() {
1074 MOVED_COUNTER
1075 .get()
1076 .ok_or_else(|| {
1077 GlideOTELError::Other(
1078 "OpenTelemetry error: Moved counter not initialized".to_string(),
1079 )
1080 })?
1081 .add(1, &[]);
1082 }
1083 Ok(())
1084 }
1085
1086 pub fn record_subscription_out_of_sync() -> Result<(), GlideOTELError> {
1090 Telemetry::incr_subscription_out_of_sync();
1091 if GlideOpenTelemetry::is_initialized() {
1092 SUBSCRIPTION_OUT_OF_SYNC_COUNTER
1093 .get()
1094 .ok_or_else(|| {
1095 GlideOTELError::Other(
1096 "OpenTelemetry error: Subscription out of sync counter not initialized"
1097 .to_string(),
1098 )
1099 })?
1100 .add(1, &[]);
1101 }
1102 Ok(())
1103 }
1104
1105 pub fn update_subscription_last_sync_timestamp() -> Result<(), GlideOTELError> {
1111 let timestamp = std::time::SystemTime::now()
1112 .duration_since(std::time::UNIX_EPOCH)
1113 .map_err(|e| GlideOTELError::Other(format!("Failed to get system time: {}", e)))?
1114 .as_millis() as u64;
1115
1116 Telemetry::update_subscription_last_sync_timestamp(timestamp);
1117
1118 if GlideOpenTelemetry::is_initialized() {
1119 SUBSCRIPTION_LAST_SYNC_GAUGE
1120 .get()
1121 .ok_or_else(|| {
1122 GlideOTELError::Other(
1123 "OpenTelemetry error: Subscription last sync gauge not initialized"
1124 .to_string(),
1125 )
1126 })?
1127 .record(timestamp, &[]);
1128 }
1129 Ok(())
1130 }
1131
1132 pub fn get_flush_interval_ms(config: GlideOpenTelemetryConfig) -> Duration {
1134 config.flush_interval_ms
1135 }
1136
1137 pub fn new_span(name: &str) -> GlideSpan {
1139 GlideSpan::new(name)
1140 }
1141
1142 pub fn shutdown() {
1144 if let Some(provider) = TRACER_PROVIDER.get()
1145 && let Err(e) = provider.shutdown()
1146 {
1147 log_warn(
1148 "opentelemetry",
1149 format!("Failed to shut down tracer provider: {e}"),
1150 );
1151 }
1152 }
1153
1154 pub fn is_initialized() -> bool {
1156 OTEL.get().is_some()
1157 }
1158}
1159
1160#[cfg(test)]
1161mod tests {
1162 use super::*;
1163 use std::panic;
1164 use std::sync::OnceLock;
1165 use tokio::runtime::Runtime;
1166 use tokio::time::sleep;
1167
1168 const SPANS_JSON: &str = "/tmp/spans.json";
1169 const METRICS_JSON: &str = "/tmp/metrics.json";
1170
1171 fn shared_runtime() -> &'static Runtime {
1172 static RUNTIME: OnceLock<Runtime> = OnceLock::new();
1173 RUNTIME.get_or_init(|| Runtime::new().expect("Failed to create runtime"))
1174 }
1175
1176 fn string_property_to_u64(json: &serde_json::Value, prop: &str) -> u64 {
1177 let s = json[prop].to_string().replace('"', "");
1178 s.parse::<u64>().unwrap()
1179 }
1180
1181 fn find_metric_by_name<'a>(
1183 metric_json: &'a serde_json::Value,
1184 metric_name: &str,
1185 ) -> Option<&'a serde_json::Value> {
1186 metric_json["scope_metrics"][0]["metrics"]
1187 .as_array()?
1188 .iter()
1189 .find(|m| m["name"] == metric_name)
1190 }
1191
1192 async fn init_otel() -> Result<(), GlideOTELError> {
1193 let config = GlideOpenTelemetryConfigBuilder::default()
1194 .with_flush_interval(Duration::from_millis(2000))
1195 .with_trace_exporter(
1196 GlideOpenTelemetrySignalsExporter::File(PathBuf::from(SPANS_JSON)),
1197 Some(100),
1198 )
1199 .with_metrics_exporter(GlideOpenTelemetrySignalsExporter::File(PathBuf::from(
1200 METRICS_JSON,
1201 )))
1202 .build();
1203 if let Err(e) = GlideOpenTelemetry::initialise(config) {
1204 panic!("Failed to initialize OpenTelemetry: {}", e);
1205 }
1206 Ok(())
1207 }
1208
1209 async fn create_test_spans() {
1210 let _ = std::fs::remove_file(SPANS_JSON);
1212
1213 let span = GlideOpenTelemetry::new_span("Root_Span_1");
1214 span.add_event("Event1");
1215 span.set_status(GlideSpanStatus::Ok);
1216
1217 let child1 = span.add_span("Network_Span").unwrap();
1218
1219 sleep(Duration::from_millis(100)).await;
1221 child1.end();
1222
1223 sleep(Duration::from_millis(100)).await;
1225 span.end();
1226
1227 let span = GlideOpenTelemetry::new_span("Root_Span_2");
1228 span.add_event("Event1");
1229 span.add_event("Event2");
1230 span.set_status(GlideSpanStatus::Ok);
1231 drop(span); sleep(Duration::from_millis(2100)).await;
1234 }
1235
1236 #[test]
1237 fn test_span_json_exporter() {
1238 let rt = shared_runtime();
1239 rt.block_on(async {
1240 let _ = std::fs::remove_file(SPANS_JSON);
1241
1242 init_otel().await.unwrap();
1243 create_test_spans().await;
1244
1245 let file_content = std::fs::read_to_string(SPANS_JSON).unwrap();
1246 let lines: Vec<&str> = file_content
1247 .split('\n')
1248 .filter(|l| !l.trim().is_empty())
1249 .collect();
1250
1251 let mut network_span: Option<serde_json::Value> = None;
1253 let mut root_span_1: Option<serde_json::Value> = None;
1254 let mut root_span_2: Option<serde_json::Value> = None;
1255
1256 for line in lines {
1257 if let Ok(span) = serde_json::from_str::<serde_json::Value>(line) {
1258 match span["name"].as_str() {
1259 Some("Network_Span") if network_span.is_none() => network_span = Some(span),
1260 Some("Root_Span_1") if span["status"] == "Ok" && root_span_1.is_none() => {
1261 root_span_1 = Some(span)
1262 }
1263 Some("Root_Span_2") if root_span_2.is_none() => root_span_2 = Some(span),
1264 _ => {}
1265 }
1266 }
1267 }
1268
1269 let network_span = network_span.expect("Network_Span not found");
1270 let root_span_1 = root_span_1.expect("Root_Span_1 not found");
1271 let root_span_2 = root_span_2.expect("Root_Span_2 not found");
1272
1273 let network_span_id = network_span["span_id"].to_string();
1275 let network_span_start_time = string_property_to_u64(&network_span, "start_time");
1276 let network_span_end_time = string_property_to_u64(&network_span, "end_time");
1277
1278 assert!(network_span_end_time - network_span_start_time >= 100_000);
1280
1281 assert_eq!(root_span_1["links"].as_array().unwrap().len(), 1); let root_1_span_start_time = string_property_to_u64(&root_span_1, "start_time");
1284 let root_1_span_end_time = string_property_to_u64(&root_span_1, "end_time");
1285
1286 assert!(network_span_start_time >= root_1_span_start_time);
1288
1289 assert!(root_1_span_end_time - network_span_end_time >= 100_000);
1291
1292 let child_span_id = root_span_1["links"][0]["span_id"].to_string();
1293 assert_eq!(child_span_id, network_span_id);
1294
1295 assert_eq!(root_span_2["events"].as_array().unwrap().len(), 2); });
1298 }
1299
1300 #[test]
1301 fn test_span_reference_count() {
1302 let rt = shared_runtime();
1303 rt.block_on(async {
1304 let _ = std::fs::remove_file(SPANS_JSON);
1305 init_otel().await.unwrap();
1306 let span = GlideOpenTelemetry::new_span("Root_Span_1");
1307 span.add_reference();
1308 assert_eq!(span.get_reference_count(), 2);
1309 drop(span);
1310 });
1311 }
1312
1313 #[test]
1314 fn test_record_timeout_error() {
1315 let rt = shared_runtime();
1316 rt.block_on(async {
1317 let _ = std::fs::remove_file(METRICS_JSON);
1318 init_otel().await.unwrap();
1319 GlideOpenTelemetry::record_timeout_error().unwrap();
1320 sleep(Duration::from_millis(2100)).await;
1321 GlideOpenTelemetry::record_timeout_error().unwrap();
1322 GlideOpenTelemetry::record_timeout_error().unwrap();
1323
1324 sleep(Duration::from_millis(2100)).await;
1326
1327 let file_content = std::fs::read_to_string(METRICS_JSON).unwrap();
1328 let lines: Vec<&str> = file_content
1329 .split('\n')
1330 .filter(|l| !l.trim().is_empty())
1331 .collect();
1332
1333 let metric_json: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
1334 assert_eq!(
1335 metric_json["scope_metrics"][0]["metrics"][0]["name"],
1336 "glide.timeout_errors"
1337 );
1338 assert_eq!(
1339 metric_json["scope_metrics"][0]["metrics"][0]["data_points"][0]["value"],
1340 1
1341 );
1342 let metric_json: serde_json::Value =
1343 serde_json::from_str(lines[lines.len() - 1]).unwrap();
1344 assert_eq!(
1345 metric_json["scope_metrics"][0]["metrics"][0]["data_points"][0]["value"],
1346 3
1347 );
1348 });
1349 }
1350
1351 #[test]
1352 fn test_record_retry_attempts() {
1353 let rt = shared_runtime();
1354 rt.block_on(async {
1355 let _ = std::fs::remove_file(METRICS_JSON);
1356 init_otel().await.unwrap();
1357 GlideOpenTelemetry::record_retry_attempt().unwrap();
1358 sleep(Duration::from_millis(2100)).await;
1359 GlideOpenTelemetry::record_retry_attempt().unwrap();
1360 GlideOpenTelemetry::record_retry_attempt().unwrap();
1361
1362 sleep(Duration::from_millis(2100)).await;
1364
1365 let file_content = std::fs::read_to_string(METRICS_JSON).unwrap();
1366 let lines: Vec<&str> = file_content
1367 .split('\n')
1368 .filter(|l| !l.trim().is_empty())
1369 .collect();
1370
1371 let metric_json: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
1372 let retry_metric = find_metric_by_name(&metric_json, "glide.retry_attempts")
1373 .expect("glide.retry_attempts metric not found");
1374 assert_eq!(retry_metric["data_points"][0]["value"], 1);
1375
1376 let metric_json: serde_json::Value =
1377 serde_json::from_str(lines[lines.len() - 1]).unwrap();
1378 let retry_metric = find_metric_by_name(&metric_json, "glide.retry_attempts")
1379 .expect("glide.retry_attempts metric not found");
1380 assert_eq!(retry_metric["data_points"][0]["value"], 3);
1381 });
1382 }
1383
1384 #[test]
1385 fn test_record_moved_error() {
1386 let rt = shared_runtime();
1387 rt.block_on(async {
1388 let _ = std::fs::remove_file(METRICS_JSON);
1389 init_otel().await.unwrap();
1390 GlideOpenTelemetry::record_moved_error().unwrap();
1391 sleep(Duration::from_millis(2100)).await;
1392 GlideOpenTelemetry::record_moved_error().unwrap();
1393 GlideOpenTelemetry::record_moved_error().unwrap();
1394
1395 sleep(Duration::from_millis(2100)).await;
1397
1398 let file_content = std::fs::read_to_string(METRICS_JSON).unwrap();
1399 let lines: Vec<&str> = file_content
1400 .split('\n')
1401 .filter(|l| !l.trim().is_empty())
1402 .collect();
1403
1404 let metric_json: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
1405 let moved_metric = find_metric_by_name(&metric_json, "glide.moved_errors")
1406 .expect("glide.moved_errors metric not found");
1407 assert_eq!(moved_metric["data_points"][0]["value"], 1);
1408
1409 let metric_json: serde_json::Value =
1410 serde_json::from_str(lines[lines.len() - 1]).unwrap();
1411 let moved_metric = find_metric_by_name(&metric_json, "glide.moved_errors")
1412 .expect("glide.moved_errors metric not found");
1413 assert_eq!(moved_metric["data_points"][0]["value"], 3);
1414 });
1415 }
1416
1417 #[test]
1418 fn test_set_status_ok() {
1419 let rt = shared_runtime();
1420 rt.block_on(async {
1421 let _ = std::fs::remove_file(SPANS_JSON);
1423
1424 init_otel().await.unwrap();
1425 let span = GlideOpenTelemetry::new_span("Root_Span_1");
1426 span.add_event("Event1");
1427 span.set_status(GlideSpanStatus::Ok);
1428
1429 let child1 = span.add_span("Network_Span").unwrap();
1430
1431 sleep(Duration::from_millis(100)).await;
1433 child1.end();
1434
1435 sleep(Duration::from_millis(100)).await;
1437 span.end();
1438
1439 let span = GlideOpenTelemetry::new_span("Root_Span_2");
1440 span.add_event("Event1");
1441 span.add_event("Event2");
1442 span.set_status(GlideSpanStatus::Error("simple error".to_string())); drop(span); sleep(Duration::from_millis(2100)).await;
1446
1447 let file_content = std::fs::read_to_string(SPANS_JSON).unwrap();
1448 let lines: Vec<&str> = file_content
1449 .split('\n')
1450 .filter(|l| !l.trim().is_empty())
1451 .collect();
1452
1453 let mut ok_span: Option<serde_json::Value> = None;
1455 let mut error_span: Option<serde_json::Value> = None;
1456
1457 for line in lines {
1458 if let Ok(span) = serde_json::from_str::<serde_json::Value>(line) {
1459 if span["name"] == "Root_Span_1" && span["status"] == "Ok" {
1460 ok_span = Some(span);
1461 } else if span["name"] == "Root_Span_2" {
1462 let status = span["status"].as_str().unwrap_or("");
1463 if status.starts_with("Error") {
1464 error_span = Some(span);
1465 }
1466 }
1467 }
1468 }
1469
1470 let ok_span = ok_span.expect("Root_Span_1 with Ok status not found");
1471 assert_eq!(ok_span["status"], "Ok");
1472
1473 let error_span = error_span.expect("Root_Span_2 with Error status not found");
1474 let status = error_span["status"].as_str().unwrap_or("");
1475 assert!(status.starts_with("Error"));
1476 assert!(status.contains("simple error"));
1477 });
1478 }
1479
1480 #[test]
1481 fn test_protocol_from_env() {
1482 unsafe {
1483 std::env::remove_var("OTEL_EXPORTER_OTLP_PROTOCOL");
1484 std::env::remove_var("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL");
1485 std::env::remove_var("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL");
1486 }
1487
1488 assert!(protocol_from_env(OtelSignal::Traces).is_none());
1490
1491 unsafe { std::env::set_var("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc") };
1492 assert_eq!(protocol_from_env(OtelSignal::Traces), Some(Protocol::Grpc));
1493
1494 unsafe { std::env::set_var("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", "http/protobuf") };
1495 assert_eq!(
1496 protocol_from_env(OtelSignal::Metrics),
1497 Some(Protocol::HttpBinary)
1498 );
1499
1500 unsafe {
1501 std::env::remove_var("OTEL_EXPORTER_OTLP_PROTOCOL");
1502 std::env::set_var("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", "http/json");
1503 }
1504 assert_eq!(
1505 protocol_from_env(OtelSignal::Traces),
1506 Some(Protocol::HttpJson)
1507 );
1508 unsafe {
1509 std::env::remove_var("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL");
1510 std::env::remove_var("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL");
1511 }
1512 }
1513
1514 #[test]
1515 fn test_span_pointer_validation() {
1516 assert!(unsafe { !GlideOpenTelemetry::is_span_pointer_valid(0) });
1518
1519 assert!(unsafe { !GlideOpenTelemetry::is_span_pointer_valid(0x1001) }); assert!(unsafe { !GlideOpenTelemetry::is_span_pointer_valid(0x1002) }); assert!(unsafe { !GlideOpenTelemetry::is_span_pointer_valid(0x1007) }); assert!(unsafe { !GlideOpenTelemetry::is_span_pointer_valid(0x800) }); assert!(unsafe { !GlideOpenTelemetry::is_span_pointer_valid(0x100) }); assert!(unsafe { !GlideOpenTelemetry::is_span_pointer_valid(0x8000_0000_0000_0000) }); assert!(unsafe { !GlideOpenTelemetry::is_span_pointer_valid(0xFFFF_FFFF_FFFF_FFFF) }); assert!(unsafe { GlideOpenTelemetry::is_span_pointer_valid(0x1000) }); assert!(unsafe { GlideOpenTelemetry::is_span_pointer_valid(0x10000) }); assert!(unsafe { GlideOpenTelemetry::is_span_pointer_valid(0x7FFF_FFFF_FFFF_FFF8) }); }
1537
1538 #[test]
1539 fn test_span_from_pointer_validation() {
1540 let rt = shared_runtime();
1541 rt.block_on(async {
1542 init_otel().await.unwrap();
1543
1544 let result = unsafe { GlideOpenTelemetry::span_from_pointer(0) };
1546 assert!(result.is_err());
1547 assert!(
1548 result
1549 .unwrap_err()
1550 .to_string()
1551 .contains("Invalid span pointer")
1552 );
1553
1554 let result = unsafe { GlideOpenTelemetry::span_from_pointer(0x1001) };
1556 assert!(result.is_err());
1557 assert!(
1558 result
1559 .unwrap_err()
1560 .to_string()
1561 .contains("failed validation checks")
1562 );
1563
1564 let result = unsafe { GlideOpenTelemetry::span_from_pointer(0x800) };
1566 assert!(result.is_err());
1567 assert!(
1568 result
1569 .unwrap_err()
1570 .to_string()
1571 .contains("failed validation checks")
1572 );
1573
1574 });
1577 }
1578
1579 #[test]
1580 fn test_new_with_parent_creates_child_span() {
1581 let rt = shared_runtime();
1582 rt.block_on(async {
1583 let _ = std::fs::remove_file(SPANS_JSON);
1584 init_otel().await.unwrap();
1585
1586 let parent_span = GlideOpenTelemetry::new_span("parent_span");
1588
1589 let child_span_result =
1591 GlideSpanInner::new_with_parent("child_span", &parent_span.inner);
1592 assert!(
1593 child_span_result.is_ok(),
1594 "Failed to create child span with parent"
1595 );
1596
1597 let child_span = child_span_result.unwrap();
1598
1599 let parent_id = parent_span.id();
1601 let child_id = child_span.id();
1602 assert_ne!(
1603 parent_id, child_id,
1604 "Child span should have different ID from parent"
1605 );
1606
1607 child_span.end();
1609 parent_span.end();
1610
1611 sleep(Duration::from_millis(2100)).await;
1613
1614 let file_content = std::fs::read_to_string(SPANS_JSON).unwrap();
1616 let lines: Vec<&str> = file_content
1617 .split('\n')
1618 .filter(|l| !l.trim().is_empty())
1619 .collect();
1620
1621 assert!(lines.len() >= 2, "Expected at least 2 spans to be exported");
1622
1623 let mut child_found = false;
1625 let mut parent_found = false;
1626
1627 for line in lines {
1628 let span_json: serde_json::Value = serde_json::from_str(line).unwrap();
1629 let span_name = span_json["name"].as_str().unwrap();
1630
1631 if span_name == "child_span" {
1632 child_found = true;
1633 assert!(
1635 span_json["parent_span_id"].is_string(),
1636 "Child span should have parent_span_id"
1637 );
1638 } else if span_name == "parent_span" {
1639 parent_found = true;
1640 }
1641 }
1642
1643 assert!(child_found, "Child span should be found in export");
1644 assert!(parent_found, "Parent span should be found in export");
1645 });
1646 }
1647
1648 #[test]
1649 fn test_new_with_parent_error_handling() {
1650 let rt = shared_runtime();
1651 rt.block_on(async {
1652 init_otel().await.unwrap();
1653
1654 let parent_span = GlideOpenTelemetry::new_span("error_test_parent");
1656
1657 let child_result = GlideSpanInner::new_with_parent("", &parent_span.inner);
1659 assert!(
1660 child_result.is_ok(),
1661 "Should be able to create child span with empty name"
1662 );
1663
1664 let long_name = "a".repeat(1000);
1666 let child_result = GlideSpanInner::new_with_parent(&long_name, &parent_span.inner);
1667 assert!(
1668 child_result.is_ok(),
1669 "Should be able to create child span with long name"
1670 );
1671
1672 if let Ok(child) = child_result {
1674 child.end();
1675 }
1676 parent_span.end();
1677 });
1678 }
1679
1680 #[test]
1681 fn test_span_from_pointer_error_messages() {
1682 let rt = shared_runtime();
1683 rt.block_on(async {
1684 init_otel().await.unwrap();
1685
1686 let result = unsafe { GlideOpenTelemetry::span_from_pointer(0) };
1688 assert!(result.is_err());
1689 let error_msg = result.unwrap_err().to_string();
1690 assert!(
1691 error_msg.contains("Invalid span pointer"),
1692 "Error message should mention invalid span pointer"
1693 );
1694 assert!(
1695 error_msg.contains("0x0"),
1696 "Error message should include the pointer value"
1697 );
1698
1699 let result = unsafe { GlideOpenTelemetry::span_from_pointer(0x1001) };
1701 assert!(result.is_err());
1702 let error_msg = result.unwrap_err().to_string();
1703 assert!(
1704 error_msg.contains("failed validation checks"),
1705 "Error message should mention validation failure"
1706 );
1707 assert!(
1708 error_msg.contains("0x1001"),
1709 "Error message should include the pointer value"
1710 );
1711
1712 let result = unsafe { GlideOpenTelemetry::span_from_pointer(0x800) };
1714 assert!(result.is_err());
1715 let error_msg = result.unwrap_err().to_string();
1716 assert!(
1717 error_msg.contains("failed validation checks"),
1718 "Error message should mention validation failure"
1719 );
1720 });
1721 }
1722
1723 #[test]
1724 fn test_new_with_remote_context_valid_inputs() {
1725 let rt = shared_runtime();
1726 rt.block_on(async {
1727 init_otel().await.unwrap();
1728
1729 let result = GlideSpanInner::new_with_remote_context(
1730 "remote_child",
1731 "0af7651916cd43dd8448eb211c80319c", "b7ad6b7169203331", 1, None,
1735 );
1736 assert!(result.is_ok(), "Valid inputs should return Ok");
1737
1738 let span = result.unwrap();
1739 span.end();
1740 });
1741 }
1742
1743 #[test]
1744 fn test_new_with_remote_context_invalid_trace_id() {
1745 let rt = shared_runtime();
1746 rt.block_on(async {
1747 init_otel().await.unwrap();
1748
1749 let result = GlideSpanInner::new_with_remote_context(
1750 "remote_child",
1751 "not_valid_hex",
1752 "b7ad6b7169203331",
1753 1,
1754 None,
1755 );
1756 assert!(result.is_err(), "Invalid trace_id should return Err");
1757 });
1758 }
1759
1760 #[test]
1761 fn test_new_with_remote_context_invalid_span_id() {
1762 let rt = shared_runtime();
1763 rt.block_on(async {
1764 init_otel().await.unwrap();
1765
1766 let result = GlideSpanInner::new_with_remote_context(
1767 "remote_child",
1768 "0af7651916cd43dd8448eb211c80319c",
1769 "zzzzzzzzzzzzzzzz", 1,
1771 None,
1772 );
1773 assert!(result.is_err(), "Invalid span_id should return Err");
1774 });
1775 }
1776
1777 #[test]
1778 fn test_validation_functions_fallback_behavior() {
1779 let rt = shared_runtime();
1780 rt.block_on(async {
1781 init_otel().await.unwrap();
1782
1783 let invalid_pointers = vec![
1787 0, 0x1001, 0x800, 0x8000_0000_0000_0000, ];
1792
1793 for &invalid_ptr in &invalid_pointers {
1794 assert!(
1796 unsafe { !GlideOpenTelemetry::is_span_pointer_valid(invalid_ptr) },
1797 "Pointer 0x{:x} should be invalid",
1798 invalid_ptr
1799 );
1800
1801 let result = unsafe { GlideOpenTelemetry::span_from_pointer(invalid_ptr) };
1803 assert!(
1804 result.is_err(),
1805 "Conversion of invalid pointer 0x{:x} should fail",
1806 invalid_ptr
1807 );
1808
1809 let error_msg = result.unwrap_err().to_string();
1811 assert!(
1812 error_msg.contains("Invalid span pointer")
1813 || error_msg.contains("failed validation"),
1814 "Error message should be meaningful for pointer 0x{:x}: {}",
1815 invalid_ptr,
1816 error_msg
1817 );
1818 }
1819 });
1820 }
1821}