Skip to main content

mx_logging/
lib.rs

1//! Logging and tracing utilities for `MultiversX` services.
2//!
3//! This crate provides a unified logging interface with optional OpenTelemetry
4//! distributed tracing support.
5//!
6//! # Basic Usage
7//!
8//! ```rust,no_run
9//! use mx_logging::init;
10//!
11//! // Initialize with default filter
12//! init(None, "info");
13//! ```
14//!
15//! # OpenTelemetry Integration
16//!
17//! When the `opentelemetry` feature is enabled, you can configure distributed tracing:
18//!
19//! ```rust,ignore
20//! use mx_logging::{OtelConfig, init_with_otel};
21//!
22//! let config = OtelConfig {
23//!     service_name: "mx-relayer".to_string(),
24//!     otlp_endpoint: "http://localhost:4317".to_string(),
25//!     ..Default::default()
26//! };
27//!
28//! init_with_otel(&config, None, "info").expect("Failed to initialize tracing");
29//! ```
30
31use std::sync::OnceLock;
32
33use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
34
35#[cfg(feature = "opentelemetry")]
36mod otel;
37
38#[cfg(feature = "opentelemetry")]
39pub use otel::{OtelConfig, OtelError, init_with_otel, shutdown_otel};
40
41#[cfg(test)]
42mod tests;
43
44static TRACING: OnceLock<()> = OnceLock::new();
45
46/// Initialize a compact, human-friendly tracing subscriber.
47///
48/// Priority for the filter is:
49/// 1. Explicit `filter` argument (if provided)
50/// 2. `RUST_LOG` / default env filter
51/// 3. `default_filter`
52///
53/// Also enables the log-to-tracing bridge so `log` crate messages (e.g., from yamux)
54/// are filtered by the same rules.
55pub fn init(filter: Option<&str>, default_filter: &str) {
56    TRACING.get_or_init(|| {
57        let env_filter = build_filter(filter, default_filter);
58        let fmt_layer = build_fmt_layer();
59
60        // Initialize with the log-to-tracing bridge enabled
61        tracing_subscriber::registry()
62            .with(env_filter)
63            .with(fmt_layer)
64            .init();
65    });
66}
67
68/// Build an `EnvFilter` based on priority: explicit filter > `RUST_LOG` env > `default_filter`.
69///
70/// This function is public for testing purposes.
71pub(crate) fn build_filter(filter: Option<&str>, default_filter: &str) -> EnvFilter {
72    if let Some(explicit) = filter {
73        EnvFilter::try_new(explicit).unwrap_or_else(|_| EnvFilter::new(default_filter))
74    } else if let Ok(env_filter) = EnvFilter::try_from_default_env() {
75        env_filter
76    } else {
77        EnvFilter::new(default_filter)
78    }
79}
80
81/// Build a compact fmt layer for console output.
82///
83/// This function is extracted for testability.
84pub(crate) fn build_fmt_layer<S>() -> impl tracing_subscriber::Layer<S>
85where
86    S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
87{
88    fmt::layer()
89        .compact()
90        .with_level(true)
91        .with_target(false)
92        .with_file(false)
93        .with_line_number(false)
94}
95
96/// Check if tracing has been initialized.
97///
98/// This is useful for applications that need to conditionally initialize logging.
99pub fn is_initialized() -> bool {
100    TRACING.get().is_some()
101}