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}