1mod injector;
2mod json;
3mod plain;
4
5pub use json::Injector as JsonInjector;
6pub use plain::Injector as PlainInjector;
7
8use libc::size_t;
9use serde_json::{json, Value};
10use std::ffi::{c_char, c_int, CStr};
11use std::io;
12use std::io::ErrorKind;
13use tarantool::ffi;
14use tarantool::ffi::tarantool::{LogFormatFn, VaList};
15use tarantool::fiber::FiberId;
16use tarantool::log::SayLevel;
17
18extern "C" {
19 #[allow(dead_code)]
20 fn vsprintf(s: *mut c_char, format: *const c_char, ap: VaList) -> c_int;
21 fn vsnprintf(s: *mut c_char, n: size_t, format: *const c_char, ap: VaList) -> c_int;
22}
23
24fn level_to_string(lvl: SayLevel) -> &'static str {
25 match lvl {
26 SayLevel::Fatal => "FATAL",
27 SayLevel::System => "SYSERROR",
28 SayLevel::Error => "ERROR",
29 SayLevel::Crit => "CRIT",
30 SayLevel::Warn => "WARN",
31 SayLevel::Info => "INFO",
32 SayLevel::Verbose => "VERBOSE",
33 SayLevel::Debug => "DEBUG",
34 }
35}
36
37fn level_to_char(lvl: SayLevel) -> &'static str {
38 match lvl {
39 SayLevel::Fatal => "F",
40 SayLevel::System => "!",
41 SayLevel::Error => "E",
42 SayLevel::Crit => "C",
43 SayLevel::Warn => "W",
44 SayLevel::Info => "I",
45 SayLevel::Verbose => "V",
46 SayLevel::Debug => "D",
47 }
48}
49
50fn json_escape_c(c_str: &CStr) -> io::Result<String> {
51 json_escape(
52 c_str
53 .to_str()
54 .map_err(|_| io::Error::new(ErrorKind::InvalidData, "invalid utf8 string"))?,
55 )
56}
57
58fn json_escape(str: &str) -> io::Result<String> {
59 let Value::String(escaped) = json!(str) else {
60 unreachable!()
61 };
62 Ok(escaped)
63}
64
65pub enum Format {
67 JsonTarantool(Option<JsonInjector>),
72 Json(Option<JsonInjector>),
76 PlainTarantool(Option<PlainInjector>),
81 Custom(LogFormatFn),
83}
84
85pub fn set_default_logger_format(format: Format) {
104 let default_logger = unsafe { &mut *tarantool::ffi::tarantool::log_default_logger() };
106
107 set_logger_format(default_logger, format)
108}
109
110pub fn set_logger_format(logger: &mut ffi::tarantool::Logger, format: Format) {
131 let log_format = match format {
132 Format::JsonTarantool(injector) => json::raw::make_custom_json_format(logger, injector),
133 Format::Json(injector) => json::serde::make_custom_json_format_serde(logger, injector),
134 Format::PlainTarantool(injector) => plain::make_custom_plain_format(logger, injector),
135 Format::Custom(f) => f,
136 };
137
138 unsafe { tarantool::ffi::tarantool::log_set_format(logger, log_format) };
140}
141
142fn get_cord_name() -> &'static str {
143 #[cfg(not(test))]
144 {
145 let cord_name_ptr = unsafe { tarantool::ffi::tarantool::current_cord_name() };
146 if cord_name_ptr.is_null() {
147 "unknown"
148 } else {
149 unsafe { std::str::from_utf8_unchecked(CStr::from_ptr(cord_name_ptr).to_bytes()) }
150 }
151 }
152 #[cfg(test)]
153 {
154 "unknown"
155 }
156}
157
158struct FiberInfo {
159 id: FiberId,
160 name: &'static str,
161}
162
163#[allow(unused)]
164fn get_fiber_info(cord_name: &str) -> Option<FiberInfo> {
165 #[cfg(not(test))]
166 {
167 use tarantool::fiber;
168
169 const FIBER_ID_SCHED: u64 = 1;
171
172 if cord_name != "unknown" {
173 let fiber_id = fiber::id();
174 if fiber_id != FIBER_ID_SCHED {
175 let fiber_name = unsafe {
176 std::str::from_utf8_unchecked(fiber::name_raw(None).unwrap_or_default())
177 };
178 return Some(FiberInfo {
179 id: fiber_id,
180 name: fiber_name,
181 });
182 }
183 };
184 None
185 }
186
187 #[cfg(test)]
188 None
189}
190
191#[cfg(test)]
192mod helper {
193 use std::ffi::c_int;
194 use std::ffi::{c_char, CString};
195 use std::ptr;
196 use tarantool::ffi::tarantool::VaList;
197 use tarantool::log::SayLevel;
198
199 #[link(name = "va_list_test", kind = "static")]
201 extern "C" {
202 pub fn dispatch(context: *mut u8, count: ::libc::c_uint, ...);
203 }
204
205 pub type CbType<'a> = &'a mut dyn FnMut(VaList<'a>);
206
207 #[no_mangle]
209 pub extern "C" fn inbound(context: *mut u8, _count: u32, args: VaList) {
210 let cb_ptr = unsafe { ptr::read(context as *mut CbType) };
211 (cb_ptr)(args);
212 }
213
214 #[macro_export]
216 macro_rules! with_va_list {
217 (($($args:expr),*), $code:expr) => ({
218 let mut cb = $code;
219 let mut cb_dyn: $crate::helper::CbType = &mut cb;
220
221 unsafe {
222 $crate::helper::dispatch(
223 &mut cb_dyn as *mut _ as *mut u8,
224 0,
225 $($args),*
226 );
227 }
228 });
229 }
230
231 pub enum MessageType {
232 TwoInts(i32, i32),
233 String(&'static str),
234 }
235
236 pub struct TestCase<I> {
237 pub level: c_int,
238 module: Option<CString>,
239 filename: Option<CString>,
240 pub line: c_int,
241 error: Option<CString>,
242 format: Option<CString>,
243 pub message_type: MessageType,
244 pub injector: Option<I>,
245 pub expected_record_re: &'static str,
246 }
247
248 #[allow(clippy::too_many_arguments)]
249 impl<I> TestCase<I> {
250 pub fn new(
251 level: SayLevel,
252 module: Option<&str>,
253 filename: Option<&str>,
254 line: i32,
255 error: Option<&str>,
256 format: Option<&str>,
257 message_type: MessageType,
258 injector: Option<I>,
259 expected_re: &'static str,
260 ) -> Self {
261 Self {
262 level: level as i32,
263 module: module.map(|m| CString::new(m).unwrap()),
264 filename: filename.map(|f| CString::new(f).unwrap()),
265 line,
266 error: error.map(|e| CString::new(e).unwrap()),
267 format: format.map(|f| CString::new(f).unwrap()),
268 message_type,
269 expected_record_re: expected_re,
270 injector,
271 }
272 }
273
274 pub fn module_ptr(&self) -> *const c_char {
275 self.module
276 .as_ref()
277 .map(|m| m.as_ptr())
278 .unwrap_or(ptr::null())
279 }
280
281 pub fn filename_ptr(&self) -> *const c_char {
282 self.filename
283 .as_ref()
284 .map(|f| f.as_ptr())
285 .unwrap_or(ptr::null())
286 }
287
288 pub fn error_ptr(&self) -> *const c_char {
289 self.error
290 .as_ref()
291 .map(|e| e.as_ptr())
292 .unwrap_or(ptr::null())
293 }
294
295 pub fn format_ptr(&self) -> *const c_char {
296 self.format
297 .as_ref()
298 .map(|f| f.as_ptr())
299 .unwrap_or(ptr::null())
300 }
301 }
302}