Skip to content

feat: add support for OTEL_SDK_DISABLED environment variable #3088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions opentelemetry-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
The logs functionality now operates independently, while automatic correlation
between logs and traces continues to work when the "trace" feature is
explicitly enabled.
- **Feature**: Add support for `OTEL_SDK_DISABLED` environment variable ([#3088](https://github.com/open-telemetry/opentelemetry-rust/pull/3088))


## 0.30.0

Expand Down
5 changes: 5 additions & 0 deletions opentelemetry-sdk/src/logs/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ impl SdkLogger {
pub(crate) fn new(scope: InstrumentationScope, provider: SdkLoggerProvider) -> Self {
SdkLogger { scope, provider }
}

#[cfg(test)]
pub(crate) fn provider(&self) -> &SdkLoggerProvider {
&self.provider
}
}

impl opentelemetry::logs::Logger for SdkLogger {
Expand Down
72 changes: 65 additions & 7 deletions opentelemetry-sdk/src/logs/logger_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{BatchLogProcessor, LogProcessor, SdkLogger, SimpleLogProcessor};
use crate::error::{OTelSdkError, OTelSdkResult};
use crate::logs::LogExporter;
use crate::Resource;
use opentelemetry::{otel_debug, otel_info, InstrumentationScope};
use opentelemetry::{otel_debug, otel_info, otel_warn, InstrumentationScope};
use std::time::Duration;
use std::{
borrow::Cow,
Expand All @@ -12,16 +12,32 @@ use std::{
},
};

// a no nop logger provider used as placeholder when the provider is shutdown
// a no op logger provider used as placeholder when the provider is shutdown
// TODO - replace it with LazyLock once it is stable
static NOOP_LOGGER_PROVIDER: OnceLock<SdkLoggerProvider> = OnceLock::new();
static SHUTDOWN_LOGGER_PROVIDER: OnceLock<SdkLoggerProvider> = OnceLock::new();

#[inline]
fn noop_logger_provider() -> &'static SdkLoggerProvider {
NOOP_LOGGER_PROVIDER.get_or_init(|| SdkLoggerProvider {
fn shutdown_logger_provider() -> &'static SdkLoggerProvider {
SHUTDOWN_LOGGER_PROVIDER.get_or_init(|| SdkLoggerProvider {
inner: Arc::new(LoggerProviderInner {
processors: Vec::new(),
is_shutdown: AtomicBool::new(true),
is_disabled: false,
}),
})
}
// a no op logger provider used as placeholder when sdk is disabled with
// help of environment variable `OTEL_SDK_DISABLED`
// TODO - replace it with LazyLock once it is stable
static DISABLED_LOGGER_PROVIDER: OnceLock<SdkLoggerProvider> = OnceLock::new();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we reuse the existing NOOP_LOGGER_PROVIDER for both shutdown and disable ?

Copy link
Author

@iamsauravsharma iamsauravsharma Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setting is_shutdown as true also for disabled one have no effect currently but to me true value looks wrong since logger provider is not in shutdown state but disabled by user so i have created new logger provider


#[inline]
fn disabled_logger_provider() -> &'static SdkLoggerProvider {
DISABLED_LOGGER_PROVIDER.get_or_init(|| SdkLoggerProvider {
inner: Arc::new(LoggerProviderInner {
processors: Vec::new(),
is_shutdown: AtomicBool::new(false),
is_disabled: true,
}),
})
}
Expand Down Expand Up @@ -53,13 +69,18 @@ impl opentelemetry::logs::LoggerProvider for SdkLoggerProvider {
}

fn logger_with_scope(&self, scope: InstrumentationScope) -> Self::Logger {
// If the provider is shutdown, new logger will refer a no-op logger provider.
// If the provider is shutdown, new logger will refer a shutdown no-op logger provider.
if self.inner.is_shutdown.load(Ordering::Relaxed) {
otel_debug!(
name: "LoggerProvider.NoOpLoggerReturned",
logger_name = scope.name(),
);
return SdkLogger::new(scope, noop_logger_provider().clone());
return SdkLogger::new(scope, shutdown_logger_provider().clone());
}
// If the provider is disabled, new logger will refer a disabled no-op logger provider.
if self.inner.is_disabled {
otel_warn!(name: "LoggerProvider.NoOpLoggerReturned", message = "Returned NoOpLogger. SDK is disabled");
return SdkLogger::new(scope, disabled_logger_provider().clone());
}
if scope.name().is_empty() {
otel_info!(name: "LoggerNameEmpty", message = "Logger name is empty; consider providing a meaningful name. Logger will function normally and the provided name will be used as-is.");
Expand Down Expand Up @@ -135,6 +156,7 @@ impl SdkLoggerProvider {
struct LoggerProviderInner {
processors: Vec<Box<dyn LogProcessor>>,
is_shutdown: AtomicBool,
is_disabled: bool,
}

impl LoggerProviderInner {
Expand Down Expand Up @@ -267,10 +289,18 @@ impl LoggerProviderBuilder {
processor.set_resource(&resource);
}

let is_disabled =
std::env::var("OTEL_SDK_DISABLED").is_ok_and(|var| var.to_lowercase() == "true");

if is_disabled {
otel_warn!(name: "LoggerProvider.Disabled", message = "SDK is disabled through environment variable");
}

let logger_provider = SdkLoggerProvider {
inner: Arc::new(LoggerProviderInner {
processors,
is_shutdown: AtomicBool::new(false),
is_disabled,
}),
};

Expand Down Expand Up @@ -749,6 +779,7 @@ mod tests {
flush_called.clone(),
))],
is_shutdown: AtomicBool::new(false),
is_disabled: false,
});

{
Expand Down Expand Up @@ -777,6 +808,32 @@ mod tests {
assert!(!*flush_called.lock().unwrap());
}

#[test]
#[ignore = "modifies OTEL_SDK_DISABLED env var which can affect other test"]
fn otel_sdk_disabled_env() {
temp_env::with_var("OTEL_SDK_DISABLED", Some("true"), || {
let exporter = InMemoryLogExporter::default();
let logger_provider = SdkLoggerProvider::builder()
.with_simple_exporter(exporter.clone())
.build();
let logger = logger_provider.logger("noop");
let mut record = logger.create_log_record();
record.set_body("Testing sdk disabled logger".into());
logger.emit(record);
let mut record = logger.create_log_record();
record.set_body("Testing sdk disabled logger".into());
logger.emit(record);
let mut record = logger.create_log_record();
record.set_body("Testing sdk disabled logger".into());
logger.emit(record);
let emitted_logs = exporter.get_emitted_logs().unwrap();
assert_eq!(emitted_logs.len(), 0);

assert!(logger.provider().shutdown().is_ok());
assert!(logger.provider().shutdown().is_err());
});
}

#[test]
fn drop_after_shutdown_test_with_multiple_providers() {
let shutdown_called = Arc::new(Mutex::new(0)); // Count the number of times shutdown is called
Expand All @@ -789,6 +846,7 @@ mod tests {
flush_called.clone(),
))],
is_shutdown: AtomicBool::new(false),
is_disabled: false,
});

// Create a scope to test behavior when providers are dropped
Expand Down
32 changes: 31 additions & 1 deletion opentelemetry-sdk/src/metrics/meter_provider.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::fmt;
use opentelemetry::{
metrics::{Meter, MeterProvider},
otel_debug, otel_error, otel_info, InstrumentationScope,
otel_debug, otel_error, otel_info, otel_warn, InstrumentationScope,
};
use std::time::Duration;
use std::{
Expand Down Expand Up @@ -41,6 +41,7 @@ struct SdkMeterProviderInner {
pipes: Arc<Pipelines>,
meters: Mutex<HashMap<InstrumentationScope, Arc<SdkMeter>>>,
shutdown_invoked: AtomicBool,
is_disabled: bool,
}

impl Default for SdkMeterProvider {
Expand Down Expand Up @@ -196,6 +197,10 @@ impl MeterProvider for SdkMeterProvider {
);
return Meter::new(Arc::new(NoopMeter::new()));
}
if self.inner.is_disabled {
otel_warn!(name: "MeterProvider.NoOpMeterReturned", message = "Returned NoOpMeter. SDK is disabled");
return Meter::new(Arc::new(NoopMeter::new()));
}

if scope.name().is_empty() {
otel_info!(name: "MeterNameEmpty", message = "Meter name is empty; consider providing a meaningful name. Meter will function normally and the provided name will be used as-is.");
Expand Down Expand Up @@ -379,6 +384,13 @@ impl MeterProviderBuilder {
builder = format!("{:?}", &self),
);

let is_disabled =
std::env::var("OTEL_SDK_DISABLED").is_ok_and(|var| var.to_lowercase() == "true");

if is_disabled {
otel_warn!(name: "MeterProvider.Disabled", message = "SDK is disabled through environment variable");
}

let meter_provider = SdkMeterProvider {
inner: Arc::new(SdkMeterProviderInner {
pipes: Arc::new(Pipelines::new(
Expand All @@ -388,6 +400,7 @@ impl MeterProviderBuilder {
)),
meters: Default::default(),
shutdown_invoked: AtomicBool::new(false),
is_disabled,
}),
};

Expand Down Expand Up @@ -729,4 +742,21 @@ mod tests {
);
assert_eq!(resource.schema_url(), Some("http://example.com"));
}

#[test]
#[ignore = "modifies OTEL_SDK_DISABLED env var which can affect other test"]
fn otel_sdk_disabled_env() {
temp_env::with_var("OTEL_SDK_DISABLED", Some("true"), || {
let meter_provider = super::SdkMeterProvider::builder().build();
let _ = meter_provider.meter("noop1");
let _ = meter_provider.meter("noop2");
let _ = meter_provider.meter("noop3");
let _ = meter_provider.meter("noop4");
let _ = meter_provider.meter("noop5");

assert_eq!(meter_provider.inner.meters.lock().unwrap().len(), 0);
assert!(meter_provider.shutdown().is_ok());
assert!(meter_provider.shutdown().is_err());
});
}
}
Loading
Loading