nautilus_common/ffi/
logging.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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 std::{
17    ffi::c_char,
18    ops::{Deref, DerefMut},
19};
20
21use nautilus_core::{
22    UUID4,
23    ffi::{
24        parsing::{optional_bytes_to_json, u8_as_bool},
25        string::{cstr_as_str, cstr_to_ustr, optional_cstr_to_str},
26    },
27};
28use nautilus_model::identifiers::TraderId;
29
30use crate::{
31    enums::{LogColor, LogLevel},
32    logging::{
33        headers, init_logging,
34        logger::{self, LogGuard, LoggerConfig},
35        map_log_level_to_filter, parse_component_levels,
36        writer::FileWriterConfig,
37    },
38};
39
40/// C compatible Foreign Function Interface (FFI) for an underlying [`LogGuard`].
41///
42/// This struct wraps `LogGuard` in a way that makes it compatible with C function
43/// calls, enabling interaction with `LogGuard` in a C environment.
44///
45/// It implements the `Deref` trait, allowing instances of `LogGuard_API` to be
46/// dereferenced to `LogGuard`, providing access to `LogGuard`'s methods without
47/// having to manually access the underlying `LogGuard` instance.
48#[repr(C)]
49#[allow(non_camel_case_types)]
50#[derive(Debug)]
51pub struct LogGuard_API(Box<LogGuard>);
52
53impl Deref for LogGuard_API {
54    type Target = LogGuard;
55
56    fn deref(&self) -> &Self::Target {
57        &self.0
58    }
59}
60
61impl DerefMut for LogGuard_API {
62    fn deref_mut(&mut self) -> &mut Self::Target {
63        &mut self.0
64    }
65}
66
67/// Initializes logging.
68///
69/// Logging should be used for Python and sync Rust logic which is most of
70/// the components in the [nautilus_trader](https://pypi.org/project/nautilus_trader) package.
71/// Logging can be configured to filter components and write up to a specific level only
72/// by passing a configuration using the `NAUTILUS_LOG` environment variable.
73///
74/// # Safety
75///
76/// Should only be called once during an application's run, ideally at the
77/// beginning of the run.
78///
79/// This function assumes:
80/// - `directory_ptr` is either NULL or a valid C string pointer.
81/// - `file_name_ptr` is either NULL or a valid C string pointer.
82/// - `file_format_ptr` is either NULL or a valid C string pointer.
83/// - `component_level_ptr` is either NULL or a valid C string pointer.
84///
85/// # Panics
86///
87/// Panics if initializing the Rust logger fails.
88#[unsafe(no_mangle)]
89pub unsafe extern "C" fn logging_init(
90    trader_id: TraderId,
91    instance_id: UUID4,
92    level_stdout: LogLevel,
93    level_file: LogLevel,
94    directory_ptr: *const c_char,
95    file_name_ptr: *const c_char,
96    file_format_ptr: *const c_char,
97    component_levels_ptr: *const c_char,
98    is_colored: u8,
99    is_bypassed: u8,
100    print_config: u8,
101    log_components_only: u8,
102    max_file_size: u64,
103    max_backup_count: u32,
104) -> LogGuard_API {
105    let level_stdout = map_log_level_to_filter(level_stdout);
106    let level_file = map_log_level_to_filter(level_file);
107
108    let component_levels_json = unsafe { optional_bytes_to_json(component_levels_ptr) };
109    let component_levels = parse_component_levels(component_levels_json)
110        .expect("Failed to parse component log levels");
111
112    let config = LoggerConfig::new(
113        level_stdout,
114        level_file,
115        component_levels,
116        u8_as_bool(log_components_only),
117        u8_as_bool(is_colored),
118        u8_as_bool(print_config),
119    );
120
121    // Configure file rotation if max_file_size > 0
122    let file_rotate = if max_file_size > 0 {
123        Some((max_file_size, max_backup_count))
124    } else {
125        None
126    };
127
128    let directory = unsafe { optional_cstr_to_str(directory_ptr).map(ToString::to_string) };
129    let file_name = unsafe { optional_cstr_to_str(file_name_ptr).map(ToString::to_string) };
130    let file_format = unsafe { optional_cstr_to_str(file_format_ptr).map(ToString::to_string) };
131
132    let file_config = FileWriterConfig::new(directory, file_name, file_format, file_rotate);
133
134    if u8_as_bool(is_bypassed) {
135        logging_set_bypass();
136    }
137
138    LogGuard_API(Box::new(
139        init_logging(trader_id, instance_id, config, file_config)
140            .expect("Failed to initialize logging"),
141    ))
142}
143
144/// Creates a new log event.
145///
146/// # Safety
147///
148/// This function assumes:
149/// - `component_ptr` is a valid C string pointer.
150/// - `message_ptr` is a valid C string pointer.
151#[unsafe(no_mangle)]
152pub unsafe extern "C" fn logger_log(
153    level: LogLevel,
154    color: LogColor,
155    component_ptr: *const c_char,
156    message_ptr: *const c_char,
157) {
158    let component = unsafe { cstr_to_ustr(component_ptr) };
159    let message = unsafe { cstr_as_str(message_ptr) };
160
161    logger::log(level, color, component, message);
162}
163
164/// Logs the Nautilus system header.
165///
166/// # Safety
167///
168/// This function assumes:
169/// - `machine_id_ptr` is a valid C string pointer.
170/// - `component_ptr` is a valid C string pointer.
171#[unsafe(no_mangle)]
172pub unsafe extern "C" fn logging_log_header(
173    trader_id: TraderId,
174    machine_id_ptr: *const c_char,
175    instance_id: UUID4,
176    component_ptr: *const c_char,
177) {
178    let component = unsafe { cstr_to_ustr(component_ptr) };
179    let machine_id = unsafe { cstr_as_str(machine_id_ptr) };
180    headers::log_header(trader_id, machine_id, instance_id, component);
181}
182
183/// Logs system information.
184///
185/// # Safety
186///
187/// Assumes `component_ptr` is a valid C string pointer.
188#[unsafe(no_mangle)]
189pub unsafe extern "C" fn logging_log_sysinfo(component_ptr: *const c_char) {
190    let component = unsafe { cstr_to_ustr(component_ptr) };
191    headers::log_sysinfo(component);
192}
193
194/// Flushes global logger buffers of any records.
195#[unsafe(no_mangle)]
196pub extern "C" fn logger_flush() {
197    log::logger().flush();
198}
199
200/// Flushes global logger buffers of any records and then drops the logger.
201#[unsafe(no_mangle)]
202pub extern "C" fn logger_drop(log_guard: LogGuard_API) {
203    drop(log_guard);
204}
205
206#[unsafe(no_mangle)]
207pub extern "C" fn logging_is_initialized() -> u8 {
208    u8::from(crate::logging::logging_is_initialized())
209}
210
211#[unsafe(no_mangle)]
212pub extern "C" fn logging_set_bypass() {
213    crate::logging::logging_set_bypass();
214}
215
216#[unsafe(no_mangle)]
217pub extern "C" fn logging_shutdown() {
218    crate::logging::logging_shutdown();
219}
220
221#[unsafe(no_mangle)]
222pub extern "C" fn logging_is_colored() -> u8 {
223    u8::from(crate::logging::logging_is_colored())
224}
225
226#[unsafe(no_mangle)]
227pub extern "C" fn logging_clock_set_realtime_mode() {
228    crate::logging::logging_clock_set_realtime_mode();
229}
230
231#[unsafe(no_mangle)]
232pub extern "C" fn logging_clock_set_static_mode() {
233    crate::logging::logging_clock_set_static_mode();
234}
235
236#[unsafe(no_mangle)]
237pub extern "C" fn logging_clock_set_static_time(time_ns: u64) {
238    crate::logging::logging_clock_set_static_time(time_ns);
239}