streamkit_plugin_sdk_native/
logger.rs

1// SPDX-FileCopyrightText: © 2025 StreamKit Contributors
2//
3// SPDX-License-Identifier: MPL-2.0
4
5//! Logging utilities for native plugins
6//!
7//! Provides a logger that sends log messages back to the host via callback.
8
9use crate::types::{CLogCallback, CLogLevel};
10use std::ffi::CString;
11use std::os::raw::c_void;
12
13/// Logger for sending log messages to the host
14#[derive(Clone)]
15pub struct Logger {
16    callback: CLogCallback,
17    user_data: *mut c_void,
18    target: String,
19}
20
21// SAFETY: The callback is a C function pointer which is thread-safe,
22// and user_data is managed by the host which ensures thread-safety
23unsafe impl Send for Logger {}
24unsafe impl Sync for Logger {}
25
26impl Logger {
27    /// Create a new logger
28    pub fn new(callback: CLogCallback, user_data: *mut c_void, target: &str) -> Self {
29        Self { callback, user_data, target: target.to_string() }
30    }
31
32    /// Log a message at the given level
33    pub fn log(&self, level: CLogLevel, message: &str) {
34        // Convert strings to C strings
35        let Ok(target_cstr) = CString::new(self.target.as_str()) else {
36            return; // Silently ignore if target has null bytes
37        };
38
39        let Ok(message_cstr) = CString::new(message) else {
40            return; // Silently ignore if message has null bytes
41        };
42
43        // Call the host's logging callback
44        (self.callback)(level, target_cstr.as_ptr(), message_cstr.as_ptr(), self.user_data);
45    }
46
47    /// Log a trace message
48    pub fn trace(&self, message: &str) {
49        self.log(CLogLevel::Trace, message);
50    }
51
52    /// Log a debug message
53    pub fn debug(&self, message: &str) {
54        self.log(CLogLevel::Debug, message);
55    }
56
57    /// Log an info message
58    pub fn info(&self, message: &str) {
59        self.log(CLogLevel::Info, message);
60    }
61
62    /// Log a warning message
63    pub fn warn(&self, message: &str) {
64        self.log(CLogLevel::Warn, message);
65    }
66
67    /// Log an error message
68    pub fn error(&self, message: &str) {
69        self.log(CLogLevel::Error, message);
70    }
71}
72
73/// Helper macro to format tracing-style field syntax into a simple string
74#[doc(hidden)]
75#[macro_export]
76macro_rules! __format_fields {
77    // Base case: just a format string
78    ($fmt:literal) => {
79        format!($fmt)
80    };
81    // Base case: format string with args
82    ($fmt:literal, $($args:expr),+ $(,)?) => {
83        format!($fmt, $($args),+)
84    };
85    // Field with % formatting (display) followed by more fields: field = %value, ...rest
86    ($field:ident = %$value:expr, $($rest:tt)+) => {{
87        let prefix = format!("{} = {}", stringify!($field), $value);
88        let suffix = $crate::__format_fields!($($rest)+);
89        if suffix.is_empty() {
90            prefix
91        } else {
92            format!("{}, {}", prefix, suffix)
93        }
94    }};
95    // Field with % formatting (display) - last field
96    ($field:ident = %$value:expr) => {
97        format!("{} = {}", stringify!($field), $value)
98    };
99    // Field with ? formatting (debug) followed by more fields: field = ?value, ...rest
100    ($field:ident = ?$value:expr, $($rest:tt)+) => {{
101        let prefix = format!("{} = {:?}", stringify!($field), $value);
102        let suffix = $crate::__format_fields!($($rest)+);
103        if suffix.is_empty() {
104            prefix
105        } else {
106            format!("{}, {}", prefix, suffix)
107        }
108    }};
109    // Field with ? formatting (debug) - last field
110    ($field:ident = ?$value:expr) => {
111        format!("{} = {:?}", stringify!($field), $value)
112    };
113    // Field without formatting followed by more fields: field = value, ...rest
114    ($field:ident = $value:expr, $($rest:tt)+) => {{
115        let prefix = format!("{} = {:?}", stringify!($field), $value);
116        let suffix = $crate::__format_fields!($($rest)+);
117        if suffix.is_empty() {
118            prefix
119        } else {
120            format!("{}, {}", prefix, suffix)
121        }
122    }};
123    // Field without formatting - last field
124    ($field:ident = $value:expr) => {
125        format!("{} = {:?}", stringify!($field), $value)
126    };
127}
128
129/// Helper macros for logging with tracing-style field syntax support
130#[macro_export]
131macro_rules! plugin_log {
132    ($logger:expr, $level:expr, $($arg:tt)*) => {
133        $logger.log($level, &$crate::__format_fields!($($arg)*))
134    };
135}
136
137#[macro_export]
138macro_rules! plugin_trace {
139    ($logger:expr, $($arg:tt)*) => {
140        $logger.trace(&$crate::__format_fields!($($arg)*))
141    };
142}
143
144#[macro_export]
145macro_rules! plugin_debug {
146    ($logger:expr, $($arg:tt)*) => {
147        $logger.debug(&$crate::__format_fields!($($arg)*))
148    };
149}
150
151#[macro_export]
152macro_rules! plugin_info {
153    ($logger:expr, $($arg:tt)*) => {
154        $logger.info(&$crate::__format_fields!($($arg)*))
155    };
156}
157
158#[macro_export]
159macro_rules! plugin_warn {
160    ($logger:expr, $($arg:tt)*) => {
161        $logger.warn(&$crate::__format_fields!($($arg)*))
162    };
163}
164
165#[macro_export]
166macro_rules! plugin_error {
167    ($logger:expr, $($arg:tt)*) => {
168        $logger.error(&$crate::__format_fields!($($arg)*))
169    };
170}