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