Skip to main content

nautilus_common/logging/
bridge.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Tracing subscriber for capturing logs from external Rust libraries.
17//!
18//! This module initializes a standard tracing subscriber that outputs directly
19//! to stdout, allowing external Rust libraries that use the `tracing` crate
20//! to have their logs displayed.
21//!
22//! # Usage
23//!
24//! 1. Set `use_tracing=True` in `LoggingConfig`.
25//! 2. Set `RUST_LOG` environment variable to control filtering.
26//!
27//! # Example
28//!
29//! ```text
30//! RUST_LOG=hyper=debug,tokio=warn python my_script.py
31//! ```
32
33use std::sync::atomic::{AtomicBool, Ordering};
34
35use tracing::{Event, Level, Subscriber};
36use tracing_subscriber::{
37    EnvFilter,
38    fmt::{self, FmtContext, FormatEvent, FormatFields, format::Writer},
39    prelude::*,
40    registry::LookupSpan,
41};
42
43static TRACING_INITIALIZED: AtomicBool = AtomicBool::new(false);
44
45struct NautilusFormatter;
46
47impl<S, N> FormatEvent<S, N> for NautilusFormatter
48where
49    S: Subscriber + for<'a> LookupSpan<'a>,
50    N: for<'a> FormatFields<'a> + 'static,
51{
52    fn format_event(
53        &self,
54        ctx: &FmtContext<'_, S, N>,
55        mut writer: Writer<'_>,
56        event: &Event<'_>,
57    ) -> std::fmt::Result {
58        let now = chrono::Utc::now();
59        let timestamp = now.format("%Y-%m-%dT%H:%M:%S%.9fZ");
60
61        let level = match *event.metadata().level() {
62            Level::TRACE => "[TRACE]",
63            Level::DEBUG => "[DEBUG]",
64            Level::INFO => "[INFO]",
65            Level::WARN => "[WARN]",
66            Level::ERROR => "[ERROR]",
67        };
68
69        let target = event.metadata().target();
70
71        write!(writer, "{timestamp} {level} {target}: ")?;
72        ctx.field_format().format_fields(writer.by_ref(), event)?;
73        writeln!(writer)
74    }
75}
76
77/// Returns whether the tracing subscriber has been initialized.
78#[must_use]
79pub fn tracing_is_initialized() -> bool {
80    TRACING_INITIALIZED.load(Ordering::Relaxed)
81}
82
83/// Initializes a tracing subscriber for external Rust crate logging.
84///
85/// This sets up a standard tracing subscriber that outputs to stdout with
86/// the format controlled by `RUST_LOG` environment variable. The output
87/// format uses nanosecond timestamps to align with Nautilus logging.
88///
89/// # Environment Variables
90///
91/// - `RUST_LOG`: Controls which modules emit tracing events and at what level.
92///   - Example: `RUST_LOG=hyper=debug,tokio=warn`.
93///   - Default: `warn` (if not set).
94///
95/// # Errors
96///
97/// Returns an error if the tracing subscriber has already been initialized.
98pub fn init_tracing() -> anyhow::Result<()> {
99    if TRACING_INITIALIZED.load(Ordering::SeqCst) {
100        anyhow::bail!("Tracing subscriber already initialized");
101    }
102
103    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
104
105    tracing_subscriber::registry()
106        .with(filter)
107        .with(fmt::layer().event_format(NautilusFormatter))
108        .try_init()
109        .map_err(|e| anyhow::anyhow!("Failed to initialize tracing subscriber: {e}"))?;
110
111    TRACING_INITIALIZED.store(true, Ordering::SeqCst);
112    Ok(())
113}
114
115#[cfg(test)]
116mod tests {
117    use rstest::rstest;
118
119    use super::*;
120
121    #[rstest]
122    fn test_tracing_is_initialized_returns_bool() {
123        let _ = tracing_is_initialized();
124    }
125}