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]
36impl LoggerConfig {
37 #[staticmethod]
43 #[pyo3(name = "from_spec")]
44 pub fn py_from_spec(spec: String) -> PyResult<Self> {
45 Self::from_spec(&spec).map_err(to_pyvalue_err)
46 }
47}
48
49#[pymethods]
50impl FileWriterConfig {
51 #[new]
52 #[pyo3(signature = (directory=None, file_name=None, file_format=None, file_rotate=None))]
53 #[must_use]
54 pub fn py_new(
55 directory: Option<String>,
56 file_name: Option<String>,
57 file_format: Option<String>,
58 file_rotate: Option<(u64, u32)>,
59 ) -> Self {
60 Self::new(directory, file_name, file_format, file_rotate)
61 }
62}
63
64#[pyfunction]
78#[pyo3(name = "init_logging")]
79#[allow(clippy::too_many_arguments)]
80#[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))]
81pub fn py_init_logging(
82 trader_id: TraderId,
83 instance_id: UUID4,
84 level_stdout: LogLevel,
85 level_file: Option<LogLevel>,
86 component_levels: Option<std::collections::HashMap<String, String>>,
87 directory: Option<String>,
88 file_name: Option<String>,
89 file_format: Option<String>,
90 file_rotate: Option<(u64, u32)>,
91 is_colored: Option<bool>,
92 is_bypassed: Option<bool>,
93 print_config: Option<bool>,
94 log_components_only: Option<bool>,
95) -> PyResult<LogGuard> {
96 let level_file = level_file.map_or(LevelFilter::Off, map_log_level_to_filter);
97
98 let component_levels = parse_component_levels(component_levels).map_err(to_pyvalue_err)?;
99
100 let config = LoggerConfig::new(
101 map_log_level_to_filter(level_stdout),
102 level_file,
103 component_levels,
104 AHashMap::new(), log_components_only.unwrap_or(false),
106 is_colored.unwrap_or(true),
107 print_config.unwrap_or(false),
108 false, );
110
111 let file_config = FileWriterConfig::new(directory, file_name, file_format, file_rotate);
112
113 if is_bypassed.unwrap_or(false) {
114 logging_set_bypass();
115 }
116
117 logging::init_logging(trader_id, instance_id, config, file_config).map_err(to_pyvalue_err)
118}
119
120#[pyfunction()]
121#[pyo3(name = "logger_flush")]
122pub fn py_logger_flush() {
123 log::logger().flush();
124}
125
126fn parse_component_levels(
127 original_map: Option<std::collections::HashMap<String, String>>,
128) -> anyhow::Result<AHashMap<Ustr, LevelFilter>> {
129 match original_map {
130 Some(map) => {
131 let mut new_map = AHashMap::new();
132 for (key, value) in map {
133 let ustr_key = Ustr::from(&key);
134 let level = parse_level_filter_str(&value)?;
135 new_map.insert(ustr_key, level);
136 }
137 Ok(new_map)
138 }
139 None => Ok(AHashMap::new()),
140 }
141}
142
143#[pyfunction]
145#[pyo3(name = "logger_log")]
146pub fn py_logger_log(level: LogLevel, color: LogColor, component: &str, message: &str) {
147 logger::log(level, color, Ustr::from(component), message);
148}
149
150#[pyfunction]
152#[pyo3(name = "log_header")]
153pub fn py_log_header(trader_id: TraderId, machine_id: &str, instance_id: UUID4, component: &str) {
154 headers::log_header(trader_id, machine_id, instance_id, Ustr::from(component));
155}
156
157#[pyfunction]
159#[pyo3(name = "log_sysinfo")]
160pub fn py_log_sysinfo(component: &str) {
161 headers::log_sysinfo(Ustr::from(component));
162}
163
164#[pyfunction]
165#[pyo3(name = "logging_clock_set_static_mode")]
166pub fn py_logging_clock_set_static_mode() {
167 logging_clock_set_static_mode();
168}
169
170#[pyfunction]
171#[pyo3(name = "logging_clock_set_realtime_mode")]
172pub fn py_logging_clock_set_realtime_mode() {
173 logging_clock_set_realtime_mode();
174}
175
176#[pyfunction]
177#[pyo3(name = "logging_clock_set_static_time")]
178pub fn py_logging_clock_set_static_time(time_ns: u64) {
179 logging_clock_set_static_time(time_ns);
180}
181
182#[cfg(feature = "tracing-bridge")]
184#[pyfunction]
185#[pyo3(name = "tracing_is_initialized")]
186#[must_use]
187pub fn py_tracing_is_initialized() -> bool {
188 crate::logging::bridge::tracing_is_initialized()
189}
190
191#[cfg(feature = "tracing-bridge")]
205#[pyfunction]
206#[pyo3(name = "init_tracing")]
207pub fn py_init_tracing() -> PyResult<()> {
208 crate::logging::bridge::init_tracing().map_err(to_pyvalue_err)
209}
210
211#[pyclass(
218 module = "nautilus_trader.core.nautilus_pyo3.common",
219 name = "Logger",
220 unsendable,
221 from_py_object
222)]
223#[derive(Debug, Clone)]
224pub struct PyLogger {
225 name: Ustr,
226}
227
228impl PyLogger {
229 pub fn new(name: &str) -> Self {
230 Self {
231 name: Ustr::from(name),
232 }
233 }
234}
235
236#[pymethods]
237impl PyLogger {
238 #[new]
240 #[pyo3(signature = (name="Python"))]
241 fn py_new(name: &str) -> Self {
242 Self::new(name)
243 }
244
245 #[getter]
247 fn name(&self) -> &str {
248 &self.name
249 }
250
251 #[pyo3(name = "trace")]
253 fn py_trace(&self, message: &str, color: Option<LogColor>) {
254 self._log(LogLevel::Trace, color, message);
255 }
256
257 #[pyo3(name = "debug")]
259 fn py_debug(&self, message: &str, color: Option<LogColor>) {
260 self._log(LogLevel::Debug, color, message);
261 }
262
263 #[pyo3(name = "info")]
265 fn py_info(&self, message: &str, color: Option<LogColor>) {
266 self._log(LogLevel::Info, color, message);
267 }
268
269 #[pyo3(name = "warning")]
271 fn py_warning(&self, message: &str, color: Option<LogColor>) {
272 self._log(LogLevel::Warning, color, message);
273 }
274
275 #[pyo3(name = "error")]
277 fn py_error(&self, message: &str, color: Option<LogColor>) {
278 self._log(LogLevel::Error, color, message);
279 }
280
281 #[pyo3(name = "exception")]
283 #[pyo3(signature = (message="", color=None))]
284 fn py_exception(&self, py: Python, message: &str, color: Option<LogColor>) {
285 let mut full_msg = message.to_owned();
286
287 if pyo3::PyErr::occurred(py) {
288 let err = PyErr::fetch(py);
289 let err_str = err.to_string();
290
291 if full_msg.is_empty() {
292 full_msg = err_str;
293 } else {
294 full_msg = format!("{full_msg}: {err_str}");
295 }
296 }
297
298 self._log(LogLevel::Error, color, &full_msg);
299 }
300
301 #[pyo3(name = "flush")]
303 fn py_flush(&self) {
304 log::logger().flush();
305 }
306
307 fn _log(&self, level: LogLevel, color: Option<LogColor>, message: &str) {
308 let color = color.unwrap_or(LogColor::Normal);
309 logger::log(level, color, self.name, message);
310 }
311}