nautilus_common/logging/
mod.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.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//! The logging framework for Posei systems.
17
18pub mod headers;
19pub mod logger;
20pub mod macros;
21pub mod writer;
22
23use std::{
24    collections::HashMap,
25    env,
26    str::FromStr,
27    sync::atomic::{AtomicBool, Ordering},
28};
29
30use log::LevelFilter;
31// Re-exports
32pub use macros::{log_debug, log_error, log_info, log_trace, log_warn};
33use nautilus_core::{UUID4, time::get_atomic_clock_static};
34use nautilus_model::identifiers::TraderId;
35use tracing_subscriber::EnvFilter;
36use ustr::Ustr;
37
38use self::{
39    logger::{LogGuard, Logger, LoggerConfig},
40    writer::FileWriterConfig,
41};
42use crate::enums::LogLevel;
43
44pub const RECV: &str = "<--";
45pub const SEND: &str = "-->";
46pub const CMD: &str = "[CMD]";
47pub const EVT: &str = "[EVT]";
48pub const DOC: &str = "[DOC]";
49pub const RPT: &str = "[RPT]";
50pub const REQ: &str = "[REQ]";
51pub const RES: &str = "[RES]";
52
53static LOGGING_INITIALIZED: AtomicBool = AtomicBool::new(false);
54static LOGGING_BYPASSED: AtomicBool = AtomicBool::new(false);
55static LOGGING_REALTIME: AtomicBool = AtomicBool::new(true);
56static LOGGING_COLORED: AtomicBool = AtomicBool::new(true);
57
58/// Returns whether the core logger is enabled.
59pub fn logging_is_initialized() -> bool {
60    LOGGING_INITIALIZED.load(Ordering::Relaxed)
61}
62
63/// Sets the logging subsystem to bypass mode.
64pub fn logging_set_bypass() {
65    LOGGING_BYPASSED.store(true, Ordering::Relaxed);
66}
67
68/// Shuts down the logging subsystem.
69pub fn logging_shutdown() {
70    // Flush any buffered logs and mark logging as uninitialized
71    log::logger().flush();
72    LOGGING_INITIALIZED.store(false, Ordering::Relaxed);
73}
74
75/// Returns whether the core logger is using ANSI colors.
76pub fn logging_is_colored() -> bool {
77    LOGGING_COLORED.load(Ordering::Relaxed)
78}
79
80/// Sets the global logging clock to real-time mode.
81pub fn logging_clock_set_realtime_mode() {
82    LOGGING_REALTIME.store(true, Ordering::Relaxed);
83}
84
85/// Sets the global logging clock to static mode.
86pub fn logging_clock_set_static_mode() {
87    LOGGING_REALTIME.store(false, Ordering::Relaxed);
88}
89
90/// Sets the global logging clock static time with the given UNIX timestamp (nanoseconds).
91pub fn logging_clock_set_static_time(time_ns: u64) {
92    let clock = get_atomic_clock_static();
93    clock.set_time(time_ns.into());
94}
95
96/// Initialize tracing.
97///
98/// Tracing is meant to be used to trace/debug async Rust code. It can be
99/// configured to filter modules and write up to a specific level by passing
100/// a configuration using the `RUST_LOG` environment variable.
101///
102/// # Safety
103///
104/// Should only be called once during an applications run, ideally at the
105/// beginning of the run.
106///
107/// # Errors
108///
109/// Returns an error if tracing subscriber fails to initialize.
110pub fn init_tracing() -> anyhow::Result<()> {
111    // Skip tracing initialization if `RUST_LOG` is not set
112    if let Ok(v) = env::var("RUST_LOG") {
113        let env_filter = EnvFilter::new(v.clone());
114
115        tracing_subscriber::fmt()
116            .with_env_filter(env_filter)
117            .try_init()
118            .map_err(|e| anyhow::anyhow!("Failed to initialize tracing subscriber: {e}"))?;
119
120        println!("Initialized tracing logs with RUST_LOG={v}");
121    }
122    Ok(())
123}
124
125/// Initialize logging.
126///
127/// Logging should be used for Python and sync Rust logic which is most of
128/// the components in the [posei_trader](https://pypi.org/project/posei_trader) package.
129/// Logging can be configured to filter components and write up to a specific level only
130/// by passing a configuration using the `NAUTILUS_LOG` environment variable.
131///
132/// # Safety
133///
134/// Should only be called once during an applications run, ideally at the
135/// beginning of the run.
136/// Initialize logging.
137///
138/// Logging should be used for Python and sync Rust logic which is most of
139/// the components in the `posei_trader` package.
140/// Logging can be configured via the `NAUTILUS_LOG` environment variable.
141///
142/// # Errors
143///
144/// Returns an error if the logging subsystem fails to initialize.
145pub fn init_logging(
146    trader_id: TraderId,
147    instance_id: UUID4,
148    config: LoggerConfig,
149    file_config: FileWriterConfig,
150) -> anyhow::Result<LogGuard> {
151    LOGGING_INITIALIZED.store(true, Ordering::Relaxed);
152    LOGGING_COLORED.store(config.is_colored, Ordering::Relaxed);
153    Logger::init_with_config(trader_id, instance_id, config, file_config)
154}
155
156#[must_use]
157pub const fn map_log_level_to_filter(log_level: LogLevel) -> LevelFilter {
158    match log_level {
159        LogLevel::Off => LevelFilter::Off,
160        LogLevel::Trace => LevelFilter::Trace,
161        LogLevel::Debug => LevelFilter::Debug,
162        LogLevel::Info => LevelFilter::Info,
163        LogLevel::Warning => LevelFilter::Warn,
164        LogLevel::Error => LevelFilter::Error,
165    }
166}
167
168/// Parses a string into a [`LevelFilter`].
169///
170/// # Panics
171///
172/// Panics if the provided string is not a valid `LevelFilter`.
173#[must_use]
174pub fn parse_level_filter_str(s: &str) -> LevelFilter {
175    let mut log_level_str = s.to_string().to_uppercase();
176    if log_level_str == "WARNING" {
177        log_level_str = "WARN".to_string();
178    }
179    LevelFilter::from_str(&log_level_str)
180        .unwrap_or_else(|_| panic!("Invalid `LevelFilter` string, was {log_level_str}"))
181}
182
183#[must_use]
184/// Parses component-specific log levels from a JSON value map.
185///
186/// # Panics
187///
188/// Panics if a JSON value in the map is not a string representing a log level.
189pub fn parse_component_levels(
190    original_map: Option<HashMap<String, serde_json::Value>>,
191) -> HashMap<Ustr, LevelFilter> {
192    match original_map {
193        Some(map) => {
194            let mut new_map = HashMap::new();
195            for (key, value) in map {
196                let ustr_key = Ustr::from(&key);
197                // Expect the JSON value to be a string representing a log level
198                let s = value
199                    .as_str()
200                    .expect("Invalid component log level: expected string");
201                let lvl = parse_level_filter_str(s);
202                new_map.insert(ustr_key, lvl);
203            }
204            new_map
205        }
206        None => HashMap::new(),
207    }
208}
209
210/// Logs that a task has started using `tracing::debug!`.
211pub fn log_task_started(task_name: &str) {
212    tracing::debug!("Started task '{task_name}'");
213}
214
215/// Logs that a task has stopped using `tracing::debug!`.
216pub fn log_task_stopped(task_name: &str) {
217    tracing::debug!("Stopped task '{task_name}'");
218}
219
220/// Logs that a task is being awaited using `tracing::debug!`.
221pub fn log_task_awaiting(task_name: &str) {
222    tracing::debug!("Awaiting task '{task_name}'");
223}
224
225/// Logs that a task was aborted using `tracing::debug!`.
226pub fn log_task_aborted(task_name: &str) {
227    tracing::debug!("Aborted task '{task_name}'");
228}
229
230/// Logs that there was an error in a task `tracing::error!`.
231pub fn log_task_error(task_name: &str, e: &anyhow::Error) {
232    tracing::error!("Error in task '{task_name}': {e}");
233}