nautilus_common/ffi/
logging.rs

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