nautilus_common/logging/
headers.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.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
16use nautilus_core::UUID4;
17use nautilus_model::identifiers::TraderId;
18use sysinfo::System;
19use ustr::Ustr;
20
21use crate::{enums::LogColor, logging::log_info};
22
23#[rustfmt::skip]
24pub fn log_header(trader_id: TraderId, machine_id: &str, instance_id: UUID4, component: Ustr) {
25    let mut sys = System::new();
26    sys.refresh_cpu_all();
27    sys.refresh_memory();
28
29    let c = component;
30
31    let kernel_version = System::kernel_version().map_or(String::new(), |v| format!("kernel-{v} "));
32    let os_version = System::long_os_version().unwrap_or_default();
33    let pid = std::process::id();
34
35    header_sepr(c, "=================================================================");
36    header_sepr(c, " NAUTILUS TRADER - Automated Algorithmic Trading Platform");
37    header_sepr(c, " by Nautech Systems Pty Ltd.");
38    header_sepr(c, " Copyright (C) 2015-2026. All rights reserved.");
39    header_sepr(c, "=================================================================");
40    header_line(c, "");
41    header_line(c, "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣶⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀");
42    header_line(c, "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣾⣿⣿⣿⠀⢸⣿⣿⣿⣿⣶⣶⣤⣀⠀⠀⠀⠀⠀");
43    header_line(c, "⠀⠀⠀⠀⠀⠀⢀⣴⡇⢀⣾⣿⣿⣿⣿⣿⠀⣾⣿⣿⣿⣿⣿⣿⣿⠿⠓⠀⠀⠀⠀");
44    header_line(c, "⠀⠀⠀⠀⠀⣰⣿⣿⡀⢸⣿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿⣿⣿⠟⠁⣠⣄⠀⠀⠀⠀");
45    header_line(c, "⠀⠀⠀⠀⢠⣿⣿⣿⣇⠀⢿⣿⣿⣿⣿⣿⠀⢻⣿⣿⣿⡿⢃⣠⣾⣿⣿⣧⡀⠀⠀");
46    header_line(c, "⠀⠀⠀⠠⣾⣿⣿⣿⣿⣿⣧⠈⠋⢀⣴⣧⠀⣿⡏⢠⡀⢸⣿⣿⣿⣿⣿⣿⣿⡇⠀");
47    header_line(c, "⠀⠀⠀⣀⠙⢿⣿⣿⣿⣿⣿⠇⢠⣿⣿⣿⡄⠹⠃⠼⠃⠈⠉⠛⠛⠛⠛⠛⠻⠇⠀");
48    header_line(c, "⠀⠀⢸⡟⢠⣤⠉⠛⠿⢿⣿⠀⢸⣿⡿⠋⣠⣤⣄⠀⣾⣿⣿⣶⣶⣶⣦⡄⠀⠀⠀");
49    header_line(c, "⠀⠀⠸⠀⣾⠏⣸⣷⠂⣠⣤⠀⠘⢁⣴⣾⣿⣿⣿⡆⠘⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀");
50    header_line(c, "⠀⠀⠀⠀⠛⠀⣿⡟⠀⢻⣿⡄⠸⣿⣿⣿⣿⣿⣿⣿⡀⠘⣿⣿⣿⣿⠟⠀⠀⠀⠀");
51    header_line(c, "⠀⠀⠀⠀⠀⠀⣿⠇⠀⠀⢻⡿⠀⠈⠻⣿⣿⣿⣿⣿⡇⠀⢹⣿⠿⠋⠀⠀⠀⠀⠀");
52    header_line(c, "⠀⠀⠀⠀⠀⠀⠋⠀⠀⠀⡘⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀");
53    header_line(c, "");
54    header_sepr(c, "=================================================================");
55    header_sepr(c, " SYSTEM SPECIFICATION");
56    header_sepr(c, "=================================================================");
57    if let Some(cpu) = sys.cpus().first() {
58        header_line(c, &format!("CPU architecture: {}", cpu.brand()));
59        header_line(c, &format!("CPU(s): {} @ {} MHz", sys.cpus().len(), cpu.frequency()));
60    } else {
61        header_line(c, "CPU: unknown");
62    }
63    header_line(c, &format!("OS: {kernel_version}{os_version}"));
64
65    log_sysinfo(component);
66
67    header_sepr(c, "=================================================================");
68    header_sepr(c, " IDENTIFIERS");
69    header_sepr(c, "=================================================================");
70    header_line(c, &format!("trader_id: {trader_id}"));
71    header_line(c, &format!("machine_id: {machine_id}"));
72    header_line(c, &format!("instance_id: {instance_id}"));
73    header_line(c, &format!("PID: {pid}"));
74
75    header_sepr(c, "=================================================================");
76    header_sepr(c, " VERSIONING");
77    header_sepr(c, "=================================================================");
78
79    #[cfg(not(feature = "python"))]
80    log_rust_versioning(c);
81
82    #[cfg(feature = "python")]
83    log_python_versioning(c);
84}
85
86#[cfg(not(feature = "python"))]
87#[rustfmt::skip]
88fn log_rust_versioning(c: Ustr) {
89    use nautilus_core::consts::NAUTILUS_VERSION;
90    header_line(c, &format!("nautilus_trader: {NAUTILUS_VERSION}"));
91}
92
93#[cfg(feature = "python")]
94#[rustfmt::skip]
95fn log_python_versioning(c: Ustr) {
96    if !python_available() {
97        return;
98    }
99
100    let package = "nautilus_trader";
101    header_line(c, &format!("{package}: {}", python_package_version(package)));
102    header_line(c, &format!("python: {}", python_version()));
103
104    for package in ["numpy", "pandas", "msgspec", "pyarrow", "pytz", "uvloop"] {
105        header_line(c, &format!("{package}: {}", python_package_version(package)));
106    }
107
108    header_sepr(c, "=================================================================");
109}
110
111#[cfg(feature = "python")]
112#[inline]
113#[allow(unsafe_code)]
114fn python_available() -> bool {
115    // SAFETY: `Py_IsInitialized` reads a flag and is safe to call at any
116    // time, even before the interpreter has been started.
117    unsafe { pyo3::ffi::Py_IsInitialized() != 0 }
118}
119
120#[rustfmt::skip]
121pub fn log_sysinfo(component: Ustr) {
122    let mut sys = System::new();
123    sys.refresh_memory();
124
125    let c = component;
126
127    let ram_total = sys.total_memory();
128    let ram_used = sys.used_memory();
129    let ram_used_p = (ram_used as f64 / ram_total as f64) * 100.0;
130    let ram_avail = ram_total - ram_used;
131    let ram_avail_p = (ram_avail as f64 / ram_total as f64) * 100.0;
132
133    header_sepr(c, "=================================================================");
134    header_sepr(c, " MEMORY USAGE");
135    header_sepr(c, "=================================================================");
136    header_line(c, &format!("RAM-Total: {:.2} GiB", bytes_to_gib(ram_total)));
137    header_line(c, &format!("RAM-Used: {:.2} GiB ({:.2}%)", bytes_to_gib(ram_used), ram_used_p));
138    header_line(c, &format!("RAM-Avail: {:.2} GiB ({:.2}%)", bytes_to_gib(ram_avail), ram_avail_p));
139
140    let swap_total = sys.total_swap();
141    if swap_total > 0 {
142        let swap_used = sys.used_swap();
143        let swap_used_p = (swap_used as f64 / swap_total as f64) * 100.0;
144        let swap_avail = swap_total.saturating_sub(swap_used);
145        let swap_avail_p = (swap_avail as f64 / swap_total as f64) * 100.0;
146        header_line(c, &format!("Swap-Total: {:.2} GiB", bytes_to_gib(swap_total)));
147        header_line(c, &format!("Swap-Used: {:.2} GiB ({:.2}%)", bytes_to_gib(swap_used), swap_used_p));
148        header_line(c, &format!("Swap-Avail: {:.2} GiB ({:.2}%)", bytes_to_gib(swap_avail), swap_avail_p));
149    } else {
150        header_line(c, "Swap: disabled");
151    }
152}
153
154fn header_sepr(c: Ustr, s: &str) {
155    log_info!("{}", s, color = LogColor::Cyan, component = c.as_str());
156}
157
158fn header_line(c: Ustr, s: &str) {
159    log_info!("{}", s, component = c.as_str());
160}
161
162fn bytes_to_gib(b: u64) -> f64 {
163    b as f64 / (2u64.pow(30) as f64)
164}
165
166#[cfg(feature = "python")]
167fn python_package_version(package: &str) -> String {
168    nautilus_core::python::version::get_python_package_version(package)
169}
170
171#[cfg(feature = "python")]
172fn python_version() -> String {
173    nautilus_core::python::version::get_python_version()
174}