From e91138351a689cd21923c15eb48f5fbc95ded807 Mon Sep 17 00:00:00 2001 From: slim Date: Mon, 21 Oct 2024 21:23:20 +0200 Subject: [PATCH 1/2] feat: update otel versions for prometheus to 0.26 (#2183) Co-authored-by: Cijo Thomas Co-authored-by: Lalit Kumar Bhasin --- opentelemetry-prometheus/CHANGELOG.md | 4 + opentelemetry-prometheus/Cargo.toml | 6 +- opentelemetry-prometheus/src/config.rs | 24 +---- opentelemetry-prometheus/src/lib.rs | 10 +-- .../tests/integration_test.rs | 89 ++++++++++--------- 5 files changed, 55 insertions(+), 78 deletions(-) diff --git a/opentelemetry-prometheus/CHANGELOG.md b/opentelemetry-prometheus/CHANGELOG.md index da42c5d26e..4d446beb29 100644 --- a/opentelemetry-prometheus/CHANGELOG.md +++ b/opentelemetry-prometheus/CHANGELOG.md @@ -3,6 +3,10 @@ ## vNext - Bump MSRV to 1.70 [#2179](https://github.com/open-telemetry/opentelemetry-rust/pull/2179) +- Update `opentelemetry` dependency version to 0.26 +- Update `opentelemetry_sdk` dependency version to 0.26 +- Update `opentelemetry-semantic-conventions` dependency version to 0.26 + ## v0.17.0 diff --git a/opentelemetry-prometheus/Cargo.toml b/opentelemetry-prometheus/Cargo.toml index 5c22b49124..71d296bdf2 100644 --- a/opentelemetry-prometheus/Cargo.toml +++ b/opentelemetry-prometheus/Cargo.toml @@ -21,13 +21,13 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] once_cell = { workspace = true } -opentelemetry = { version = "0.24", default-features = false, features = ["metrics"] } -opentelemetry_sdk = { version = "0.24", default-features = false, features = ["metrics"] } +opentelemetry = { version = "0.26", default-features = false, features = ["metrics"] } +opentelemetry_sdk = { version = "0.26", default-features = false, features = ["metrics"] } prometheus = "0.13" protobuf = "2.14" [dev-dependencies] -opentelemetry-semantic-conventions = { version = "0.16" } +opentelemetry-semantic-conventions = { version = "0.26" } http-body-util = { workspace = true } hyper = { workspace = true, features = ["full"] } hyper-util = { workspace = true, features = ["full"] } diff --git a/opentelemetry-prometheus/src/config.rs b/opentelemetry-prometheus/src/config.rs index 0143e59692..40d6f6779b 100644 --- a/opentelemetry-prometheus/src/config.rs +++ b/opentelemetry-prometheus/src/config.rs @@ -1,10 +1,7 @@ use core::fmt; use once_cell::sync::OnceCell; use opentelemetry::metrics::{MetricsError, Result}; -use opentelemetry_sdk::metrics::{ - reader::{AggregationSelector, MetricProducer}, - ManualReaderBuilder, -}; +use opentelemetry_sdk::metrics::ManualReaderBuilder; use std::sync::{Arc, Mutex}; use crate::{Collector, PrometheusExporter, ResourceSelector}; @@ -105,16 +102,6 @@ impl ExporterBuilder { self } - /// Configure the [AggregationSelector] the exporter will use. - /// - /// If no selector is provided, the [DefaultAggregationSelector] is used. - /// - /// [DefaultAggregationSelector]: opentelemetry_sdk::metrics::reader::DefaultAggregationSelector - pub fn with_aggregation_selector(mut self, agg: impl AggregationSelector + 'static) -> Self { - self.reader = self.reader.with_aggregation_selector(agg); - self - } - /// Configures whether to export resource as attributes with every metric. /// /// Note that this is orthogonal to the `target_info` metric, which can be disabled using `without_target_info`. @@ -128,15 +115,6 @@ impl ExporterBuilder { self } - /// Registers an external [MetricProducer] with this reader. - /// - /// The producer is used as a source of aggregated metric data which is - /// incorporated into metrics collected from the SDK. - pub fn with_producer(mut self, producer: impl MetricProducer + 'static) -> Self { - self.reader = self.reader.with_producer(producer); - self - } - /// Creates a new [PrometheusExporter] from this configuration. pub fn build(self) -> Result { let reader = Arc::new(self.reader.build()); diff --git a/opentelemetry-prometheus/src/lib.rs b/opentelemetry-prometheus/src/lib.rs index 64cf6e5266..28383d6beb 100644 --- a/opentelemetry-prometheus/src/lib.rs +++ b/opentelemetry-prometheus/src/lib.rs @@ -105,8 +105,8 @@ use opentelemetry::{ use opentelemetry_sdk::{ metrics::{ data::{self, ResourceMetrics, Temporality}, - reader::{AggregationSelector, MetricReader, TemporalitySelector}, - Aggregation, InstrumentKind, ManualReader, Pipeline, + reader::{MetricReader, TemporalitySelector}, + InstrumentKind, ManualReader, Pipeline, }, Resource, Scope, }; @@ -160,12 +160,6 @@ impl TemporalitySelector for PrometheusExporter { } } -impl AggregationSelector for PrometheusExporter { - fn aggregation(&self, kind: InstrumentKind) -> Aggregation { - self.reader.aggregation(kind) - } -} - impl MetricReader for PrometheusExporter { fn register_pipeline(&self, pipeline: Weak) { self.reader.register_pipeline(pipeline) diff --git a/opentelemetry-prometheus/tests/integration_test.rs b/opentelemetry-prometheus/tests/integration_test.rs index 906290a092..34963a1121 100644 --- a/opentelemetry-prometheus/tests/integration_test.rs +++ b/opentelemetry-prometheus/tests/integration_test.rs @@ -15,6 +15,7 @@ use opentelemetry_sdk::Resource; use opentelemetry_semantic_conventions::resource::{SERVICE_NAME, TELEMETRY_SDK_VERSION}; use prometheus::{Encoder, TextEncoder}; +#[ignore = "https://github.com/open-telemetry/opentelemetry-rust/pull/2224"] #[test] fn prometheus_exporter_integration() { struct TestCase { @@ -46,10 +47,10 @@ fn prometheus_exporter_integration() { expected_file: "counter.txt", record_metrics: Box::new(|meter| { let attrs = vec![ - Key::new("A").string("B"), - Key::new("C").string("D"), - Key::new("E").bool(true), - Key::new("F").i64(42), + KeyValue::new("A", "B"), + KeyValue::new("C", "D"), + KeyValue::new("E", true), + KeyValue::new("F", 42), ]; let counter = meter .f64_counter("foo") @@ -60,10 +61,10 @@ fn prometheus_exporter_integration() { counter.add(10.3, &attrs); counter.add(9.0, &attrs); let attrs2 = vec![ - Key::new("A").string("D"), - Key::new("C").string("B"), - Key::new("E").bool(true), - Key::new("F").i64(42), + KeyValue::new("A", "D"), + KeyValue::new("C", "B"), + KeyValue::new("E", true), + KeyValue::new("F", 42), ]; counter.add(5.0, &attrs2); }), @@ -75,10 +76,10 @@ fn prometheus_exporter_integration() { builder: ExporterBuilder::default().without_counter_suffixes(), record_metrics: Box::new(|meter| { let attrs = vec![ - Key::new("A").string("B"), - Key::new("C").string("D"), - Key::new("E").bool(true), - Key::new("F").i64(42), + KeyValue::new("A", "B"), + KeyValue::new("C", "D"), + KeyValue::new("E", true), + KeyValue::new("F", 42), ]; let counter = meter .f64_counter("foo") @@ -89,10 +90,10 @@ fn prometheus_exporter_integration() { counter.add(10.3, &attrs); counter.add(9.0, &attrs); let attrs2 = vec![ - Key::new("A").string("D"), - Key::new("C").string("B"), - Key::new("E").bool(true), - Key::new("F").i64(42), + KeyValue::new("A", "D"), + KeyValue::new("C", "B"), + KeyValue::new("E", true), + KeyValue::new("F", 42), ]; counter.add(5.0, &attrs2); }), @@ -102,7 +103,7 @@ fn prometheus_exporter_integration() { name: "gauge", expected_file: "gauge.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = vec![KeyValue::new("A", "B"), KeyValue::new("C", "D")]; let gauge = meter .f64_up_down_counter("bar") .with_description("a fun little gauge") @@ -117,7 +118,7 @@ fn prometheus_exporter_integration() { name: "histogram", expected_file: "histogram.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = vec![KeyValue::new("A", "B"), KeyValue::new("C", "D")]; let histogram = meter .f64_histogram("histogram_baz") .with_description("a very nice histogram") @@ -137,11 +138,11 @@ fn prometheus_exporter_integration() { record_metrics: Box::new(|meter| { let attrs = vec![ // exact match, value should be overwritten - Key::new("A.B").string("X"), - Key::new("A.B").string("Q"), + KeyValue::new("A.B", "X"), + KeyValue::new("A.B", "Q"), // unintended match due to sanitization, values should be concatenated - Key::new("C.D").string("Y"), - Key::new("C/D").string("Z"), + KeyValue::new("C.D", "Y"), + KeyValue::new("C/D", "Z"), ]; let counter = meter .f64_counter("foo") @@ -159,7 +160,7 @@ fn prometheus_exporter_integration() { name: "invalid instruments are renamed", expected_file: "sanitized_names.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = vec![KeyValue::new("A", "B"), KeyValue::new("C", "D")]; // Valid. let mut gauge = meter .f64_up_down_counter("bar") @@ -195,10 +196,10 @@ fn prometheus_exporter_integration() { expected_file: "empty_resource.txt", record_metrics: Box::new(|meter| { let attrs = vec![ - Key::new("A").string("B"), - Key::new("C").string("D"), - Key::new("E").bool(true), - Key::new("F").i64(42), + KeyValue::new("A", "B"), + KeyValue::new("C", "D"), + KeyValue::new("E", true), + KeyValue::new("F", 42), ]; let counter = meter .f64_counter("foo") @@ -212,14 +213,14 @@ fn prometheus_exporter_integration() { }, TestCase { name: "custom resource", - custom_resource_attrs: vec![Key::new("A").string("B"), Key::new("C").string("D")], + custom_resource_attrs: vec![KeyValue::new("A", "B"), KeyValue::new("C", "D")], expected_file: "custom_resource.txt", record_metrics: Box::new(|meter| { let attrs = vec![ - Key::new("A").string("B"), - Key::new("C").string("D"), - Key::new("E").bool(true), - Key::new("F").i64(42), + KeyValue::new("A", "B"), + KeyValue::new("C", "D"), + KeyValue::new("E", true), + KeyValue::new("F", 42), ]; let counter = meter .f64_counter("foo") @@ -237,10 +238,10 @@ fn prometheus_exporter_integration() { expected_file: "without_target_info.txt", record_metrics: Box::new(|meter| { let attrs = vec![ - Key::new("A").string("B"), - Key::new("C").string("D"), - Key::new("E").bool(true), - Key::new("F").i64(42), + KeyValue::new("A", "B"), + KeyValue::new("C", "D"), + KeyValue::new("E", true), + KeyValue::new("F", 42), ]; let counter = meter .f64_counter("foo") @@ -257,7 +258,7 @@ fn prometheus_exporter_integration() { builder: ExporterBuilder::default().without_scope_info(), expected_file: "without_scope_info.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = vec![KeyValue::new("A", "B"), KeyValue::new("C", "D")]; let gauge = meter .i64_up_down_counter("bar") .with_description("a fun little gauge") @@ -275,7 +276,7 @@ fn prometheus_exporter_integration() { .without_target_info(), expected_file: "without_scope_and_target_info.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = vec![KeyValue::new("A", "B"), KeyValue::new("C", "D")]; let counter = meter .u64_counter("bar") .with_description("a fun little counter") @@ -292,10 +293,10 @@ fn prometheus_exporter_integration() { expected_file: "with_namespace.txt", record_metrics: Box::new(|meter| { let attrs = vec![ - Key::new("A").string("B"), - Key::new("C").string("D"), - Key::new("E").bool(true), - Key::new("F").i64(42), + KeyValue::new("A", "B"), + KeyValue::new("C", "D"), + KeyValue::new("E", true), + KeyValue::new("F", 42), ]; let counter = meter .f64_counter("foo") @@ -313,7 +314,7 @@ fn prometheus_exporter_integration() { builder: ExporterBuilder::default().with_resource_selector(ResourceSelector::All), expected_file: "resource_in_every_metrics.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = vec![KeyValue::new("A", "B"), KeyValue::new("C", "D")]; let gauge = meter .i64_up_down_counter("bar") .with_description("a fun little gauge") @@ -330,7 +331,7 @@ fn prometheus_exporter_integration() { .with_resource_selector(HashSet::from([Key::new("service.name")])), expected_file: "select_resource_in_every_metrics.txt", record_metrics: Box::new(|meter| { - let attrs = vec![Key::new("A").string("B"), Key::new("C").string("D")]; + let attrs = vec![KeyValue::new("A", "B"), KeyValue::new("C", "D")]; let gauge = meter .i64_up_down_counter("bar") .with_description("a fun little gauge") From ea4b5e4d992610bb23a059f05253d0c47e262015 Mon Sep 17 00:00:00 2001 From: Lalit Kumar Bhasin Date: Mon, 21 Oct 2024 15:30:50 -0700 Subject: [PATCH 2/2] Add Unit Tests for Sync and Async Log Exporters - with and without runtime - for SimpleLogProcessor (#2218) --- opentelemetry-sdk/src/logs/log_processor.rs | 245 +++++++++++++++++++- 1 file changed, 233 insertions(+), 12 deletions(-) diff --git a/opentelemetry-sdk/src/logs/log_processor.rs b/opentelemetry-sdk/src/logs/log_processor.rs index d74047fe13..47489881b0 100644 --- a/opentelemetry-sdk/src/logs/log_processor.rs +++ b/opentelemetry-sdk/src/logs/log_processor.rs @@ -553,6 +553,7 @@ mod tests { use opentelemetry::InstrumentationLibrary; use opentelemetry::Key; use opentelemetry::{logs::LogResult, KeyValue}; + use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -831,9 +832,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] #[ignore = "See issue https://github.com/open-telemetry/opentelemetry-rust/issues/1968"] async fn test_batch_log_processor_shutdown_with_async_runtime_current_flavor_multi_thread() { - let exporter = InMemoryLogsExporterBuilder::default() - .keep_records_on_shutdown() - .build(); + let exporter = InMemoryLogsExporterBuilder::default().build(); let processor = BatchLogProcessor::new( Box::new(exporter.clone()), BatchConfig::default(), @@ -848,9 +847,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn test_batch_log_processor_shutdown_with_async_runtime_current_flavor_current_thread() { - let exporter = InMemoryLogsExporterBuilder::default() - .keep_records_on_shutdown() - .build(); + let exporter = InMemoryLogsExporterBuilder::default().build(); let processor = BatchLogProcessor::new( Box::new(exporter.clone()), BatchConfig::default(), @@ -862,9 +859,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_batch_log_processor_shutdown_with_async_runtime_multi_flavor_multi_thread() { - let exporter = InMemoryLogsExporterBuilder::default() - .keep_records_on_shutdown() - .build(); + let exporter = InMemoryLogsExporterBuilder::default().build(); let processor = BatchLogProcessor::new( Box::new(exporter.clone()), BatchConfig::default(), @@ -876,9 +871,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_batch_log_processor_shutdown_with_async_runtime_multi_flavor_current_thread() { - let exporter = InMemoryLogsExporterBuilder::default() - .keep_records_on_shutdown() - .build(); + let exporter = InMemoryLogsExporterBuilder::default().build(); let processor = BatchLogProcessor::new( Box::new(exporter.clone()), BatchConfig::default(), @@ -994,4 +987,232 @@ mod tests { == AnyValue::String("Updated by FirstProcessor".into()) ); } + + #[test] + fn test_simple_processor_sync_exporter_without_runtime() { + let exporter = InMemoryLogsExporterBuilder::default().build(); + let processor = SimpleLogProcessor::new(Box::new(exporter.clone())); + + let mut record: LogRecord = Default::default(); + let instrumentation: InstrumentationLibrary = Default::default(); + + processor.emit(&mut record, &instrumentation); + + assert_eq!(exporter.get_emitted_logs().unwrap().len(), 1); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + async fn test_simple_processor_sync_exporter_with_runtime() { + let exporter = InMemoryLogsExporterBuilder::default().build(); + let processor = SimpleLogProcessor::new(Box::new(exporter.clone())); + + let mut record: LogRecord = Default::default(); + let instrumentation: InstrumentationLibrary = Default::default(); + + processor.emit(&mut record, &instrumentation); + + assert_eq!(exporter.get_emitted_logs().unwrap().len(), 1); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_simple_processor_sync_exporter_with_multi_thread_runtime() { + let exporter = InMemoryLogsExporterBuilder::default().build(); + let processor = Arc::new(SimpleLogProcessor::new(Box::new(exporter.clone()))); + + let mut handles = vec![]; + for _ in 0..10 { + let processor_clone = Arc::clone(&processor); + let handle = tokio::spawn(async move { + let mut record: LogRecord = Default::default(); + let instrumentation: InstrumentationLibrary = Default::default(); + processor_clone.emit(&mut record, &instrumentation); + }); + handles.push(handle); + } + + for handle in handles { + handle.await.unwrap(); + } + + assert_eq!(exporter.get_emitted_logs().unwrap().len(), 10); + } + + #[tokio::test(flavor = "current_thread")] + async fn test_simple_processor_sync_exporter_with_current_thread_runtime() { + let exporter = InMemoryLogsExporterBuilder::default().build(); + let processor = SimpleLogProcessor::new(Box::new(exporter.clone())); + + let mut record: LogRecord = Default::default(); + let instrumentation: InstrumentationLibrary = Default::default(); + + processor.emit(&mut record, &instrumentation); + + assert_eq!(exporter.get_emitted_logs().unwrap().len(), 1); + } + + #[derive(Debug, Clone)] + struct LogExporterThatRequiresTokio { + export_count: Arc, + } + + impl LogExporterThatRequiresTokio { + /// Creates a new instance of `LogExporterThatRequiresTokio`. + fn new() -> Self { + LogExporterThatRequiresTokio { + export_count: Arc::new(AtomicUsize::new(0)), + } + } + + /// Returns the number of logs stored in the exporter. + fn len(&self) -> usize { + self.export_count.load(Ordering::Acquire) + } + } + + #[async_trait::async_trait] + impl LogExporter for LogExporterThatRequiresTokio { + async fn export(&mut self, batch: LogBatch<'_>) -> LogResult<()> { + // Simulate minimal dependency on tokio by sleeping asynchronously for a short duration + tokio::time::sleep(Duration::from_millis(50)).await; + + for _ in batch.iter() { + self.export_count.fetch_add(1, Ordering::Acquire); + } + Ok(()) + } + } + + #[test] + fn test_simple_processor_async_exporter_without_runtime() { + // Use `catch_unwind` to catch the panic caused by missing Tokio runtime + let result = std::panic::catch_unwind(|| { + let exporter = LogExporterThatRequiresTokio::new(); + let processor = SimpleLogProcessor::new(Box::new(exporter.clone())); + + let mut record: LogRecord = Default::default(); + let instrumentation: InstrumentationLibrary = Default::default(); + + // This will panic because an tokio async operation within exporter without a runtime. + processor.emit(&mut record, &instrumentation); + }); + + // Verify that the panic occurred and check the panic message for the absence of a Tokio runtime + assert!( + result.is_err(), + "The test should fail due to missing Tokio runtime, but it did not." + ); + let panic_payload = result.unwrap_err(); + let panic_message = panic_payload + .downcast_ref::() + .map(|s| s.as_str()) + .or_else(|| panic_payload.downcast_ref::<&str>().copied()) + .unwrap_or("No panic message"); + + assert!( + panic_message.contains("no reactor running") + || panic_message.contains("must be called from the context of a Tokio 1.x runtime"), + "Expected panic message about missing Tokio runtime, but got: {}", + panic_message + ); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] + #[ignore] + // This test demonstrates a potential deadlock scenario in a multi-threaded Tokio runtime. + // It spawns Tokio tasks equal to the number of runtime worker threads (4) to emit log events. + // Each task attempts to acquire a mutex on the exporter in `SimpleLogProcessor::emit`. + // Only one task obtains the lock, while the others are blocked, waiting for its release. + // + // The task holding the lock invokes the LogExporterThatRequiresTokio, which performs an + // asynchronous operation (e.g., network I/O simulated by `tokio::sleep`). This operation + // requires yielding control back to the Tokio runtime to make progress. + // + // However, all worker threads are occupied: + // - One thread is executing the async exporter operation + // - Three threads are blocked waiting for the mutex + // + // This leads to a deadlock as there are no available threads to drive the async operation + // to completion, preventing the mutex from being released. Consequently, neither the blocked + // tasks nor the exporter can proceed. + async fn test_simple_processor_async_exporter_with_all_runtime_worker_threads_blocked() { + let exporter = LogExporterThatRequiresTokio::new(); + let processor = Arc::new(SimpleLogProcessor::new(Box::new(exporter.clone()))); + + let concurrent_emit = 4; // number of worker threads + + let mut handles = vec![]; + // try send `concurrent_emit` events concurrently + for _ in 0..concurrent_emit { + let processor_clone = Arc::clone(&processor); + let handle = tokio::spawn(async move { + let mut record: LogRecord = Default::default(); + let instrumentation: InstrumentationLibrary = Default::default(); + processor_clone.emit(&mut record, &instrumentation); + }); + handles.push(handle); + } + + // below code won't get executed + for handle in handles { + handle.await.unwrap(); + } + assert_eq!(exporter.len(), concurrent_emit); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + // This test uses a multi-threaded runtime setup with a single worker thread. Note that even + // though only one worker thread is created, it is distinct from the main thread. The processor + // emits a log event, and the exporter performs an async operation that requires the runtime. + // The single worker thread handles this operation without deadlocking, as long as no other + // tasks occupy the runtime. + async fn test_simple_processor_async_exporter_with_runtime() { + let exporter = LogExporterThatRequiresTokio::new(); + let processor = SimpleLogProcessor::new(Box::new(exporter.clone())); + + let mut record: LogRecord = Default::default(); + let instrumentation: InstrumentationLibrary = Default::default(); + + processor.emit(&mut record, &instrumentation); + + assert_eq!(exporter.len(), 1); + } + + #[tokio::test(flavor = "multi_thread")] + // This test uses a multi-threaded runtime setup with the default number of worker threads. + // The processor emits a log event, and the exporter, which requires the runtime for its async + // operations, can access one of the available worker threads to complete its task. As there + // are multiple threads, the exporter can proceed without blocking other tasks, ensuring the + // test completes successfully. + async fn test_simple_processor_async_exporter_with_multi_thread_runtime() { + let exporter = LogExporterThatRequiresTokio::new(); + + let processor = SimpleLogProcessor::new(Box::new(exporter.clone())); + + let mut record: LogRecord = Default::default(); + let instrumentation: InstrumentationLibrary = Default::default(); + + processor.emit(&mut record, &instrumentation); + + assert_eq!(exporter.len(), 1); + } + + #[tokio::test(flavor = "current_thread")] + #[ignore] + // This test uses a current-thread runtime, where all operations run on the main thread. + // The processor emits a log event while the runtime is blocked using `futures::block_on` + // to complete the export operation. The exporter, which performs an async operation and + // requires the runtime, cannot progress because the main thread is already blocked. + // This results in a deadlock, as the runtime cannot move forward. + async fn test_simple_processor_async_exporter_with_current_thread_runtime() { + let exporter = LogExporterThatRequiresTokio::new(); + + let processor = SimpleLogProcessor::new(Box::new(exporter.clone())); + + let mut record: LogRecord = Default::default(); + let instrumentation: InstrumentationLibrary = Default::default(); + + processor.emit(&mut record, &instrumentation); + + assert_eq!(exporter.len(), 1); + } }