1use ahash::AHashMap;
17use log::LevelFilter;
18use nautilus_core::{UUID4, python::to_pyvalue_err};
19use nautilus_model::identifiers::TraderId;
20use pyo3::prelude::*;
21use ustr::Ustr;
22
23use crate::{
24 enums::{LogColor, LogLevel},
25 logging::{
26 self, headers,
27 logger::{self, LogGuard, LoggerConfig},
28 logging_clock_set_realtime_mode, logging_clock_set_static_mode,
29 logging_clock_set_static_time, logging_set_bypass, map_log_level_to_filter,
30 parse_level_filter_str,
31 writer::FileWriterConfig,
32 },
33};
34
35#[pymethods]
36#[pyo3_stub_gen::derive::gen_stub_pymethods]
37impl LoggerConfig {
38 #[staticmethod]
51 #[pyo3(name = "from_spec")]
52 pub fn py_from_spec(spec: &str) -> PyResult<Self> {
53 Self::from_spec(spec).map_err(to_pyvalue_err)
54 }
55}
56
57#[pymethods]
58#[pyo3_stub_gen::derive::gen_stub_pymethods]
59impl FileWriterConfig {
60 #[new]
62 #[pyo3(signature = (directory=None, file_name=None, file_format=None, file_rotate=None))]
63 #[must_use]
64 pub fn py_new(
65 directory: Option<String>,
66 file_name: Option<String>,
67 file_format: Option<String>,
68 file_rotate: Option<(u64, u32)>,
69 ) -> Self {
70 Self::new(directory, file_name, file_format, file_rotate)
71 }
72}
73
74#[pyfunction]
84#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
85#[pyo3(name = "init_logging")]
86#[expect(clippy::too_many_arguments)]
87#[pyo3(signature = (trader_id, instance_id, level_stdout, level_file=None, component_levels=None, directory=None, file_name=None, file_format=None, file_rotate=None, is_colored=None, is_bypassed=None, print_config=None, log_components_only=None))]
88pub fn py_init_logging(
89 trader_id: TraderId,
90 instance_id: UUID4,
91 level_stdout: LogLevel,
92 level_file: Option<LogLevel>,
93 component_levels: Option<std::collections::HashMap<String, String>>,
94 directory: Option<String>,
95 file_name: Option<String>,
96 file_format: Option<String>,
97 file_rotate: Option<(u64, u32)>,
98 is_colored: Option<bool>,
99 is_bypassed: Option<bool>,
100 print_config: Option<bool>,
101 log_components_only: Option<bool>,
102) -> PyResult<LogGuard> {
103 let level_file = level_file.map_or(LevelFilter::Off, map_log_level_to_filter);
104
105 let component_levels = parse_component_levels(component_levels).map_err(to_pyvalue_err)?;
106
107 let file_config = FileWriterConfig::new(directory, file_name, file_format, file_rotate);
108
109 let config = LoggerConfig::new(
110 map_log_level_to_filter(level_stdout),
111 level_file,
112 component_levels,
113 AHashMap::new(), log_components_only.unwrap_or(false),
115 is_colored.unwrap_or(true),
116 print_config.unwrap_or(false),
117 false, is_bypassed.unwrap_or(false), None, false, );
122
123 if config.bypass_logging {
124 logging_set_bypass();
125 }
126
127 logging::init_logging(trader_id, instance_id, config, file_config).map_err(to_pyvalue_err)
128}
129
130#[pyfunction()]
131#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
132#[pyo3(name = "logger_flush")]
133pub fn py_logger_flush() {
134 log::logger().flush();
135}
136
137fn parse_component_levels(
138 original_map: Option<std::collections::HashMap<String, String>>,
139) -> anyhow::Result<AHashMap<Ustr, LevelFilter>> {
140 match original_map {
141 Some(map) => {
142 let mut new_map = AHashMap::new();
143
144 for (key, value) in map {
145 let ustr_key = Ustr::from(&key);
146 let level = parse_level_filter_str(&value)?;
147 new_map.insert(ustr_key, level);
148 }
149 Ok(new_map)
150 }
151 None => Ok(AHashMap::new()),
152 }
153}
154
155#[pyfunction]
157#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
158#[pyo3(name = "logger_log")]
159pub fn py_logger_log(level: LogLevel, color: LogColor, component: &str, message: &str) {
160 logger::log(level, color, Ustr::from(component), message);
161}
162
163#[pyfunction]
165#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
166#[pyo3(name = "log_header")]
167pub fn py_log_header(trader_id: TraderId, machine_id: &str, instance_id: UUID4, component: &str) {
168 headers::log_header(trader_id, machine_id, instance_id, Ustr::from(component));
169}
170
171#[pyfunction]
173#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
174#[pyo3(name = "log_sysinfo")]
175pub fn py_log_sysinfo(component: &str) {
176 headers::log_sysinfo(Ustr::from(component));
177}
178
179#[pyfunction]
181#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
182#[pyo3(name = "logging_clock_set_static_mode")]
183pub fn py_logging_clock_set_static_mode() {
184 logging_clock_set_static_mode();
185}
186
187#[pyfunction]
189#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
190#[pyo3(name = "logging_clock_set_realtime_mode")]
191pub fn py_logging_clock_set_realtime_mode() {
192 logging_clock_set_realtime_mode();
193}
194
195#[pyfunction]
197#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
198#[pyo3(name = "logging_clock_set_static_time")]
199pub fn py_logging_clock_set_static_time(time_ns: u64) {
200 logging_clock_set_static_time(time_ns);
201}
202
203#[cfg(feature = "tracing-bridge")]
205#[pyfunction]
206#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
207#[pyo3(name = "tracing_is_initialized")]
208#[must_use]
209pub fn py_tracing_is_initialized() -> bool {
210 crate::logging::bridge::tracing_is_initialized()
211}
212
213#[cfg(feature = "tracing-bridge")]
229#[pyfunction]
230#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.common")]
231#[pyo3(name = "init_tracing")]
232pub fn py_init_tracing() -> PyResult<()> {
233 crate::logging::bridge::init_tracing().map_err(to_pyvalue_err)
234}
235
236#[pyclass(
243 module = "nautilus_trader.core.nautilus_pyo3.common",
244 name = "Logger",
245 unsendable,
246 from_py_object
247)]
248#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")]
249#[derive(Debug, Clone)]
250pub struct PyLogger {
251 name: Ustr,
252}
253
254impl PyLogger {
255 pub fn new(name: &str) -> Self {
256 Self {
257 name: Ustr::from(name),
258 }
259 }
260}
261
262#[pymethods]
263#[pyo3_stub_gen::derive::gen_stub_pymethods]
264impl PyLogger {
265 #[new]
267 #[pyo3(signature = (name="Python"))]
268 fn py_new(name: &str) -> Self {
269 Self::new(name)
270 }
271
272 #[getter]
274 fn name(&self) -> &str {
275 &self.name
276 }
277
278 #[pyo3(name = "trace")]
280 fn py_trace(&self, message: &str, color: Option<LogColor>) {
281 self._log(LogLevel::Trace, color, message);
282 }
283
284 #[pyo3(name = "debug")]
286 fn py_debug(&self, message: &str, color: Option<LogColor>) {
287 self._log(LogLevel::Debug, color, message);
288 }
289
290 #[pyo3(name = "info")]
292 fn py_info(&self, message: &str, color: Option<LogColor>) {
293 self._log(LogLevel::Info, color, message);
294 }
295
296 #[pyo3(name = "warning")]
298 fn py_warning(&self, message: &str, color: Option<LogColor>) {
299 self._log(LogLevel::Warning, color, message);
300 }
301
302 #[pyo3(name = "error")]
304 fn py_error(&self, message: &str, color: Option<LogColor>) {
305 self._log(LogLevel::Error, color, message);
306 }
307
308 #[pyo3(name = "exception")]
310 #[pyo3(signature = (message="", color=None))]
311 fn py_exception(&self, py: Python, message: &str, color: Option<LogColor>) {
312 let mut full_msg = message.to_owned();
313
314 if pyo3::PyErr::occurred(py) {
315 let err = PyErr::fetch(py);
316 let err_str = err.to_string();
317
318 if full_msg.is_empty() {
319 full_msg = err_str;
320 } else {
321 full_msg = format!("{full_msg}: {err_str}");
322 }
323 }
324
325 self._log(LogLevel::Error, color, &full_msg);
326 }
327
328 #[pyo3(name = "flush")]
330 fn py_flush(&self) {
331 log::logger().flush();
332 }
333
334 fn _log(&self, level: LogLevel, color: Option<LogColor>, message: &str) {
335 let color = color.unwrap_or(LogColor::Normal);
336 logger::log(level, color, self.name, message);
337 }
338}