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