use std::sync::atomic::{AtomicBool, Ordering};
#[allow(
dead_code,
reason = "only dead if no foundations telemetry features are enabled"
)]
static TELEMETRY_INITIALIZED: AtomicBool = AtomicBool::new(false);
#[allow(
dead_code,
reason = "only dead if no foundations telemetry features are enabled"
)]
pub(crate) fn is_initialized() -> bool {
TELEMETRY_INITIALIZED.load(Ordering::Relaxed)
}
#[cfg(any(feature = "logging", feature = "tracing"))]
mod scope;
mod telemetry_context;
#[cfg(all(feature = "tracing", feature = "telemetry-otlp-grpc"))]
mod otlp_conversion;
#[cfg(feature = "testing")]
mod testing;
#[cfg(feature = "logging")]
pub mod log;
#[cfg(feature = "metrics")]
pub mod metrics;
#[cfg(feature = "tracing")]
pub mod tracing;
#[cfg(all(target_os = "linux", feature = "memory-profiling"))]
mod memory_profiler;
pub mod settings;
#[cfg(all(
feature = "tokio-runtime-metrics",
tokio_unstable,
foundations_unstable
))]
#[cfg_attr(
foundations_docsrs,
doc(cfg(all(
feature = "tokio-runtime-metrics",
tokio_unstable,
foundations_unstable
)))
)]
pub mod tokio_runtime_metrics;
#[cfg(feature = "telemetry-server")]
mod server;
feature_use!(
cfg(any(
feature = "logging",
feature = "tracing",
feature = "metrics",
feature = "telemetry-server",
)),
{
mod driver;
pub use self::driver::TelemetryDriver;
use crate::BootstrapResult;
use futures_util::stream::FuturesUnordered;
}
);
use self::settings::TelemetrySettings;
use crate::ServiceInfo;
use crate::utils::feature_use;
feature_use!(cfg(feature = "tracing"), {
use self::tracing::SpanScope;
feature_use!(cfg(feature = "testing"), {
use self::tracing::testing::TestTracerScope;
});
});
#[cfg(feature = "logging")]
use self::log::internal::LogScope;
#[cfg(feature = "testing")]
pub use self::testing::TestTelemetryContext;
#[cfg(all(target_os = "linux", feature = "memory-profiling"))]
pub use self::memory_profiler::MemoryProfiler;
#[cfg(feature = "telemetry-server")]
pub use self::server::{
TelemetryRouteBody, TelemetryRouteHandler, TelemetryRouteHandlerFuture, TelemetryServerRoute,
};
#[cfg(feature = "telemetry-server")]
pub mod reexports {
pub use http_body_util;
pub use hyper;
}
pub use self::telemetry_context::{
TelemetryContext, WithTelemetryContext, WithTelemetryContextLocal,
};
#[cfg(feature = "testing")]
pub use foundations_macros::with_test_telemetry;
#[must_use = "Telemetry context is not applied when scope is dropped."]
pub struct TelemetryScope {
#[cfg(feature = "logging")]
_log_scope: LogScope,
#[cfg(feature = "tracing")]
_span_scope: Option<SpanScope>,
#[cfg(all(feature = "tracing", feature = "testing"))]
_test_tracer_scope: Option<TestTracerScope>,
}
pub struct TelemetryConfig<'c> {
pub service_info: &'c ServiceInfo,
pub settings: &'c TelemetrySettings,
#[cfg(feature = "telemetry-server")]
pub custom_server_routes: Vec<TelemetryServerRoute>,
}
#[cfg(any(
feature = "logging",
feature = "tracing",
feature = "metrics",
feature = "telemetry-server",
))]
#[track_caller]
pub fn init(config: TelemetryConfig) -> BootstrapResult<TelemetryDriver> {
if is_initialized() {
let loc = std::panic::Location::caller();
anyhow::bail!(
"foundations::telemetry::init() should only be called once. Called at {}:{}:{}",
loc.file(),
loc.line(),
loc.column()
)
}
#[cfg(feature = "metrics")]
self::metrics::init::init(config.service_info, &config.settings.metrics);
#[cfg(feature = "metrics")]
crate::panic::install_hook();
let tele_futures: FuturesUnordered<_> = Default::default();
#[cfg(feature = "logging")]
let logging_guard = self::log::init::init(config.service_info, &config.settings.logging)?;
#[cfg(feature = "tracing")]
{
if let Some(fut) = self::tracing::init::init(config.service_info, &config.settings.tracing)?
{
tele_futures.push(fut);
}
}
TELEMETRY_INITIALIZED.store(true, Ordering::Relaxed);
#[cfg(feature = "telemetry-server")]
{
let server_fut = server::TelemetryServerFuture::new(
config.settings.clone(),
config.custom_server_routes,
)?;
#[allow(unused_mut)]
let mut telemetry_driver = TelemetryDriver::new(server_fut, tele_futures);
#[cfg(feature = "logging")]
telemetry_driver.set_logging_guard(logging_guard);
Ok(telemetry_driver)
}
#[cfg(not(feature = "telemetry-server"))]
{
#[allow(unused_mut)]
let mut telemetry_driver = TelemetryDriver::new(tele_futures);
#[cfg(feature = "logging")]
telemetry_driver.set_logging_guard(logging_guard);
Ok(telemetry_driver)
}
}
#[cfg(test)]
mod tests {
use super::{TelemetryConfig, TelemetryContext, init, is_initialized};
use crate::service_info;
#[tokio::test]
async fn double_init_call_errors() {
let first_res = init(TelemetryConfig {
service_info: &service_info!(),
settings: &Default::default(),
custom_server_routes: Default::default(),
});
assert!(first_res.is_ok());
assert!(is_initialized());
let second_res = init(TelemetryConfig {
service_info: &service_info!(),
settings: &Default::default(),
custom_server_routes: Default::default(),
});
assert!(second_res.is_err());
assert!(is_initialized());
}
#[test]
fn testing_telemetry_metrics_uninit() {
let _ctx = TelemetryContext::test();
assert!(
!super::metrics::internal::Registries::is_initialized(),
"metrics::Registries should not be initialized in `TelemetryContext::test`",
);
}
}