1use std::collections::HashMap;
17
18use log::LevelFilter;
19use nautilus_core::{UUID4, python::to_pyvalue_err};
20use nautilus_model::identifiers::TraderId;
21use pyo3::prelude::*;
22use ustr::Ustr;
23
24use crate::{
25 enums::{LogColor, LogLevel},
26 logging::{
27 self, headers,
28 logger::{self, LogGuard, LoggerConfig},
29 logging_clock_set_realtime_mode, logging_clock_set_static_mode,
30 logging_clock_set_static_time, logging_set_bypass, map_log_level_to_filter,
31 parse_level_filter_str,
32 writer::FileWriterConfig,
33 },
34};
35
36#[pymethods]
37impl LoggerConfig {
38 #[staticmethod]
44 #[pyo3(name = "from_spec")]
45 pub fn py_from_spec(spec: String) -> PyResult<Self> {
46 Self::from_spec(&spec).map_err(to_pyvalue_err)
47 }
48}
49
50#[pymethods]
51impl FileWriterConfig {
52 #[new]
53 #[pyo3(signature = (directory=None, file_name=None, file_format=None, file_rotate=None))]
54 #[must_use]
55 pub fn py_new(
56 directory: Option<String>,
57 file_name: Option<String>,
58 file_format: Option<String>,
59 file_rotate: Option<(u64, u32)>,
60 ) -> Self {
61 Self::new(directory, file_name, file_format, file_rotate)
62 }
63}
64
65#[pyfunction()]
80#[pyo3(name = "init_tracing")]
81pub fn py_init_tracing() -> PyResult<()> {
82 logging::init_tracing().map_err(to_pyvalue_err)
83}
84
85#[pyfunction]
102#[pyo3(name = "init_logging")]
103#[allow(clippy::too_many_arguments)]
104#[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))]
105pub fn py_init_logging(
106 trader_id: TraderId,
107 instance_id: UUID4,
108 level_stdout: LogLevel,
109 level_file: Option<LogLevel>,
110 component_levels: Option<HashMap<String, String>>,
111 directory: Option<String>,
112 file_name: Option<String>,
113 file_format: Option<String>,
114 file_rotate: Option<(u64, u32)>,
115 is_colored: Option<bool>,
116 is_bypassed: Option<bool>,
117 print_config: Option<bool>,
118 log_components_only: Option<bool>,
119) -> PyResult<LogGuard> {
120 let level_file = level_file.map_or(LevelFilter::Off, map_log_level_to_filter);
121
122 let component_levels = parse_component_levels(component_levels).map_err(to_pyvalue_err)?;
123
124 let config = LoggerConfig::new(
125 map_log_level_to_filter(level_stdout),
126 level_file,
127 component_levels,
128 log_components_only.unwrap_or(false),
129 is_colored.unwrap_or(true),
130 print_config.unwrap_or(false),
131 );
132
133 let file_config = FileWriterConfig::new(directory, file_name, file_format, file_rotate);
134
135 if is_bypassed.unwrap_or(false) {
136 logging_set_bypass();
137 }
138
139 logging::init_logging(trader_id, instance_id, config, file_config).map_err(to_pyvalue_err)
140}
141
142#[pyfunction()]
143#[pyo3(name = "logger_flush")]
144pub fn py_logger_flush() {
145 log::logger().flush();
146}
147
148fn parse_component_levels(
149 original_map: Option<HashMap<String, String>>,
150) -> anyhow::Result<HashMap<Ustr, LevelFilter>> {
151 match original_map {
152 Some(map) => {
153 let mut new_map = HashMap::new();
154 for (key, value) in map {
155 let ustr_key = Ustr::from(&key);
156 let level = parse_level_filter_str(&value)?;
157 new_map.insert(ustr_key, level);
158 }
159 Ok(new_map)
160 }
161 None => Ok(HashMap::new()),
162 }
163}
164
165#[pyfunction]
167#[pyo3(name = "logger_log")]
168pub fn py_logger_log(level: LogLevel, color: LogColor, component: &str, message: &str) {
169 logger::log(level, color, Ustr::from(component), message);
170}
171
172#[pyfunction]
174#[pyo3(name = "log_header")]
175pub fn py_log_header(trader_id: TraderId, machine_id: &str, instance_id: UUID4, component: &str) {
176 headers::log_header(trader_id, machine_id, instance_id, Ustr::from(component));
177}
178
179#[pyfunction]
181#[pyo3(name = "log_sysinfo")]
182pub fn py_log_sysinfo(component: &str) {
183 headers::log_sysinfo(Ustr::from(component));
184}
185
186#[pyfunction]
187#[pyo3(name = "logging_clock_set_static_mode")]
188pub fn py_logging_clock_set_static_mode() {
189 logging_clock_set_static_mode();
190}
191
192#[pyfunction]
193#[pyo3(name = "logging_clock_set_realtime_mode")]
194pub fn py_logging_clock_set_realtime_mode() {
195 logging_clock_set_realtime_mode();
196}
197
198#[pyfunction]
199#[pyo3(name = "logging_clock_set_static_time")]
200pub fn py_logging_clock_set_static_time(time_ns: u64) {
201 logging_clock_set_static_time(time_ns);
202}
203
204#[pyclass(
211 module = "nautilus_trader.core.nautilus_pyo3.common",
212 name = "Logger",
213 unsendable
214)]
215#[derive(Debug, Clone)]
216pub struct PyLogger {
217 name: Ustr,
218}
219
220impl PyLogger {
221 pub fn new(name: &str) -> Self {
222 Self {
223 name: Ustr::from(name),
224 }
225 }
226}
227
228#[pymethods]
229impl PyLogger {
230 #[new]
232 #[pyo3(signature = (name="Python"))]
233 fn py_new(name: &str) -> Self {
234 Self::new(name)
235 }
236
237 #[getter]
239 fn name(&self) -> &str {
240 &self.name
241 }
242
243 #[pyo3(name = "trace")]
245 fn py_trace(&self, message: &str, color: Option<LogColor>) {
246 self._log(LogLevel::Trace, color, message);
247 }
248
249 #[pyo3(name = "debug")]
251 fn py_debug(&self, message: &str, color: Option<LogColor>) {
252 self._log(LogLevel::Debug, color, message);
253 }
254
255 #[pyo3(name = "info")]
257 fn py_info(&self, message: &str, color: Option<LogColor>) {
258 self._log(LogLevel::Info, color, message);
259 }
260
261 #[pyo3(name = "warning")]
263 fn py_warning(&self, message: &str, color: Option<LogColor>) {
264 self._log(LogLevel::Warning, color, message);
265 }
266
267 #[pyo3(name = "error")]
269 fn py_error(&self, message: &str, color: Option<LogColor>) {
270 self._log(LogLevel::Error, color, message);
271 }
272
273 #[pyo3(name = "exception")]
275 #[pyo3(signature = (message="", color=None))]
276 fn py_exception(&self, py: Python, message: &str, color: Option<LogColor>) {
277 let mut full_msg = message.to_owned();
278
279 if pyo3::PyErr::occurred(py) {
280 let err = PyErr::fetch(py);
281 let err_str = err.to_string();
282 if full_msg.is_empty() {
283 full_msg = err_str;
284 } else {
285 full_msg = format!("{full_msg}: {err_str}");
286 }
287 }
288
289 self._log(LogLevel::Error, color, &full_msg);
290 }
291
292 #[pyo3(name = "flush")]
294 fn py_flush(&self) {
295 log::logger().flush();
296 }
297
298 fn _log(&self, level: LogLevel, color: Option<LogColor>, message: &str) {
299 let color = color.unwrap_or(LogColor::Normal);
300 logger::log(level, color, self.name, message);
301 }
302}