Skip to main content

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