Initial implementation 67/315967/2 tizen
authorGrzegorz Nowakowski <G.nowakowski@samsung.com>
Fri, 6 Dec 2024 17:26:37 +0000 (18:26 +0100)
committerGrzegorz Nowakowski <g.nowakowski@samsung.com>
Mon, 17 Feb 2025 15:35:26 +0000 (16:35 +0100)
Change-Id: I222f3d4f781a234c29288b3178f74010e719c79e

14 files changed:
dlog-redirect-stdout-sys/src/lib.rs [new file with mode: 0644]
dlog-redirect-stdout/src/lib.rs [new file with mode: 0644]
dlog-sys/src/lib.rs [new file with mode: 0644]
dlog/src/lib.rs [new file with mode: 0644]
dlogutil-sys/src/lib.rs [new file with mode: 0644]
dlogutil/src/lib.rs [new file with mode: 0644]
packaging/rust-dlog.manifest [new file with mode: 0644]
packaging/rust-dlog.spec [new file with mode: 0644]
tests/src/dlogsend.rs [new file with mode: 0644]
tests/src/logutil.rs [new file with mode: 0644]
tests/src/test_dlog_critical_print.rs [new file with mode: 0644]
tests/src/test_dlog_custom_tag.rs [new file with mode: 0644]
tests/src/test_dlog_print.rs [new file with mode: 0644]
tests/src/test_dlog_redirect.rs [new file with mode: 0644]

diff --git a/dlog-redirect-stdout-sys/src/lib.rs b/dlog-redirect-stdout-sys/src/lib.rs
new file mode 100644 (file)
index 0000000..c6ac8f6
--- /dev/null
@@ -0,0 +1,6 @@
+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;
+}
diff --git a/dlog-redirect-stdout/src/lib.rs b/dlog-redirect-stdout/src/lib.rs
new file mode 100644 (file)
index 0000000..92cbebc
--- /dev/null
@@ -0,0 +1,103 @@
+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) }
+}
diff --git a/dlog-sys/src/lib.rs b/dlog-sys/src/lib.rs
new file mode 100644 (file)
index 0000000..8e5352c
--- /dev/null
@@ -0,0 +1,41 @@
+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;
+}
diff --git a/dlog/src/lib.rs b/dlog/src/lib.rs
new file mode 100644 (file)
index 0000000..c2d7b35
--- /dev/null
@@ -0,0 +1,228 @@
+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)),
+    }
+}
diff --git a/dlogutil-sys/src/lib.rs b/dlogutil-sys/src/lib.rs
new file mode 100644 (file)
index 0000000..5c8868d
--- /dev/null
@@ -0,0 +1,114 @@
+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;
+}
diff --git a/dlogutil/src/lib.rs b/dlogutil/src/lib.rs
new file mode 100644 (file)
index 0000000..16c15e7
--- /dev/null
@@ -0,0 +1,823 @@
+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)),
+    }
+}
diff --git a/packaging/rust-dlog.manifest b/packaging/rust-dlog.manifest
new file mode 100644 (file)
index 0000000..1a63101
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+   <request>
+     <domain name="_"/>
+   </request>
+</manifest>
diff --git a/packaging/rust-dlog.spec b/packaging/rust-dlog.spec
new file mode 100644 (file)
index 0000000..64f1f41
--- /dev/null
@@ -0,0 +1,216 @@
+%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
diff --git a/tests/src/dlogsend.rs b/tests/src/dlogsend.rs
new file mode 100644 (file)
index 0000000..8e05b0a
--- /dev/null
@@ -0,0 +1,22 @@
+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(())
+}
diff --git a/tests/src/logutil.rs b/tests/src/logutil.rs
new file mode 100644 (file)
index 0000000..2ed9fa1
--- /dev/null
@@ -0,0 +1,96 @@
+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(())
+}
diff --git a/tests/src/test_dlog_critical_print.rs b/tests/src/test_dlog_critical_print.rs
new file mode 100644 (file)
index 0000000..58c910d
--- /dev/null
@@ -0,0 +1,58 @@
+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()
+}
diff --git a/tests/src/test_dlog_custom_tag.rs b/tests/src/test_dlog_custom_tag.rs
new file mode 100644 (file)
index 0000000..8417fa3
--- /dev/null
@@ -0,0 +1,36 @@
+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()
+}
diff --git a/tests/src/test_dlog_print.rs b/tests/src/test_dlog_print.rs
new file mode 100644 (file)
index 0000000..5463990
--- /dev/null
@@ -0,0 +1,58 @@
+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()
+}
diff --git a/tests/src/test_dlog_redirect.rs b/tests/src/test_dlog_redirect.rs
new file mode 100644 (file)
index 0000000..01c76de
--- /dev/null
@@ -0,0 +1,41 @@
+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()
+}