--- /dev/null
+use std::ffi::{c_char, c_int};
+
+extern "C" {
+ pub fn dlog_connect_fd(buffer: i32, fileno: c_int, tag: *const c_char, prio: i32) -> c_int;
+ pub fn dlog_is_log_fd(fd: c_int) -> bool;
+}
--- /dev/null
+use dlog::{BufferId, Priority};
+use dlog_redirect_stdout_sys::*;
+use std::ffi::CStr;
+use std::io::{Error, ErrorKind, Result};
+
+/// Open an unstructured connection to a DLog logging sink.
+///
+/// Opens a connection with stdout to which you can stream data.
+/// DLog will automatically convert it to individual logs appropriately.
+///
+/// # Arguments
+///
+/// * `buffer` - Target DLog buffer.
+/// * `tag` - DLog tag for the resulting logs.
+/// * `prio` - Priority for the resulting logs.
+///
+/// # Errors
+///
+/// * [`std::io::ErrorKind::InvalidInput`] - Buffer does not allow connections.
+/// * [`std::io::ErrorKind::PermissionDenied`] - Buffer disabled via dlog config.
+/// * [`std::io::ErrorKind::Uncategorized`] - Buffer not configured via dlog config.
+/// * [`std::io::ErrorKind::Uncategorized`] - Unsupported DLog backend.
+/// * [`std::io::ErrorKind::InvalidInput`] - Tag empty.
+///
+/// # Examples
+///
+/// ```
+/// use dlog::{BufferId, Priority};
+/// use dlog_redirect_stdout::redirect_stdout;
+///
+/// fn main() -> Result<(), std::io::Error> {
+/// redirect_stdout(BufferId::LogIdMain, "foo", Priority::Info)?;
+/// // The following sends an info log with tag "foo" and message "my_message".
+/// println!("my_message");
+/// Ok(())
+/// }
+/// ```
+pub fn redirect_stdout(buffer: BufferId, tag: &str, prio: Priority) -> Result<()> {
+ let tag = format!("{}\0", tag);
+ let tag_c = CStr::from_bytes_with_nul(tag.as_bytes()).map_err(|_| ErrorKind::InvalidInput)?;
+ // SAFETY: the string is defined in a verified way above. All other parameters
+ // are checked for correctness on the C side.
+ match unsafe { dlog_connect_fd(buffer as i32, 1, tag_c.as_ptr(), prio as i32) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+}
+
+/// Open an unstructured connection to a DLog logging sink.
+///
+/// Opens a connection with stderr to which you can stream data.
+/// DLog will automatically convert it to individual logs appropriately.
+///
+/// # Arguments
+///
+/// * `buffer` - Target DLog buffer.
+/// * `tag` - DLog tag for the resulting logs.
+/// * `prio` - Priority for the resulting logs.
+///
+/// # Errors
+///
+/// * [`std::io::ErrorKind::InvalidInput`] - Buffer does not allow connections.
+/// * [`std::io::ErrorKind::PermissionDenied`] - Buffer disabled via dlog config.
+/// * [`std::io::ErrorKind::Uncategorized`] - Buffer not configured via dlog config.
+/// * [`std::io::ErrorKind::Uncategorized`] - Unsupported DLog backend.
+/// * [`std::io::ErrorKind::InvalidInput`] - Tag empty.
+///
+/// # Examples
+///
+/// ```
+/// use dlog::{BufferId, Priority};
+/// use dlog_redirect_stdout::redirect_stderr;
+///
+/// fn main() -> Result<(), std::io::Error> {
+/// redirect_stderr(BufferId::LogIdMain, "foo", Priority::Info)?;
+/// // The following sends an info log with tag "foo" and message "my_message".
+/// eprintln!("my_message");
+/// Ok(())
+/// }
+/// ```
+pub fn redirect_stderr(buffer: BufferId, tag: &str, prio: Priority) -> Result<()> {
+ let tag = format!("{}\0", tag);
+ let tag_c = CStr::from_bytes_with_nul(tag.as_bytes()).map_err(|_| ErrorKind::InvalidInput)?;
+ // SAFETY: the string is defined in a verified way above. All other parameters
+ // are checked for correctness on the C side.
+ match unsafe { dlog_connect_fd(buffer as i32, 2, tag_c.as_ptr(), prio as i32) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+}
+
+/// Check whether a file descriptor has been opened via DLog.
+///
+/// Use for low-level code that does bulk operations on FDs (for example, close
+/// them all) but still wants to be able to use DLog interfaces afterwards.
+///
+/// # Returns
+///
+/// * `bool` - Whether the descriptor refers to a DLog file.
+pub fn is_log_fd(fd: i32) -> bool {
+ // SAFETY: the C function is safe by itself.
+ unsafe { dlog_is_log_fd(fd) }
+}
--- /dev/null
+use std::ffi::{c_char, c_int};
+
+#[repr(C)]
+#[allow(non_camel_case_types)]
+pub enum log_priority {
+ DLOG_UNKNOWN = 0,
+ DLOG_DEFAULT,
+ DLOG_VERBOSE,
+ DLOG_DEBUG,
+ DLOG_INFO,
+ DLOG_WARN,
+ DLOG_ERROR,
+ DLOG_FATAL,
+ DLOG_SILENT,
+ DLOG_PRIO_MAX,
+}
+
+#[repr(C)]
+#[allow(non_camel_case_types)]
+pub enum log_id_t {
+ LOG_ID_INVALID = -1,
+ LOG_ID_MAIN,
+ LOG_ID_RADIO,
+ LOG_ID_SYSTEM,
+ LOG_ID_APPS,
+ LOG_ID_KMSG,
+ LOG_ID_SYSLOG,
+ LOG_ID_MAX,
+}
+
+extern "C" {
+ pub fn dlog_print(prio: log_priority, tag: *const c_char, fmt: *const c_char, ...) -> c_int;
+ pub fn __dlog_critical_print(
+ log_id: log_id_t,
+ prio: c_int,
+ tag: *const c_char,
+ fmt: *const c_char,
+ ...
+ ) -> c_int;
+ pub fn dlog_set_minimum_priority(priority: c_int) -> c_int;
+}
--- /dev/null
+use dlog_sys::*;
+use log::{Level, LevelFilter, Log, Metadata, Record};
+use std::ffi::CStr;
+use std::io::{Error, Result};
+
+/// Enumeration for log priority values in ascending priority order.
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
+pub enum Priority {
+ Unknown = 0,
+ Default,
+ Verbose,
+ Debug,
+ Info,
+ Warn,
+ Error,
+ Fatal,
+ Silent,
+ PrioMax,
+}
+
+/// Log id.
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
+pub enum BufferId {
+ LogIdMain = 0,
+ LogIdRadio,
+ LogIdSystem,
+ LogIdApps,
+ LogIdKmsg,
+ LogIdSyslog,
+}
+
+impl From<log_priority> for Priority {
+ fn from(c_enum: log_priority) -> Self {
+ match c_enum {
+ log_priority::DLOG_UNKNOWN => Priority::Unknown,
+ log_priority::DLOG_DEFAULT => Priority::Default,
+ log_priority::DLOG_VERBOSE => Priority::Verbose,
+ log_priority::DLOG_DEBUG => Priority::Debug,
+ log_priority::DLOG_INFO => Priority::Info,
+ log_priority::DLOG_WARN => Priority::Warn,
+ log_priority::DLOG_ERROR => Priority::Error,
+ log_priority::DLOG_FATAL => Priority::Fatal,
+ log_priority::DLOG_SILENT => Priority::Silent,
+ log_priority::DLOG_PRIO_MAX => Priority::PrioMax,
+ }
+ }
+}
+
+impl From<log_id_t> for BufferId {
+ fn from(c_enum: log_id_t) -> Self {
+ match c_enum {
+ log_id_t::LOG_ID_MAIN => BufferId::LogIdMain,
+ log_id_t::LOG_ID_RADIO => BufferId::LogIdRadio,
+ log_id_t::LOG_ID_SYSTEM => BufferId::LogIdSystem,
+ log_id_t::LOG_ID_APPS => BufferId::LogIdApps,
+ log_id_t::LOG_ID_KMSG => BufferId::LogIdKmsg,
+ log_id_t::LOG_ID_SYSLOG => BufferId::LogIdSyslog,
+ _ => panic!("Invalid log_id_t"),
+ }
+ }
+}
+
+impl From<BufferId> for log_id_t {
+ fn from(rust_enum: BufferId) -> Self {
+ match rust_enum {
+ BufferId::LogIdMain => log_id_t::LOG_ID_MAIN,
+ BufferId::LogIdRadio => log_id_t::LOG_ID_RADIO,
+ BufferId::LogIdSystem => log_id_t::LOG_ID_SYSTEM,
+ BufferId::LogIdApps => log_id_t::LOG_ID_APPS,
+ BufferId::LogIdKmsg => log_id_t::LOG_ID_KMSG,
+ BufferId::LogIdSyslog => log_id_t::LOG_ID_SYSLOG,
+ }
+ }
+}
+
+struct DlogLogger {
+ is_critical: bool,
+}
+
+impl Log for DlogLogger {
+ fn enabled(&self, _meta: &Metadata) -> bool {
+ true
+ }
+
+ fn log(&self, record: &Record) {
+ let target = record.target();
+ let file = record
+ .file()
+ .map(|s| s.to_owned())
+ .unwrap_or_else(|| "?".to_owned());
+ let line = record
+ .line()
+ .map(|l| l.to_string())
+ .unwrap_or_else(|| "?".to_owned());
+ let args = record.args();
+
+ let prio = match record.level() {
+ Level::Error => log_priority::DLOG_ERROR,
+ Level::Warn => log_priority::DLOG_WARN,
+ Level::Info => log_priority::DLOG_INFO,
+ Level::Debug => log_priority::DLOG_DEBUG,
+ Level::Trace => log_priority::DLOG_VERBOSE,
+ };
+
+ let tag = target.to_string().replace('\0', "\\0") + "\0";
+ let contents = format!("{}({}) > {}", file, line, args).replace('\0', "\\0") + "\0";
+
+ // SAFETY: the three _unchecked calls are ok, since we make sure that the
+ // three strings only have a single \0 and it is at the end. Then the dlog
+ // call is called with verified parameters, which is completely safe.
+ unsafe {
+ let tag_c = CStr::from_bytes_with_nul_unchecked(tag.as_bytes());
+ let fmt_c = CStr::from_bytes_with_nul_unchecked(b"%s\0");
+ let contents_c = CStr::from_bytes_with_nul_unchecked(contents.as_bytes());
+ // TODO: What about structured values?
+ if self.is_critical {
+ __dlog_critical_print(
+ BufferId::LogIdMain.into(),
+ prio as i32,
+ tag_c.as_ptr(),
+ fmt_c.as_ptr(),
+ contents_c.as_ptr(),
+ );
+ } else {
+ dlog_print(prio, tag_c.as_ptr(), fmt_c.as_ptr(), contents_c.as_ptr());
+ }
+ }
+ }
+
+ fn flush(&self) {}
+}
+
+/// Initializes the DLog logging system.
+///
+/// This function sets up the logging infrastructure by configuring the logger
+/// for the current application. **It should be called once**.
+///
+/// Under the hood, there is a struct with [`Log`] trait implemented that uses
+/// `dlog_print` for sending logs to DLog. If invoked with `is_critical = true`,
+/// it sends critical logs instead.
+///
+/// Priority of a log is defined by a macro used and tag is constructed from
+/// the binary name. However, it can be overwritten - see example below.
+///
+/// [`Log`]: log::Log
+///
+/// # Errors
+///
+/// * [`std::io::ErrorKind::Other`] - Wrapped error from [`set_boxed_logger`].
+///
+/// [`set_boxed_logger`]: log::set_boxed_logger
+///
+/// # Examples
+///
+/// Sending non-critical logs:
+///
+/// ```
+/// use log::info;
+///
+/// fn main() -> Result<(), std::io::Error> {
+/// dlog::init(false)?;
+/// info!("my first log");
+/// }
+/// ```
+///
+/// Sending critical logs:
+///
+/// ```
+/// use log::info;
+///
+/// fn main() -> Result<(), std::io::Error> {
+/// dlog::init(true)?;
+/// info!("my first critical log");
+/// }
+/// ```
+///
+/// You can set your custom tag by setting `target` argument:
+///
+/// ```
+/// use log::info;
+///
+/// fn main() -> Result<(), std::io::Error> {
+/// dlog::init(false)?;
+/// info!(target: "my_tag", "my first log with a custom tag");
+/// }
+/// ```
+pub fn init(is_critical: bool) -> Result<()> {
+ log::set_boxed_logger(Box::new(DlogLogger { is_critical })).map_err(Error::other)?;
+ log::set_max_level(LevelFilter::Trace);
+ Ok(())
+}
+
+/// Blocks low priority logs.
+///
+/// Allows filtering low priority logs globally, think a runtime "--verbose" mode,
+/// except you can choose which priorities to filter.
+/// Using this (as opposed to filtering later, when retrieving logs) is that it
+/// helps conserve system logging resources (buffer space etc.).
+///
+/// # Arguments
+///
+/// * `prio` - Minimum priority level.
+///
+/// # Examples
+///
+/// ```
+/// use dlog::Priority;
+/// use log::{error, info, warn};
+///
+/// fn main() -> Result<(), std::io::Error> {
+/// dlog::init(false)?;
+/// info!("no filter so this log goes through");
+/// dlog::set_minimum_priority(Priority::Warn)?;
+/// info!("WARN is a higher level than INFO so this log gets filtered");
+/// warn!("but this one still goes through");
+/// error!("as does this one");
+/// Ok(())
+/// }
+/// ```
+pub fn set_minimum_priority(prio: Priority) -> Result<()> {
+ // SAFETY: the underlying C function is safe by itself, even if the parameter
+ // is out of range. The parameter will be in range in our case anyway, since
+ // our enum matches the C one.
+ match unsafe { dlog_set_minimum_priority(prio as i32) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+}
--- /dev/null
+use dlog_sys::{log_id_t, log_priority};
+use libc::timespec;
+use std::ffi::{c_char, c_int, c_uint};
+
+#[repr(C)]
+#[allow(non_camel_case_types)]
+pub enum dlogutil_sorting_order {
+ DLOGUTIL_SORT_SENT_MONO = 0,
+ DLOGUTIL_SORT_SENT_REAL,
+ DLOGUTIL_SORT_RECV_MONO,
+ DLOGUTIL_SORT_RECV_REAL,
+ DLOGUTIL_SORT_DEFAULT,
+}
+
+#[repr(C)]
+pub struct dlogutil_config {
+ _data: [u8; 0],
+ _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
+}
+#[repr(C)]
+pub struct dlogutil_state {
+ _data: [u8; 0],
+ _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
+}
+#[repr(C)]
+pub struct dlogutil_entry {
+ _data: [u8; 0],
+ _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
+}
+
+extern "C" {
+ pub fn dlogutil_config_create() -> *mut dlogutil_config;
+ pub fn dlogutil_config_destroy(config: *mut dlogutil_config);
+ pub fn dlogutil_config_filter_tid(config: *mut dlogutil_config, tid: c_int) -> c_int;
+ pub fn dlogutil_config_filter_pid(config: *mut dlogutil_config, pid: c_int) -> c_int;
+ pub fn dlogutil_config_filter_filterspec(
+ config: *mut dlogutil_config,
+ query: *const c_char,
+ ) -> c_int;
+ pub fn dlogutil_config_sorting_disable(config: *mut dlogutil_config) -> c_int;
+ pub fn dlogutil_config_sorting_enable(config: *mut dlogutil_config) -> c_int;
+ pub fn dlogutil_config_sorting_enable_with_size(
+ config: *mut dlogutil_config,
+ entry_count: c_uint,
+ ) -> c_int;
+ pub fn dlogutil_config_order_set(
+ config: *mut dlogutil_config,
+ sort_by: dlogutil_sorting_order,
+ ) -> c_int;
+ pub fn dlogutil_config_buffer_add(config: *mut dlogutil_config, buf: log_id_t) -> c_int;
+ pub fn dlogutil_state_destroy(state: *mut dlogutil_state);
+ pub fn dlogutil_config_connect(
+ config: *mut dlogutil_config,
+ out_state: *mut *mut dlogutil_state,
+ ) -> c_int;
+ pub fn dlogutil_config_mode_set_continuous(config: *mut dlogutil_config) -> c_int;
+ pub fn dlogutil_config_mode_set_monitor(config: *mut dlogutil_config) -> c_int;
+ pub fn dlogutil_config_mode_set_dump(
+ config: *mut dlogutil_config,
+ entry_count: c_uint,
+ ) -> c_int;
+ pub fn dlogutil_config_mode_set_compressed_memory_dump(
+ config: *mut dlogutil_config,
+ compress_buffer: *const c_char,
+ ) -> c_int;
+ pub fn dlogutil_get_log(
+ state: *mut dlogutil_state,
+ timeout: c_int,
+ entry_out: *mut *mut dlogutil_entry,
+ ) -> c_int;
+ pub fn dlogutil_buffer_clear(state: *mut dlogutil_state, buffer: log_id_t) -> c_int;
+ pub fn dlogutil_buffer_get_name(buffer: log_id_t, name: *mut *const c_char) -> c_int;
+ pub fn dlogutil_buffer_get_capacity(
+ state: *mut dlogutil_state,
+ buffer: log_id_t,
+ capacity: *mut c_uint,
+ ) -> c_int;
+ pub fn dlogutil_buffer_get_usage(
+ state: *mut dlogutil_state,
+ buffer: log_id_t,
+ usage: *mut c_uint,
+ ) -> c_int;
+ pub fn dlogutil_buffer_get_default_ts_type(
+ buffer: log_id_t,
+ typ: *mut dlogutil_sorting_order,
+ ) -> c_int;
+ pub fn dlogutil_buffer_check_ts_type_available(
+ buffer: log_id_t,
+ typ: dlogutil_sorting_order,
+ available: *mut bool,
+ ) -> c_int;
+ pub fn dlogutil_buffer_get_alias(
+ state: *mut dlogutil_state,
+ buffer: log_id_t,
+ real_buffer: *mut log_id_t,
+ ) -> c_int;
+ pub fn dlogutil_entry_destroy(entry: *mut dlogutil_entry);
+ pub fn dlogutil_entry_get_timestamp(
+ entry: *const dlogutil_entry,
+ stamp_type: dlogutil_sorting_order,
+ ts: *mut timespec,
+ ) -> c_int;
+ pub fn dlogutil_entry_get_tid(entry: *const dlogutil_entry, tid: *mut c_int) -> c_int;
+ pub fn dlogutil_entry_get_pid(entry: *const dlogutil_entry, pid: *mut c_int) -> c_int;
+ pub fn dlogutil_entry_get_priority(
+ entry: *const dlogutil_entry,
+ prio: *mut log_priority,
+ ) -> c_int;
+ pub fn dlogutil_entry_get_tag(entry: *const dlogutil_entry, tag: *mut *const c_char) -> c_int;
+ pub fn dlogutil_entry_get_message(
+ entry: *const dlogutil_entry,
+ msg: *mut *const c_char,
+ ) -> c_int;
+}
--- /dev/null
+use dlog::{BufferId, Priority};
+use dlog_sys::{log_id_t, log_priority};
+use dlogutil_sys::*;
+use libc::timespec;
+use std::ffi::CStr;
+use std::io::{Error, ErrorKind, Result};
+use std::ptr::{null, null_mut};
+use std::time::Duration;
+
+/// Enumeration for timestamp-based log sorting orderings.
+///
+/// For more detailed information on the timestamp documentation,
+/// refer to the C library documentation.
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
+pub enum SortingOrder {
+ /// Monotonic timestamp applied by the sender.
+ SentMono,
+ /// Real-time timestamp applied by the sender.
+ SentReal,
+ /// Monotonic timestamp applied by the receiver.
+ RecvMono,
+ /// Real-time timestamp applied by the receiver.
+ RecvReal,
+ /// The default timestamp of the buffer. See [`buffer_get_default_ts_type`].
+ Default,
+}
+
+impl From<dlogutil_sorting_order> for SortingOrder {
+ fn from(c_enum: dlogutil_sorting_order) -> Self {
+ match c_enum {
+ dlogutil_sorting_order::DLOGUTIL_SORT_SENT_MONO => SortingOrder::SentMono,
+ dlogutil_sorting_order::DLOGUTIL_SORT_SENT_REAL => SortingOrder::SentReal,
+ dlogutil_sorting_order::DLOGUTIL_SORT_RECV_MONO => SortingOrder::RecvMono,
+ dlogutil_sorting_order::DLOGUTIL_SORT_RECV_REAL => SortingOrder::RecvReal,
+ dlogutil_sorting_order::DLOGUTIL_SORT_DEFAULT => SortingOrder::Default,
+ }
+ }
+}
+
+impl From<SortingOrder> for dlogutil_sorting_order {
+ fn from(rust_enum: SortingOrder) -> Self {
+ match rust_enum {
+ SortingOrder::SentMono => dlogutil_sorting_order::DLOGUTIL_SORT_SENT_MONO,
+ SortingOrder::SentReal => dlogutil_sorting_order::DLOGUTIL_SORT_SENT_REAL,
+ SortingOrder::RecvMono => dlogutil_sorting_order::DLOGUTIL_SORT_RECV_MONO,
+ SortingOrder::RecvReal => dlogutil_sorting_order::DLOGUTIL_SORT_RECV_REAL,
+ SortingOrder::Default => dlogutil_sorting_order::DLOGUTIL_SORT_DEFAULT,
+ }
+ }
+}
+
+/// A struct containing libdlogutil initialisation configuration.
+pub struct DlogutilConfig {
+ config: *mut dlogutil_config,
+}
+
+// SAFETY: it is ok to use the this object from different threads if only one thread
+// is using the memory at the time.
+unsafe impl Send for DlogutilConfig {}
+
+// SAFETY: the operations that do not modify the memory at all are ok to be used from
+// different threads at once, and those who do are marked as &mut.
+unsafe impl Sync for DlogutilConfig {}
+
+/// A struct containing the state of a log handling request.
+pub struct DlogutilState {
+ state: *mut dlogutil_state,
+}
+
+// SAFETY: it is ok to use the this object from different threads if only one thread
+// is using the memory at the time.
+unsafe impl Send for DlogutilState {}
+
+// SAFETY: the operations that do not modify the memory at all are ok to be used from
+// different threads at once, and those who do are marked as &mut.
+unsafe impl Sync for DlogutilState {}
+
+/// A struct containing the metadata and contents for a single dlog entry.
+pub struct DlogutilEntry {
+ entry: *mut dlogutil_entry,
+}
+
+// SAFETY: it is ok to use the this object from different threads if only one thread
+// is using the memory at the time.
+unsafe impl Send for DlogutilEntry {}
+
+// SAFETY: the operations that do not modify the memory at all are ok to be used from
+// different threads at once, and those who do are marked as &mut.
+unsafe impl Sync for DlogutilEntry {}
+
+impl Drop for DlogutilConfig {
+ fn drop(&mut self) {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ unsafe {
+ dlogutil_config_destroy(self.config);
+ }
+ }
+}
+
+impl Drop for DlogutilState {
+ fn drop(&mut self) {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ unsafe {
+ dlogutil_state_destroy(self.state);
+ }
+ }
+}
+
+impl Drop for DlogutilEntry {
+ fn drop(&mut self) {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ unsafe {
+ dlogutil_entry_destroy(self.entry);
+ }
+ }
+}
+
+impl DlogutilConfig {
+ /// Creates a new [`DlogutilConfig`] struct to be filled with configuration.
+ ///
+ /// Useful for dumping the logs of a specific thread in a multithreaded process.
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(Self)` - A handle to the config struct.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::OutOfMemory`] - Out of memory.
+ pub fn create() -> Result<Self> {
+ // SAFETY: the function is safe by itself. It returns either a valid pointer,
+ // in which case we return Ok, or NULL, in which case we return Err, which
+ // means that if Self is created, the inner pointer is guaranteed to be correct.
+ let config = unsafe { dlogutil_config_create() };
+ if config.is_null() {
+ return Err(Error::from(ErrorKind::OutOfMemory));
+ }
+ Ok(DlogutilConfig { config })
+ }
+
+ /// Enables retrieving only those logs that are logged by the thread with
+ /// the given TID.
+ ///
+ /// Useful for dumping the logs of a specific thread in a multithreaded process.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ /// * [`std::io::ErrorKind::OutOfMemory`] - Out of memory.
+ pub fn filter_tid(&mut self, tid: i32) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_filter_tid(self.config, tid) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Enables retrieving only those logs that are logged by the process with
+ /// the given PID.
+ ///
+ /// Useful for dumping the logs of a specific process.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ /// * [`std::io::ErrorKind::OutOfMemory`] - Out of memory.
+ pub fn filter_pid(&mut self, pid: i32) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_filter_pid(self.config, pid) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Enables retrieving only those logs that match a given filter.
+ ///
+ /// # Arguments
+ ///
+ /// * `query` - The filter query. For syntax, see dlogutil's --help.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - Invalid syntax of the filterspec.
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ /// * [`std::io::ErrorKind::OutOfMemory`] - Out of memory.
+ pub fn filter_filterspec(&mut self, query: &str) -> Result<()> {
+ let query = format!("{}\0", query);
+ let query_c =
+ CStr::from_bytes_with_nul(query.as_bytes()).map_err(|_| ErrorKind::InvalidInput)?;
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // The string is defined in a verified way above.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_filter_filterspec(self.config, query_c.as_ptr()) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Disables log sorting for given log retrieval request.
+ ///
+ /// Logs are still received in some order that is usually largely sorted,
+ /// but if sorting is disabled logutil-side there may be cases where a log
+ /// with a later timestamp is in front of a log with an earlier one.
+ /// The use case here is performance, since the failure case above is rare.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ pub fn sorting_disable(&mut self) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_sorting_disable(self.config) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Enables log sorting for given log retrieval request.
+ ///
+ /// This is the default and generally makes sure that logs are in order,
+ /// but has a modest performance cost.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ pub fn sorting_enable(&mut self) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_sorting_enable(self.config) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Enables sorting, choosing the sort buffer size manually.
+ ///
+ /// The count parameter influences the quality of sorting, but also memory usage.
+ /// This version is a somewhat lower level version of [`sorting_enable`].
+ /// For more information on sorting quality, refer to the C library documentation.
+ ///
+ /// [`sorting_enable`]: DlogutilConfig::sorting_enable
+ ///
+ /// # Arguments
+ ///
+ /// * `entry_count` - How many logs to keep at a given time. At least 1.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - Zero size.
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ pub fn sorting_enable_with_size(&mut self, entry_count: u32) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_sorting_enable_with_size(self.config, entry_count) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Chooses a timestamp type by which returned logs are sorted by.
+ ///
+ /// If the chosen timestamp is missing in the logs, currently they will not
+ /// be sorted at all. This should, however, still become a reasonable order,
+ /// since logs are usually stored sorted by one of timestamps. If only some
+ /// logs are missing the chosen timestamp, ordering of the logs is undefined.
+ /// See [`buffer_get_default_ts_type`] for the default.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ pub fn order_set(&mut self, sort_by: SortingOrder) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_order_set(self.config, sort_by.into()) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Adds a buffer whence logs will be taken to a request.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ pub fn buffer_add(&mut self, buf: BufferId) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_buffer_add(self.config, buf.into()) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Set log retrieval mode to retrieving all the logs since the start of
+ /// the system without an end.
+ ///
+ /// This is similar to `dlogutil` in a default mode.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ pub fn mode_set_continuous(&mut self) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_mode_set_continuous(self.config) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Set log retrieval mode to retrieving all the logs since the call
+ /// without an end.
+ ///
+ /// This is similar to `dlogutil -m`.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ pub fn mode_set_monitor(&mut self) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_mode_set_monitor(self.config) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Set log retrieval mode to dumping all the logs since the start of the
+ /// system until the call (possibly a specified amount of the most recent
+ /// of them instead).
+ ///
+ /// This is similar to `dlogutil -d`. After dumping all the logs,
+ /// [`get_log`] will signal this by returning `TIZEN_ERROR_NO_DATA`.
+ ///
+ /// [`get_log`]: DlogutilState::get_log
+ ///
+ /// # Arguments
+ ///
+ /// * `entry_count` - Number of logs to be returned. It can be
+ /// `DLOGUTIL_MAX_DUMP_SIZE`, in which case all the logs will be dumped.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ pub fn mode_set_dump(&mut self, entry_count: u32) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_config_mode_set_dump(self.config, entry_count) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Set log retrieval mode to dumping compressed historical logs.
+ ///
+ /// This is similar to `cat /var/log/dlog/xyz`. After dumping all the logs,
+ /// [`get_log`] will signal this by returning `TIZEN_ERROR_NO_DATA`.
+ ///
+ /// [`get_log`]: DlogutilState::get_log
+ ///
+ /// # Arguments
+ ///
+ /// * `compress_buffer` - The name of the compression storage entry.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::OutOfMemory`] - Not enough memory. Parameters left unchanged.
+ /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL.
+ pub fn mode_set_compressed_memory_dump(&mut self, compress_buffer: &str) -> Result<()> {
+ let compress_buffer = format!("{}\0", compress_buffer);
+ let compress_buffer_c = CStr::from_bytes_with_nul(compress_buffer.as_bytes())
+ .map_err(|_| ErrorKind::InvalidInput)?;
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // The string is defined in a verified way above.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe {
+ dlogutil_config_mode_set_compressed_memory_dump(self.config, compress_buffer_c.as_ptr())
+ } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+}
+
+impl DlogutilState {
+ /// Finalizes the config into a state struct by connecting to buffers.
+ ///
+ /// An application having platform privilege level can read platform log
+ /// data by declaring <http://tizen.org/privilege/log>.
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(Self)` - A handle to the state struct.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::InvalidInput`] - No buffers selected.
+ /// * [`std::io::ErrorKind::Uncategorized`] - Unsupported buffer set (KMSG + non-KMSG).
+ /// * [`std::io::ErrorKind::Uncategorized`] - Unsupported backend (zero-copy).
+ /// * [`std::io::ErrorKind::Uncategorized`] - No buffers were opened (incl. due to null backend).
+ /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't read config file.
+ /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't contact log backend.
+ /// * [`std::io::ErrorKind::OutOfMemory`] - There's not enough memory.
+ pub fn create(config: DlogutilConfig) -> Result<Self> {
+ let mut state = DlogutilState { state: null_mut() };
+ // SAFETY: the config pointer is guaranteed to be correct by the way it's created.
+ // Otherwise, the function is safe by itself. It returns either a valid pointer,
+ // in which case we return Ok, or NULL, in which case we return Err, which
+ // means that if Self is created, the inner pointer is guaranteed to be correct.
+ match unsafe { dlogutil_config_connect(config.config, &mut state.state) } {
+ 0 => Ok(state),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Retrieves a single log according to a dump request.
+ ///
+ /// Returns a returned log as a [`DlogutilEntry`] struct.
+ ///
+ /// If the calling process doesn't have `CAP_SYSLOG` and is not in the
+ /// log group, you will only get some of the logs.
+ /// Also, you must set the mode (`DlogutilConfig::mode_set_*`).
+ ///
+ /// [`DlogutilEntry`]: DlogutilEntry
+ ///
+ /// # Arguments
+ ///
+ /// * `timeout` - How many miliseconds to wait for the log.
+ /// The actual runtime of the call can obviously be slightly longer than
+ /// this argument. 0 means don't wait, None means wait indefinitely.
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(DlogutilEntry)` - A returned log.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::Uncategorized`] - Timeout exceeded.
+ /// * [`std::io::ErrorKind::Uncategorized`] - In dump mode, no more logs remaining.
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::InvalidInput`] - State not in log-getting mode.
+ /// * [`std::io::ErrorKind::OutOfMemory`] - There's not enough memory.
+ /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't fulfill request.
+ pub fn get_log(&mut self, timeout: Option<Duration>) -> Result<DlogutilEntry> {
+ let mut entry_out = DlogutilEntry { entry: null_mut() };
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // The C function passes the ownership of the entry to the pointer iff the
+ // return value is nonzero, which is exactly the case in which we return
+ // the struct in an Ok. Since the function takes &mut, no other references can exist.
+ match unsafe {
+ dlogutil_get_log(
+ self.state,
+ timeout.map_or(-1, |t| t.as_millis() as i32),
+ &mut entry_out.entry,
+ )
+ } {
+ 0 => Ok(entry_out),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Irreversibly clears a log buffer from any logs inside.
+ ///
+ /// Either `CAP_SYSLOG` or being in the log group is required.
+ /// Also, you can't use one of the log-getting modes
+ /// (`DlogutilConfig::mode_set_*`).
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't fulfill request.
+ pub fn buffer_clear(&mut self, buffer: BufferId) -> Result<()> {
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Since the function takes &mut, no other references can exist.
+ match unsafe { dlogutil_buffer_clear(self.state, buffer.into()) } {
+ 0 => Ok(()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Gets the data storage capacity of a log buffer in bytes.
+ ///
+ /// Either `CAP_SYSLOG` or being in the log group is required.
+ /// Also, you can't use one of the log-getting modes
+ /// (`DlogutilConfig::mode_set_*`).
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(u32)` - The buffer's maximum capacity in bytes.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - State in log-getting mode.
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't fulfill request.
+ pub fn buffer_get_capacity(&mut self, buffer: BufferId) -> Result<u32> {
+ let mut capacity = 0;
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Note that it is not correct for this function to take a shared reference, as in case of
+ // the pipe backend, the function will write to a pipe and expect to read an answer
+ // immediately.
+ match unsafe { dlogutil_buffer_get_capacity(self.state, buffer.into(), &mut capacity) } {
+ 0 => Ok(capacity),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Gets the storage data usage of a log buffer in bytes.
+ ///
+ /// Either `CAP_SYSLOG` or being in the log group is required.
+ /// Also, you can't use one of the log-getting modes
+ /// (`DlogutilConfig::mode_set_*`).
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(u32)` - Buffer's current usage in bytes.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - State in log-getting mode.
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't fulfill request.
+ pub fn buffer_get_usage(&mut self, buffer: BufferId) -> Result<u32> {
+ let mut usage = 0;
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // Note that it is not correct for this function to take a shared reference, as in case of
+ // the pipe backend, the function will write to a pipe and expect to read an answer
+ // immediately.
+ match unsafe { dlogutil_buffer_get_usage(self.state, buffer.into(), &mut usage) } {
+ 0 => Ok(usage),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Gets the buffer aliasing (same storage) information.
+ ///
+ /// Sometimes, multiple buffers will be backed by a single log storage
+ /// (for example, by the same kernel device). In such cases, the storage
+ /// will only be opened once.
+ /// This function allows you to see whether this is the case.
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(BufferId)` - Buffer aliasing information.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ pub fn buffer_get_alias(&self, buffer: BufferId) -> Result<BufferId> {
+ let mut real_buffer = log_id_t::LOG_ID_MAIN;
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // This method only reads from the memory of the object, so it is ok for it to take
+ // a shared reference.
+ match unsafe { dlogutil_buffer_get_alias(self.state, buffer.into(), &mut real_buffer) } {
+ 0 => Ok(real_buffer.into()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+}
+
+impl DlogutilEntry {
+ /// Retrieves the timestamp of given type from the log entry.
+ ///
+ /// The information about timestamp availability can be retrieved using the
+ /// [`buffer_check_ts_type_available`] function.
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(Duration)` - Timestamp of the entry as a [`Duration`] struct.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// [`Duration`]: std::time::Duration
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - Invalid value of order.
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::Uncategorized`] - The timestamp is missing.
+ pub fn get_timestamp(&self, stamp_type: SortingOrder) -> Result<Duration> {
+ let mut ts = timespec {
+ tv_sec: 0,
+ tv_nsec: 0,
+ };
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // This method only reads from the memory of the object, so it is ok for it to take
+ // a shared reference.
+ match unsafe { dlogutil_entry_get_timestamp(self.entry, stamp_type.into(), &mut ts) } {
+ 0 => Ok(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32)),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Retrieves the TID (thread identificator) of the log sender.
+ ///
+ /// If [`LogIdKmsg`] is used as the buffer, this function will always return
+ /// `TIZEN_ERROR_NO_DATA`.
+ /// This is because the KMSG buffer contains no TID information.
+ ///
+ /// [`LogIdKmsg`]: BufferId::LogIdKmsg
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(i32)` - TID of the log sender.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::Uncategorized`] - TID is missing or not applicable.
+ pub fn get_tid(&self) -> Result<i32> {
+ let mut tid = 0;
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // This method only reads from the memory of the object, so it is ok for it to take
+ // a shared reference.
+ match unsafe { dlogutil_entry_get_tid(self.entry, &mut tid) } {
+ 0 => Ok(tid),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Retrieves the PID (process identificator) of the log sender.
+ ///
+ /// If [`LogIdKmsg`] is used as the buffer, this function will always return
+ /// `TIZEN_ERROR_NO_DATA`.
+ /// This is because the KMSG buffer contains no PID information.
+ ///
+ /// [`LogIdKmsg`]: BufferId::LogIdKmsg
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(i32)` - PID of the log sender.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::Uncategorized`] - PID is missing or not applicable.
+ pub fn get_pid(&self) -> Result<i32> {
+ let mut pid = 0;
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // This method only reads from the memory of the object, so it is ok for it to take
+ // a shared reference.
+ match unsafe { dlogutil_entry_get_pid(self.entry, &mut pid) } {
+ 0 => Ok(pid),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Retrieves the priority level metadata of the log entry.
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(Priority)` - Log priority.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::Uncategorized`] - The priority is missing.
+ pub fn get_priority(&self) -> Result<Priority> {
+ let mut prio = log_priority::DLOG_UNKNOWN;
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // This method only reads from the memory of the object, so it is ok for it to take
+ // a shared reference.
+ match unsafe { dlogutil_entry_get_priority(self.entry, &mut prio) } {
+ 0 => Ok(prio.into()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+
+ /// Retrieves the tag (arbitrary label) of the log entry.
+ ///
+ /// In some rare cases the entry may be malformed and the tag
+ /// may turn out to be unavailable.
+ ///
+ /// In such cases, an empty string may be returned instead.
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(&str)` - Log tag.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::Uncategorized`] - The tag is missing.
+ pub fn get_tag(&self) -> Result<&str> {
+ let mut tag_ptr = null();
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // The function returns either NULL, in which case we return Err,
+ // or a string with same lifetime as self.entry, in which case we can safely
+ // call CStr::from_ptr and return it in an Ok.
+ // This method only reads from the memory of the object, so it is ok for it to take
+ // a shared reference.
+ unsafe {
+ match dlogutil_entry_get_tag(self.entry, &mut tag_ptr) {
+ 0 => Ok(CStr::from_ptr(tag_ptr).to_str().unwrap()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+ }
+
+ /// Retrieves the message (without any metadata) of the log entry.
+ ///
+ /// In some rare cases the entry may be malformed and the message
+ /// may turn out to be unavailable.
+ ///
+ /// In such cases, an empty string may be returned instead.
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(&str)` - Log message.
+ /// * `Err(std::io::Error)` - An error.
+ ///
+ /// # Errors
+ ///
+ /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+ /// * [`std::io::ErrorKind::Uncategorized`] - The message is missing.
+ pub fn get_message(&self) -> Result<&str> {
+ let mut msg_ptr = null();
+ // SAFETY: the pointer is guaranteed to be correct by the way it's created.
+ // The function returns either NULL, in which case we return Err,
+ // or a string with same lifetime as self.entry, in which case we can safely
+ // call CStr::from_ptr and return it in an Ok.
+ // This method only reads from the memory of the object, so it is ok for it to take
+ // a shared reference.
+ unsafe {
+ match dlogutil_entry_get_message(self.entry, &mut msg_ptr) {
+ 0 => Ok(CStr::from_ptr(msg_ptr).to_str().unwrap()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+ }
+}
+
+/// Gets the human-readable, constant name of a buffer.
+///
+/// # Returns
+///
+/// * `Ok(&str)` - The name of the passed buffer.
+/// * `Err(std::io::Error)` - An error.
+///
+/// # Errors
+///
+/// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+pub fn buffer_get_name(buffer: BufferId) -> Result<&'static str> {
+ let mut name_ptr = null();
+ // SAFETY: the function is safe by itself provided the target is a valid pointer.
+ // The function returns either NULL, in which case we return Err,
+ // or a string with static lifetime, in which case we can safely call
+ // CStr::from_ptr and return it in an Ok.
+ unsafe {
+ match dlogutil_buffer_get_name(buffer.into(), &mut name_ptr) {
+ 0 => Ok(CStr::from_ptr(name_ptr).to_str().unwrap()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+ }
+}
+
+/// Gets the default sorting timestamp type of a buffer.
+///
+/// This is the timestamp type that will be used for sorting by default.
+/// We assume that it is always available and the input is sorted by it.
+///
+/// # Returns
+///
+/// * `Ok(SortingOrder)` - The default timestamp type of the passed buffer.
+/// * `Err(std::io::Error)` - An error.
+///
+/// # Errors
+///
+/// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+/// * [`std::io::ErrorKind::Uncategorized`] - Couldn't read config file.
+pub fn buffer_get_default_ts_type(buffer: BufferId) -> Result<SortingOrder> {
+ let mut typ = dlogutil_sorting_order::DLOGUTIL_SORT_SENT_MONO;
+ // SAFETY: the function is safe by itself provided the target is a valid pointer.
+ match unsafe { dlogutil_buffer_get_default_ts_type(buffer.into(), &mut typ) } {
+ 0 => Ok(typ.into()),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+}
+
+/// Checks if a buffer contains timestamps of a given type.
+///
+/// If false is returned, the timestamp may still be available in some of the logs.
+/// However, if true is returned, the timestamp will always be available.
+/// You can check the timestamp availability per log using the [`get_timestamp`]
+/// function.
+///
+/// [`get_timestamp`]: DlogutilEntry::get_timestamp
+///
+/// # Returns
+///
+/// * `Ok(bool)` - Whether the given timestamp type is guaranteed to be available.
+/// * `Err(std::io::Error)` - An error.
+///
+/// # Errors
+///
+/// * [`std::io::ErrorKind::InvalidInput`] - More than one buffer.
+/// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL.
+/// * [`std::io::ErrorKind::Uncategorized`] - Couldn't read config file.
+pub fn buffer_check_ts_type_available(buffer: BufferId, stamp_type: SortingOrder) -> Result<bool> {
+ let mut available = false;
+ // SAFETY: the function is safe by itself provided the target is a valid pointer.
+ match unsafe {
+ dlogutil_buffer_check_ts_type_available(buffer.into(), stamp_type.into(), &mut available)
+ } {
+ 0 => Ok(available),
+ e => Err(Error::from_raw_os_error(-e)),
+ }
+}
--- /dev/null
+<manifest>
+ <request>
+ <domain name="_"/>
+ </request>
+</manifest>
--- /dev/null
+%define _rpm_strip_option --keep-section=.rustc
+
+%global rustc_edition 2021
+
+Name: rust-dlog
+Summary: rust-dlog
+Version: 0.1
+Release: 1
+Group: System/Management
+License: MIT
+Source0: %{name}-%{version}.tar.gz
+Source1: %{name}.manifest
+BuildRequires: rust
+BuildRequires: rust-libc
+BuildRequires: rust-log
+BuildRequires: libdlog-devel
+
+%description
+dlog rust bindings
+
+%package -n rust-libdlog
+Summary: Logging service dlog API
+%description -n rust-libdlog
+
+%package -n rust-libdlogutil
+Summary: Logging service dlog API
+%description -n rust-libdlogutil
+
+%package tests
+Summary: Rust-dlog tests
+Requires: rust-devel
+%description tests
+
+%package doc
+Summary: Rust-dlog documentation
+%description doc
+
+%prep
+%setup -q
+cp %{SOURCE1} .
+
+%build
+build_library () {
+ %{rustc_std_build} \
+ --crate-type=dylib \
+ $@
+ clippy-driver \
+ -C prefer-dynamic \
+ --extern std=${__rust_std} \
+ -L%{_rust_libdir} \
+ -L%{_rust_dylibdir} \
+ --crate-type=dylib \
+ --out-dir=/tmp \
+ -Dclippy::undocumented_unsafe_blocks \
+ $@
+ rustdoc \
+ -C prefer-dynamic \
+ --extern std=${__rust_std} \
+ -L%{_rust_libdir} \
+ -L%{_rust_dylibdir} \
+ --crate-type=dylib \
+ -o rustdoc_output \
+ $@
+}
+
+build_library \
+ --crate-name=dlog_sys \
+ --edition=%{rustc_edition} \
+ --cfg='feature="std"' \
+ ./dlog-sys/src/lib.rs
+
+build_library \
+ --crate-name=dlog \
+ --edition=%{rustc_edition} \
+ --cfg='feature="std"' \
+ %rust_dylib_extern log \
+ --extern dlog_sys=libdlog_sys.so \
+ ./dlog/src/lib.rs
+
+build_library \
+ --crate-name=dlogutil_sys \
+ --edition=%{rustc_edition} \
+ --cfg='feature="std"' \
+ %rust_dylib_extern libc \
+ --extern dlog_sys=libdlog_sys.so \
+ ./dlogutil-sys/src/lib.rs
+
+build_library \
+ --crate-name=dlogutil \
+ --edition=%{rustc_edition} \
+ --cfg='feature="std"' \
+ -L. \
+ %rust_dylib_extern libc \
+ --extern dlog_sys=libdlog_sys.so \
+ --extern dlog=libdlog.so \
+ --extern dlogutil_sys=libdlogutil_sys.so \
+ ./dlogutil/src/lib.rs
+
+build_library \
+ --crate-name=dlog_redirect_stdout_sys \
+ --edition=%{rustc_edition} \
+ --cfg='feature="std"' \
+ ./dlog-redirect-stdout-sys/src/lib.rs
+
+build_library \
+ --crate-name=dlog_redirect_stdout \
+ --edition=%{rustc_edition} \
+ --cfg='feature="std"' \
+ -L. \
+ --extern dlog=libdlog.so \
+ --extern dlog_redirect_stdout_sys=libdlog_redirect_stdout_sys.so \
+ ./dlog-redirect-stdout/src/lib.rs
+
+%{rustc_std_build} --crate-type=bin \
+ --crate-name=dlogsend_rust \
+ --edition=%{rustc_edition} \
+ --cfg='feature="std"' \
+ -L. \
+ %rust_dylib_extern log \
+ --extern dlog=libdlog.so \
+ --extern dlogutil=libdlogutil.so \
+ -Clink-arg=%{_libdir}/libdlog.so \
+ ./tests/src/dlogsend.rs
+
+TESTFLAGS="--edition=%{rustc_edition} \
+ --cfg feature=\"std\" \
+ -L. \
+ %rust_dylib_extern libc \
+ %rust_dylib_extern log \
+ --extern dlog=libdlog.so \
+ --extern dlogutil=libdlogutil.so \
+ --extern dlog_redirect_stdout=libdlog_redirect_stdout.so \
+ -Clink-arg=%{_libdir}/libdlog.so \
+ -Clink-arg=%{_libdir}/libdlogutil.so \
+ -Clink-arg=%{_libdir}/libdlog_redirect_stdout.so \
+ "
+
+build_test () {
+ %{rustc_std_build} \
+ --crate-type=bin \
+ $TESTFLAGS \
+ $@
+ clippy-driver \
+ -C prefer-dynamic \
+ --extern std=${__rust_std} \
+ -L%{_rust_libdir} \
+ -L%{_rust_dylibdir} \
+ --crate-type=bin \
+ --out-dir=/tmp \
+ -Dclippy::undocumented_unsafe_blocks \
+ $TESTFLAGS \
+ $@
+}
+
+build_test \
+ --crate-name=test_dlog_print \
+ ./tests/src/test_dlog_print.rs
+
+build_test \
+ --crate-name=test_dlog_critical_print \
+ ./tests/src/test_dlog_critical_print.rs
+
+build_test \
+ --crate-name=test_dlog_custom_tag \
+ ./tests/src/test_dlog_custom_tag.rs
+
+build_test \
+ --crate-name=test_dlog_redirect \
+ ./tests/src/test_dlog_redirect.rs
+
+%install
+mkdir -p %{buildroot}%{_bindir}
+
+install -d -m 0755 %{buildroot}%{_rust_dylibdir}
+install -m 0644 libdlog_sys.so %{buildroot}%{_rust_dylibdir}/libdlog_sys.so
+install -m 0644 libdlog.so %{buildroot}%{_rust_dylibdir}/libdlog.so
+install -m 0644 libdlogutil_sys.so %{buildroot}%{_rust_dylibdir}/libdlogutil_sys.so
+install -m 0644 libdlogutil.so %{buildroot}%{_rust_dylibdir}/libdlogutil.so
+install -m 0644 libdlog_redirect_stdout_sys.so %{buildroot}%{_rust_dylibdir}/libdlog_redirect_stdout_sys.so
+install -m 0644 libdlog_redirect_stdout.so %{buildroot}%{_rust_dylibdir}/libdlog_redirect_stdout.so
+install -m 0755 dlogsend_rust %{buildroot}%{_bindir}/dlogsend_rust
+
+for file in test_*; do
+ install -m 0755 "$file" %{buildroot}%{_bindir}/"$file"
+done
+
+mkdir -p %{buildroot}/usr/share/doc/rust-dlog
+cp -r rustdoc_output/* %{buildroot}/usr/share/doc/rust-dlog
+
+%files
+%manifest %{name}.manifest
+
+%files -n rust-libdlog
+%manifest %{name}.manifest
+%{_rust_dylibdir}/libdlog_sys.so
+%{_rust_dylibdir}/libdlog.so
+%{_rust_dylibdir}/libdlog_redirect_stdout_sys.so
+%{_rust_dylibdir}/libdlog_redirect_stdout.so
+
+%files -n rust-libdlogutil
+%manifest %{name}.manifest
+%{_rust_dylibdir}/libdlogutil_sys.so
+%{_rust_dylibdir}/libdlogutil.so
+
+%files tests
+%manifest %{name}.manifest
+%attr(750,log,log) %{_bindir}/dlogsend_rust
+%attr(750,log,log) %{_bindir}/test_*
+
+%post tests
+echo "This package contains binaries: dlogsend_rust, which should be treated as an example,"
+echo "and integration tests that are run in dlog_test with other dlog tests."
+
+%files doc
+%manifest %{name}.manifest
+/usr/share/doc/rust-dlog
--- /dev/null
+pub mod logutil;
+
+use logutil::{log_message, parse_options};
+use std::io;
+use std::io::{BufRead, Result};
+
+fn main() -> Result<()> {
+ let opts = parse_options()?;
+
+ dlog::init(opts.is_critical)?;
+
+ if opts.msg.is_empty() {
+ let stdin = io::stdin();
+ for line in stdin.lock().lines() {
+ log_message(opts.prio, &line.unwrap());
+ }
+ } else {
+ log_message(opts.prio, &opts.msg);
+ }
+
+ Ok(())
+}
--- /dev/null
+use dlog::{BufferId, Priority};
+use dlogutil::{DlogutilConfig, DlogutilState};
+use log::{debug, error, info, trace, warn};
+use std::env;
+use std::io::{Error, Result};
+
+pub struct Options {
+ pub prio: Priority,
+ pub is_critical: bool,
+ pub msg: String,
+}
+
+fn str_to_prio(prog: &str, s: &str) -> Result<Priority> {
+ match s {
+ "1" | "*" => Ok(Priority::Default),
+ "2" | "v" => Ok(Priority::Verbose),
+ "3" | "d" => Ok(Priority::Debug),
+ "4" | "i" => Ok(Priority::Info),
+ "5" | "w" => Ok(Priority::Warn),
+ "6" | "e" => Ok(Priority::Error),
+ "7" | "f" => Ok(Priority::Fatal),
+ _ => {
+ show_help(prog);
+ Err(Error::from_raw_os_error(22))
+ }
+ }
+}
+
+pub fn parse_options() -> Result<Options> {
+ let args: Vec<String> = env::args().collect();
+ let mut opts = Options {
+ prio: Priority::Info,
+ is_critical: false,
+ msg: String::new(),
+ };
+
+ let mut i = 1;
+ while i < args.len() {
+ match args[i].as_str() {
+ "-p" => {
+ if i + 1 < args.len() {
+ opts.prio = str_to_prio(&args[0], &args[i + 1])?;
+ i += 1;
+ }
+ }
+ "-k" => {
+ opts.is_critical = true;
+ }
+ "-h" => {
+ show_help(&args[0]);
+ return Err(Error::from_raw_os_error(22));
+ }
+ _ => {
+ opts.msg = args[i..].join(" ");
+ break;
+ }
+ }
+ i += 1;
+ }
+
+ Ok(opts)
+}
+
+fn show_help(prog: &str) {
+ println!(
+ "\
+Usage: {prog} [-p priority] [-k] [message]
+\t-p priority \tone of {{Debug, Info, Warning, Error, Verbose, Fatal}}
+\t \tfirst letter is enough; defaults to Info
+\t-k \tmake this a 'critical' log
+\t-h \tshow this help
+\tmessage \tmessage for logging; if not specified, each line from standard input
+\t \tis logged separately until EOF"
+ );
+}
+
+pub fn log_message(prio: Priority, msg: &str) {
+ match prio {
+ Priority::Default => info!("{}", msg),
+ Priority::Verbose => trace!("{}", msg),
+ Priority::Debug => debug!("{}", msg),
+ Priority::Info => info!("{}", msg),
+ Priority::Warn => warn!("{}", msg),
+ Priority::Error => error!("{}", msg),
+ Priority::Fatal => error!("{}", msg),
+ _ => panic!("impossible case"),
+ }
+}
+
+pub fn clear_buffer(buffer: BufferId) -> Result<()> {
+ let mut config = DlogutilConfig::create()?;
+ config.buffer_add(buffer)?;
+ let mut state = DlogutilState::create(config)?;
+ state.buffer_clear(buffer)?;
+ Ok(())
+}
--- /dev/null
+pub mod logutil;
+
+use dlog::{BufferId, Priority};
+use dlogutil::{DlogutilConfig, DlogutilState, SortingOrder};
+use log::{debug, error, info, trace, warn};
+use logutil::clear_buffer;
+use std::io::Result;
+use std::time::Duration;
+
+fn test_dlog_critical_print() -> Result<()> {
+ let mut config = DlogutilConfig::create()?;
+ config.filter_filterspec("test_dlog_critical_print")?;
+ config.buffer_add(BufferId::LogIdMain)?;
+ config.mode_set_continuous()?;
+ config.order_set(SortingOrder::Default)?;
+ config.sorting_enable()?;
+ let mut state = DlogutilState::create(config)?;
+
+ dlog::init(true)?;
+
+ trace!("test_verbose_crit");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Verbose);
+ assert_eq!(entry.get_tag()?, "test_dlog_critical_print");
+ assert!(entry.get_message()?.contains("test_verbose_crit"));
+
+ debug!("test_debug_crit");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Debug);
+ assert_eq!(entry.get_tag()?, "test_dlog_critical_print");
+ assert!(entry.get_message()?.contains("test_debug_crit"));
+
+ info!("test_info_crit");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Info);
+ assert_eq!(entry.get_tag()?, "test_dlog_critical_print");
+ assert!(entry.get_message()?.contains("test_info_crit"));
+
+ warn!("test_warn_crit");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Warn);
+ assert_eq!(entry.get_tag()?, "test_dlog_critical_print");
+ assert!(entry.get_message()?.contains("test_warn_crit"));
+
+ error!("test_error_crit");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Error);
+ assert_eq!(entry.get_tag()?, "test_dlog_critical_print");
+ assert!(entry.get_message()?.contains("test_error_crit"));
+
+ clear_buffer(BufferId::LogIdMain)?;
+
+ Ok(())
+}
+
+fn main() -> Result<()> {
+ test_dlog_critical_print()
+}
--- /dev/null
+pub mod logutil;
+
+use dlog::BufferId;
+use dlogutil::{DlogutilConfig, DlogutilState, SortingOrder};
+use logutil::clear_buffer;
+use std::io::Result;
+use std::time::Duration;
+
+fn test_custom_tag() -> Result<()> {
+ let mut config = DlogutilConfig::create()?;
+ config.filter_filterspec("test_dlog_custom_tag")?;
+ config.filter_filterspec("custom_tag")?;
+ config.buffer_add(BufferId::LogIdApps)?;
+ config.mode_set_continuous()?;
+ config.order_set(SortingOrder::Default)?;
+ config.sorting_enable()?;
+ let mut state = DlogutilState::create(config)?;
+
+ dlog::init(false)?;
+
+ log::info!("log with the default tag");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_tag()?, "test_dlog_custom_tag");
+
+ log::info!(target: "custom_tag", "log with a custom tag");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_tag()?, "custom_tag");
+
+ clear_buffer(BufferId::LogIdApps)?;
+
+ Ok(())
+}
+
+fn main() -> Result<()> {
+ test_custom_tag()
+}
--- /dev/null
+pub mod logutil;
+
+use dlog::{BufferId, Priority};
+use dlogutil::{DlogutilConfig, DlogutilState, SortingOrder};
+use log::{debug, error, info, trace, warn};
+use logutil::clear_buffer;
+use std::io::Result;
+use std::time::Duration;
+
+fn test_dlog_print() -> Result<()> {
+ let mut config = DlogutilConfig::create()?;
+ config.filter_filterspec("test_dlog_print")?;
+ config.buffer_add(BufferId::LogIdApps)?;
+ config.mode_set_continuous()?;
+ config.order_set(SortingOrder::Default)?;
+ config.sorting_enable()?;
+ let mut state = DlogutilState::create(config)?;
+
+ dlog::init(false)?;
+
+ trace!("test_verbose");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Verbose);
+ assert_eq!(entry.get_tag()?, "test_dlog_print");
+ assert!(entry.get_message()?.contains("test_verbose"));
+
+ debug!("test_debug");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Debug);
+ assert_eq!(entry.get_tag()?, "test_dlog_print");
+ assert!(entry.get_message()?.contains("test_debug"));
+
+ info!("test_info");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Info);
+ assert_eq!(entry.get_tag()?, "test_dlog_print");
+ assert!(entry.get_message()?.contains("test_info"));
+
+ warn!("test_warn");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Warn);
+ assert_eq!(entry.get_tag()?, "test_dlog_print");
+ assert!(entry.get_message()?.contains("test_warn"));
+
+ error!("test_error");
+ let entry = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry.get_priority()?, Priority::Error);
+ assert_eq!(entry.get_tag()?, "test_dlog_print");
+ assert!(entry.get_message()?.contains("test_error"));
+
+ clear_buffer(BufferId::LogIdApps)?;
+
+ Ok(())
+}
+
+fn main() -> Result<()> {
+ test_dlog_print()
+}
--- /dev/null
+pub mod logutil;
+
+use dlog::{BufferId, Priority};
+use dlog_redirect_stdout::{redirect_stderr, redirect_stdout};
+use dlogutil::{DlogutilConfig, DlogutilState, SortingOrder};
+use logutil::clear_buffer;
+use std::io::Result;
+use std::time::Duration;
+
+fn test_dlog_redirect() -> Result<()> {
+ let mut config = DlogutilConfig::create()?;
+ config.filter_filterspec("dlog_test_redirect")?;
+ config.buffer_add(BufferId::LogIdMain)?;
+ config.mode_set_continuous()?;
+ config.order_set(SortingOrder::Default)?;
+ config.sorting_enable()?;
+ let mut state = DlogutilState::create(config)?;
+
+ redirect_stdout(BufferId::LogIdMain, "dlog_test_redirect", Priority::Info)?;
+ redirect_stderr(BufferId::LogIdMain, "dlog_test_redirect", Priority::Error)?;
+
+ println!("test_redirect_stdout");
+ let entry_out = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry_out.get_priority()?, Priority::Info);
+ assert_eq!(entry_out.get_tag()?, "dlog_test_redirect");
+ assert_eq!(entry_out.get_message()?, "test_redirect_stdout");
+
+ eprintln!("test_redirect_stderr");
+ let entry_err = state.get_log(Some(Duration::from_millis(1_000)))?;
+ assert_eq!(entry_err.get_priority()?, Priority::Error);
+ assert_eq!(entry_err.get_tag()?, "dlog_test_redirect");
+ assert_eq!(entry_err.get_message()?, "test_redirect_stderr");
+
+ clear_buffer(BufferId::LogIdMain)?;
+
+ Ok(())
+}
+
+fn main() -> Result<()> {
+ test_dlog_redirect()
+}