Skip to content

Commit 2440573

Browse files
committed
feat: Leverage Suppression Context in Sdk
1 parent 2971467 commit 2440573

File tree

14 files changed

+58
-19
lines changed

14 files changed

+58
-19
lines changed

docs/design/logs.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,25 @@ only meant for OTel components itself and anyone writing extensions like custom
345345
Exporters etc.
346346

347347
// TODO: Document the principles followed when selecting severity for internal
348-
logs // TODO: Document how this can cause circular loop and plans to address it.
348+
logs
349+
350+
When OpenTelemetry components generate logs that could potentially feed back
351+
into OpenTelemetry, this can result in what is known as "telemetry-induced
352+
telemetry." To address this, OpenTelemetry provides a mechanism to suppress such
353+
telemetry using the `Context`. Components are expected to mark telemetry as
354+
suppressed within a specific `Context` by invoking
355+
`Context::enter_telemetry_suppressed_scope()`. The Logs SDK implementation
356+
checks this flag in the current `Context` and ignores logs if suppression is
357+
enabled.
358+
359+
This mechanism relies on proper in-process propagation of the `Context`.
360+
However, external libraries like `hyper` and `tonic`, which are used by
361+
OpenTelemetry in its OTLP Exporters, do not propagate OpenTelemetry's `Context`.
362+
As a result, the suppression mechanism does not work out-of-the-box to suppress
363+
logs originating from these libraries.
364+
365+
// TODO: Document how OTLP can solve this issue without asking external
366+
crates to respect and propagate OTel Context.
349367

350368
## Summary
351369

examples/logs-basic/src/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ fn main() {
2727
// https://github.com/open-telemetry/opentelemetry-rust/issues/761
2828
let filter_otel = EnvFilter::new("info")
2929
.add_directive("hyper=off".parse().unwrap())
30-
.add_directive("opentelemetry=off".parse().unwrap())
3130
.add_directive("tonic=off".parse().unwrap())
3231
.add_directive("h2=off".parse().unwrap())
3332
.add_directive("reqwest=off".parse().unwrap());

opentelemetry-appender-tracing/src/layer.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,8 @@ mod tests {
285285
use opentelemetry::logs::Severity;
286286
use opentelemetry::trace::TracerProvider;
287287
use opentelemetry::trace::{TraceContextExt, TraceFlags, Tracer};
288-
use opentelemetry::InstrumentationScope;
289288
use opentelemetry::{logs::AnyValue, Key};
289+
use opentelemetry::{Context, InstrumentationScope};
290290
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
291291
use opentelemetry_sdk::logs::{InMemoryLogExporter, LogProcessor};
292292
use opentelemetry_sdk::logs::{LogBatch, LogExporter};
@@ -319,25 +319,26 @@ mod tests {
319319

320320
impl LogExporter for ReentrantLogExporter {
321321
async fn export(&self, _batch: LogBatch<'_>) -> OTelSdkResult {
322-
// This will cause a deadlock as the export itself creates a log
322+
let _suppress = Context::enter_telemetry_suppressed_scope();
323+
// Without the suppression above, this will cause a deadlock as the export itself creates a log
323324
// while still within the lock of the SimpleLogProcessor.
324325
warn!(name: "my-event-name", target: "reentrant", event_id = 20, user_name = "otel", user_email = "[email protected]");
325326
Ok(())
326327
}
327328
}
328329

329330
#[test]
330-
#[ignore = "See issue: https://github.com/open-telemetry/opentelemetry-rust/issues/1745"]
331+
#[ignore = "While this test runs fine, this uses global subscriber and does not play well with other tests and hence ignored in CI."]
331332
fn simple_processor_deadlock() {
333+
// TODO: This test maybe better suited in the opentelemetry-sdk crate tests
332334
let exporter: ReentrantLogExporter = ReentrantLogExporter;
333335
let logger_provider = SdkLoggerProvider::builder()
334-
.with_simple_exporter(exporter.clone())
336+
.with_simple_exporter(exporter)
335337
.build();
336338

337339
let layer = layer::OpenTelemetryTracingBridge::new(&logger_provider);
338-
339-
// Setting subscriber as global as that is the only way to test this scenario.
340340
tracing_subscriber::registry().with(layer).init();
341+
341342
warn!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "[email protected]");
342343
}
343344

opentelemetry-otlp/examples/basic-otlp-http/src/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
8484
// https://github.com/open-telemetry/opentelemetry-rust/issues/761
8585
let filter_otel = EnvFilter::new("info")
8686
.add_directive("hyper=off".parse().unwrap())
87-
.add_directive("opentelemetry=off".parse().unwrap())
8887
.add_directive("tonic=off".parse().unwrap())
8988
.add_directive("h2=off".parse().unwrap())
9089
.add_directive("reqwest=off".parse().unwrap());

opentelemetry-otlp/examples/basic-otlp/src/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
7878
// https://github.com/open-telemetry/opentelemetry-rust/issues/761
7979
let filter_otel = EnvFilter::new("info")
8080
.add_directive("hyper=off".parse().unwrap())
81-
.add_directive("opentelemetry=off".parse().unwrap())
8281
.add_directive("tonic=off".parse().unwrap())
8382
.add_directive("h2=off".parse().unwrap())
8483
.add_directive("reqwest=off".parse().unwrap());

opentelemetry-sdk/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
## vNext
44

5+
`SdkLogger` modified to respect telemetry suppression based on `Context`. In
6+
other words, if the current context has telemetry suppression enabled, then logs
7+
will be ignored. The flag is typically set by OTel components to prevent
8+
telemetry from itself being fed back into OTel. `BatchLogProcessor`,
9+
`BatchSpanProcessor`, and `PeriodicReader` modified to set the suppression flag
10+
in their dedicated thread, so that telemetry generated from those threads will
11+
not be fed back into OTel. Similarly, `SimpleSpanProcessor` and
12+
`SimpleLogProcessor` also modified to suppress telemetry before invoking
13+
exporters.
14+
515
## 0.29.0
616

717
Released 2025-Mar-21

opentelemetry-sdk/benches/log_enabled.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
Total Number of Cores:   14 (10 performance and 4 efficiency)
66
| Test | Average time|
77
|---------------------------------------------|-------------|
8-
| exporter_disabled_concurrent_processor | 1.1 ns |
9-
| exporter_disabled_simple_processor | 4.3 ns |
8+
| exporter_disabled_concurrent_processor | 2.5 ns |
9+
| exporter_disabled_simple_processor | 5.3 ns |
1010
*/
1111

1212
// cargo bench --bench log_enabled --features="spec_unstable_logs_enabled,experimental_logs_concurrent_log_processor"

opentelemetry-sdk/src/logs/batch_log_processor.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::{
2323
};
2424
use std::sync::mpsc::{self, RecvTimeoutError, SyncSender};
2525

26-
use opentelemetry::{otel_debug, otel_error, otel_warn, InstrumentationScope};
26+
use opentelemetry::{otel_debug, otel_error, otel_warn, Context, InstrumentationScope};
2727

2828
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
2929
use std::{cmp::min, env, sync::Mutex};
@@ -342,6 +342,7 @@ impl BatchLogProcessor {
342342
let handle = thread::Builder::new()
343343
.name("OpenTelemetry.Logs.BatchProcessor".to_string())
344344
.spawn(move || {
345+
let _suppress_guard = Context::enter_telemetry_suppressed_scope();
345346
otel_debug!(
346347
name: "BatchLogProcessor.ThreadStarted",
347348
interval_in_millisecs = config.scheduled_delay.as_millis(),

opentelemetry-sdk/src/logs/logger.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ impl opentelemetry::logs::Logger for SdkLogger {
2929

3030
/// Emit a `LogRecord`.
3131
fn emit(&self, mut record: Self::LogRecord) {
32+
if Context::is_current_telemetry_suppressed() {
33+
return;
34+
}
3235
let provider = &self.provider;
3336
let processors = provider.log_processors();
3437

@@ -52,6 +55,9 @@ impl opentelemetry::logs::Logger for SdkLogger {
5255
#[cfg(feature = "spec_unstable_logs_enabled")]
5356
#[inline]
5457
fn event_enabled(&self, level: Severity, target: &str, name: Option<&str>) -> bool {
58+
if Context::is_current_telemetry_suppressed() {
59+
return false;
60+
}
5561
self.provider
5662
.log_processors()
5763
.iter()

opentelemetry-sdk/src/logs/logger_provider.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ impl opentelemetry::logs::LoggerProvider for SdkLoggerProvider {
5252
}
5353

5454
fn logger_with_scope(&self, scope: InstrumentationScope) -> Self::Logger {
55+
// TODO: Should this check suppression context and return a no-op logger?
5556
// If the provider is shutdown, new logger will refer a no-op logger provider.
5657
if self.inner.is_shutdown.load(Ordering::Relaxed) {
5758
otel_debug!(

0 commit comments

Comments
 (0)