1pub mod ffi;
2
3use std::{
4 ffi::{CStr, CString, c_char},
5 os::unix::ffi::OsStrExt,
6 path::Path,
7};
8
9pub use slog2_types::RegisterFlags;
10pub use slog2_types::Verbosity;
11
12use bitflags::bitflags;
13
14bitflags! {
15 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
16 pub struct ParseFlags: u32 {
17 const DYNAMIC = ffi::SLOG2_PARSE_FLAGS_DYNAMIC;
18 const CURRENT = ffi::SLOG2_PARSE_FLAGS_CURRENT;
19 }
20}
21
22bitflags! {
23 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
24 pub struct PacketFlags: u32 {
25 const FIRSTPACKET = ffi::SLOG2_PACKET_FLAGS_FIRSTPACKET;
26 const MONOTONIC = ffi::SLOG2_PACKET_FLAGS_MONOTONIC;
27 }
28}
29
30#[derive(Clone, Copy)]
31pub struct PacketInfo<'a> {
32 info: &'a ffi::slog2_packet_info_t,
33 payload: &'a CStr,
34}
35
36impl<'a> PacketInfo<'a> {
37 pub(crate) fn from_callback_data(
38 info: &'a ffi::slog2_packet_info_t,
39 payload: &'a CStr,
40 ) -> PacketInfo<'a> {
41 Self { info, payload }
42 }
43
44 pub fn sequence_number(&self) -> u16 {
45 self.info.sequence_number
46 }
47
48 pub fn size(&self) -> u16 {
49 self.info.data_size
50 }
51
52 pub fn timestamp_raw(&self) -> u64 {
53 self.info.timestamp
54 }
55
56 pub fn timestamp(&self) -> chrono::NaiveDateTime {
57 chrono::DateTime::from_timestamp_nanos(self.timestamp_raw() as i64).naive_local()
58 }
59
60 pub fn thread_id(&self) -> u16 {
61 self.info.thread_id
62 }
63
64 pub fn severity(&self) -> Verbosity {
65 Verbosity::from_u8(self.info.severity)
66 }
67
68 pub fn file_name(&self) -> Result<&str, std::str::Utf8Error> {
69 unsafe { CStr::from_ptr(self.info.file_name.as_ptr()).to_str() }
70 }
71
72 pub fn buffer_name(&self) -> Result<&str, std::str::Utf8Error> {
73 unsafe { CStr::from_ptr(self.info.buffer_name.as_ptr()).to_str() }
74 }
75
76 pub fn owner_pid(&self) -> u32 {
77 self.info.owner_pid
78 }
79
80 pub fn flags(&self) -> Option<PacketFlags> {
81 PacketFlags::from_bits(self.info.flags)
82 }
83
84 pub fn register_flags(&self) -> Option<RegisterFlags> {
85 RegisterFlags::from_bits(self.info.register_flags)
86 }
87
88 pub fn message(&self) -> Result<&str, std::str::Utf8Error> {
89 self.payload.to_str()
90 }
91}
92
93impl std::fmt::Debug for PacketInfo<'_> {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.debug_struct("PacketInfo")
96 .field("sequence_number", &self.sequence_number())
97 .field("size", &self.size())
98 .field("timestamp", &self.timestamp())
99 .field("thread_id", &self.thread_id())
100 .field("severity", &self.severity())
101 .field("file_name", &self.file_name().ok())
102 .field("buffer_name", &self.buffer_name().ok())
103 .field("owner_pid", &self.owner_pid())
104 .field("flags", &self.flags())
105 .field("register_flags", &self.register_flags())
106 .field("message", &self.message().ok())
107 .finish()
108 }
109}
110
111pub struct LogInfo(ffi::slog2_log_info_t);
112
113impl LogInfo {
114 pub fn num_buffers(&self) -> usize {
115 self.0.num_buffers as usize
116 }
117
118 pub fn owner_pid(&self) -> u32 {
119 self.0.owner_pid
120 }
121
122 pub fn buffer_name(&self) -> Result<&str, std::str::Utf8Error> {
123 unsafe { CStr::from_ptr(self.0.buffer_set_name).to_str() }
124 }
125
126 pub fn verbosity(&self) -> Verbosity {
127 Verbosity::from_u8(self.0.verbosity_level)
128 }
129}
130
131impl std::fmt::Debug for LogInfo {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 f.debug_struct("LogInfo")
134 .field("num_buffers", &self.num_buffers())
135 .field("owner_pid", &self.owner_pid())
136 .field("buffer_name", &self.buffer_name().ok())
137 .field("verbosity", &self.verbosity())
138 .finish()
139 }
140}
141
142pub struct BufferInfo(ffi::slog2_buffer_info_t);
143
144impl BufferInfo {
145 pub fn buffer_size(&self) -> u32 {
146 self.0.buffer_size
147 }
148
149 pub fn buffer_name(&self) -> Result<&str, std::str::Utf8Error> {
150 unsafe { CStr::from_ptr(self.0.buffer_name).to_str() }
151 }
152}
153
154impl std::fmt::Debug for BufferInfo {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 f.debug_struct("BufferInfo")
157 .field("buffer_size", &self.buffer_size())
158 .field("buffer_name", &self.buffer_name().ok())
159 .finish()
160 }
161}
162
163pub struct BufferInfoIterator<'a> {
164 log_file: &'a LogFile,
165 num_buffers: usize,
166 current_index: usize,
167}
168
169impl<'a> BufferInfoIterator<'a> {
170 pub fn new(log_file: &'a LogFile, num_buffers: usize) -> BufferInfoIterator<'a> {
171 BufferInfoIterator {
172 num_buffers,
173 current_index: 0,
174 log_file,
175 }
176 }
177}
178
179impl Iterator for BufferInfoIterator<'_> {
180 type Item = Option<BufferInfo>;
181
182 fn next(&mut self) -> Option<Self::Item> {
183 if self.current_index >= self.num_buffers {
184 None
185 } else {
186 let buffer = self.log_file.buffer_info(self.current_index as i32).ok();
187 self.current_index += 1;
188 Some(buffer)
189 }
190 }
191}
192
193pub struct LogFile(ffi::slog2_log_t);
194
195impl LogFile {
196 pub fn open(filename: &str) -> Option<Self> {
197 let filename = CString::new(filename).ok()?;
198 let result = unsafe { ffi::slog2_open_log(filename.as_ptr()) };
199 if result.is_null() {
200 None
201 } else {
202 Some(LogFile(result))
203 }
204 }
205
206 pub fn info(&self) -> Result<LogInfo, i32> {
207 let mut info = ffi::SLOG2_LOG_INFO_INIT;
208 let result = unsafe { ffi::slog2_get_log_info(self.0, &mut info) };
209 if result == 0 {
210 Ok(LogInfo(info))
211 } else {
212 Err(result)
213 }
214 }
215
216 pub fn buffer_info(&self, buffer_index: i32) -> Result<BufferInfo, i32> {
217 let mut info = ffi::SLOG2_BUFFER_INFO_INIT;
218 let result = unsafe { ffi::slog2_get_buffer_info(self.0, buffer_index, &mut info) };
219 if result == 0 {
220 Ok(BufferInfo(info))
221 } else {
222 Err(result)
223 }
224 }
225
226 pub fn parse_static<F>(&self, buffer_index: i32, callback: F) -> Result<(), i32>
227 where
228 F: FnMut(PacketInfo) -> Result<(), i32>,
229 {
230 let mut callback_box = Box::new(callback);
231 let param = &mut *callback_box as *mut _ as *mut core::ffi::c_void;
232 let mut packet_info = ffi::SLOG2_PACKET_INFO_INIT;
233 let result = unsafe {
234 ffi::slog2_parse_dynamic_buffer(
235 self.0,
236 buffer_index,
237 &mut packet_info,
238 Some(parse_trampoline::<F>),
239 param,
240 )
241 };
242 if result == 0 { Ok(()) } else { Err(result) }
243 }
244
245 pub fn parse_dynamic<F>(&self, buffer_index: i32, callback: F) -> Result<(), i32>
246 where
247 F: FnMut(PacketInfo) -> Result<(), i32>,
248 {
249 let mut callback_box = Box::new(callback);
250 let param = &mut *callback_box as *mut _ as *mut core::ffi::c_void;
251 let mut packet_info = ffi::SLOG2_PACKET_INFO_INIT;
252 let result = unsafe {
253 ffi::slog2_parse_dynamic_buffer(
254 self.0,
255 buffer_index,
256 &mut packet_info,
257 Some(parse_trampoline::<F>),
258 param,
259 )
260 };
261 if result == 0 { Ok(()) } else { Err(result) }
262 }
263
264 pub(crate) fn buffer_info_iter(&self) -> Result<BufferInfoIterator<'_>, i32> {
265 Ok(BufferInfoIterator::new(self, self.info()?.num_buffers()))
266 }
267}
268
269impl<'a> IntoIterator for &'a LogFile {
270 type Item = Option<BufferInfo>;
271 type IntoIter = BufferInfoIterator<'a>;
272
273 fn into_iter(self) -> Self::IntoIter {
274 self.buffer_info_iter().unwrap_or(BufferInfoIterator {
276 log_file: self,
277 num_buffers: 0,
278 current_index: 0,
279 })
280 }
281}
282
283impl Drop for LogFile {
284 fn drop(&mut self) {
285 unsafe { ffi::slog2_close_log(self.0) };
286 }
287}
288
289pub fn parse_all<F>(
290 flags: Option<ParseFlags>,
291 directory: Option<&Path>,
292 match_list: Option<&str>,
293 callback: F,
294) -> Result<(), i32>
295where
296 F: FnMut(PacketInfo) -> Result<(), i32>,
297{
298 let directory = directory
299 .map(|p| CString::new(p.as_os_str().as_bytes()).expect("Path contains interior NUL"));
300 let directory_ptr = directory
301 .as_ref()
302 .map_or(std::ptr::null_mut(), |cstr| cstr.as_ptr() as *mut c_char);
303
304 let match_list = match_list.map(|s| CString::new(s).expect("Path contains interior NUL"));
305 let match_list_ptr = match_list
306 .as_ref()
307 .map_or(std::ptr::null_mut(), |cstr| cstr.as_ptr() as *mut c_char);
308
309 let mut callback_box = Box::new(callback);
310 let param = &mut *callback_box as *mut _ as *mut core::ffi::c_void;
311 let mut packet_info = ffi::SLOG2_PACKET_INFO_INIT;
312 let result = unsafe {
313 ffi::slog2_parse_all(
314 flags.map(|flags| flags.bits()).unwrap_or_default(),
315 directory_ptr,
316 match_list_ptr,
317 &mut packet_info,
318 Some(parse_trampoline::<F>),
319 param,
320 )
321 };
322 if result == 0 { Ok(()) } else { Err(result) }
323}
324
325extern "C" fn parse_trampoline<F>(
326 info: *mut ffi::slog2_packet_info_t,
327 payload: *mut core::ffi::c_void,
328 param: *mut core::ffi::c_void,
329) -> i32
330where
331 F: FnMut(PacketInfo) -> Result<(), i32>,
332{
333 let closure: &mut F = unsafe { &mut *(param as *mut F) };
334 let payload = unsafe { CStr::from_ptr(payload as *mut c_char) };
335 let info = PacketInfo::from_callback_data(unsafe { &*info }, payload);
336 if let Err(res) = closure(info) { res } else { 0 }
337}