diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3753264..2da0f2f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,6 +9,7 @@ jobs: strategy: matrix: target: [x86_64-apple-darwin, aarch64-apple-darwin, aarch64-apple-ios] + features-flags: ["", "-Flog", "-Fcategories"] steps: - name: Checkout sources uses: actions/checkout@v2 @@ -25,7 +26,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: check - args: --target ${{ matrix.target }} + args: --target ${{ matrix.target }} ${{ matrix.feature-flags }} test: name: Test Suite diff --git a/Cargo.toml b/Cargo.toml index 56742b6..176e942 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,14 +11,17 @@ keywords = ["log", "logging", "macos", "apple"] categories = ["development-tools::debugging"] [features] -default = ["logger"] -# Enables support for the `log` crate. -logger = ["dashmap", "log"] +default = ["categories"] + +# Enables support for the log crate. +log = ["dep:log"] + +# Supports creating a new log for each category. This introduces a Mutex. +categories = ["log"] [dependencies] log = { version = "0.4.14", default-features = false, features = ["std"], optional = true } -dashmap = { version = "5.1.0", optional = true } [build-dependencies] cc = "1.0.73" diff --git a/README.md b/README.md index db4f4d6..36a6872 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,13 @@ fn main() { } ``` +## Features + +* `log` enables [log](https://docs.rs/log/latest/log/) crate support via + `OsLogger`. +* `categories` enables dynamically creating a log for each category. This + introduces a mutex which slows logging down. + ## Limitations Most of Apple's logging related functions are macros that enable some @@ -49,3 +56,6 @@ By wrapping the macros for use from Rust, we lose those benefits. Attempting to work around this would involve digging in to opaque types, which would be an automatic or eventual rejection from the App store. + +We also lose performance by having to convert log messages to C strings, even if +there's no observer of in-memory log streams. diff --git a/src/level.rs b/src/level.rs new file mode 100644 index 0000000..8f5828f --- /dev/null +++ b/src/level.rs @@ -0,0 +1,12 @@ +use crate::sys::{ + OS_LOG_TYPE_DEBUG, OS_LOG_TYPE_DEFAULT, OS_LOG_TYPE_ERROR, OS_LOG_TYPE_FAULT, OS_LOG_TYPE_INFO, +}; + +#[repr(u8)] +pub enum Level { + Debug = OS_LOG_TYPE_DEBUG, + Info = OS_LOG_TYPE_INFO, + Default = OS_LOG_TYPE_DEFAULT, + Error = OS_LOG_TYPE_ERROR, + Fault = OS_LOG_TYPE_FAULT, +} diff --git a/src/lib.rs b/src/lib.rs index 6a4ef4d..36ddd15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,188 +1,13 @@ +mod level; +mod minimal; +mod os_log_string; mod sys; -#[cfg(feature = "logger")] -mod logger; +#[cfg(feature = "log")] +mod log; -#[cfg(feature = "logger")] -pub use logger::OsLogger; +#[cfg(feature = "log")] +pub use log::OsLogger; -use crate::sys::*; -use std::ffi::{c_void, CString}; - -#[inline] -fn to_cstr(message: &str) -> CString { - let fixed = message.replace('\0', "(null)"); - CString::new(fixed).unwrap() -} - -#[repr(u8)] -pub enum Level { - Debug = OS_LOG_TYPE_DEBUG, - Info = OS_LOG_TYPE_INFO, - Default = OS_LOG_TYPE_DEFAULT, - Error = OS_LOG_TYPE_ERROR, - Fault = OS_LOG_TYPE_FAULT, -} - -#[cfg(feature = "logger")] -impl From for Level { - fn from(other: log::Level) -> Self { - match other { - log::Level::Trace => Self::Debug, - log::Level::Debug => Self::Info, - log::Level::Info => Self::Default, - log::Level::Warn => Self::Error, - log::Level::Error => Self::Fault, - } - } -} - -pub struct OsLog { - inner: os_log_t, -} - -unsafe impl Send for OsLog {} -unsafe impl Sync for OsLog {} - -impl Drop for OsLog { - fn drop(&mut self) { - unsafe { - if self.inner != wrapped_get_default_log() { - os_release(self.inner as *mut c_void); - } - } - } -} - -impl OsLog { - #[inline] - pub fn new(subsystem: &str, category: &str) -> Self { - let subsystem = to_cstr(subsystem); - let category = to_cstr(category); - - let inner = unsafe { os_log_create(subsystem.as_ptr(), category.as_ptr()) }; - - assert!(!inner.is_null(), "Unexpected null value from os_log_create"); - - Self { inner } - } - - #[inline] - pub fn global() -> Self { - let inner = unsafe { wrapped_get_default_log() }; - - assert!(!inner.is_null(), "Unexpected null value for OS_DEFAULT_LOG"); - - Self { inner } - } - - #[inline] - pub fn with_level(&self, level: Level, message: &str) { - let message = to_cstr(message); - unsafe { wrapped_os_log_with_type(self.inner, level as u8, message.as_ptr()) } - } - - #[inline] - pub fn debug(&self, message: &str) { - let message = to_cstr(message); - unsafe { wrapped_os_log_debug(self.inner, message.as_ptr()) } - } - - #[inline] - pub fn info(&self, message: &str) { - let message = to_cstr(message); - unsafe { wrapped_os_log_info(self.inner, message.as_ptr()) } - } - - #[inline] - pub fn default(&self, message: &str) { - let message = to_cstr(message); - unsafe { wrapped_os_log_default(self.inner, message.as_ptr()) } - } - - #[inline] - pub fn error(&self, message: &str) { - let message = to_cstr(message); - unsafe { wrapped_os_log_error(self.inner, message.as_ptr()) } - } - - #[inline] - pub fn fault(&self, message: &str) { - let message = to_cstr(message); - unsafe { wrapped_os_log_fault(self.inner, message.as_ptr()) } - } - - #[inline] - pub fn level_is_enabled(&self, level: Level) -> bool { - unsafe { os_log_type_enabled(self.inner, level as u8) } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_subsystem_interior_null() { - let log = OsLog::new("com.example.oslog\0test", "category"); - log.with_level(Level::Debug, "Hi"); - } - - #[test] - fn test_category_interior_null() { - let log = OsLog::new("com.example.oslog", "category\0test"); - log.with_level(Level::Debug, "Hi"); - } - - #[test] - fn test_message_interior_null() { - let log = OsLog::new("com.example.oslog", "category"); - log.with_level(Level::Debug, "Hi\0test"); - } - - #[test] - fn test_message_emoji() { - let log = OsLog::new("com.example.oslog", "category"); - log.with_level(Level::Debug, "\u{1F601}"); - } - - #[test] - fn test_global_log_with_level() { - let log = OsLog::global(); - log.with_level(Level::Debug, "Debug"); - log.with_level(Level::Info, "Info"); - log.with_level(Level::Default, "Default"); - log.with_level(Level::Error, "Error"); - log.with_level(Level::Fault, "Fault"); - } - - #[test] - fn test_global_log() { - let log = OsLog::global(); - log.debug("Debug"); - log.info("Info"); - log.default("Default"); - log.error("Error"); - log.fault("Fault"); - } - - #[test] - fn test_custom_log_with_level() { - let log = OsLog::new("com.example.oslog", "testing"); - log.with_level(Level::Debug, "Debug"); - log.with_level(Level::Info, "Info"); - log.with_level(Level::Default, "Default"); - log.with_level(Level::Error, "Error"); - log.with_level(Level::Fault, "Fault"); - } - - #[test] - fn test_custom_log() { - let log = OsLog::new("com.example.oslog", "testing"); - log.debug("Debug"); - log.info("Info"); - log.default("Default"); - log.error("Error"); - log.fault("Fault"); - } -} +pub use level::Level; +pub use minimal::OsLog; diff --git a/src/log/level.rs b/src/log/level.rs new file mode 100644 index 0000000..ce3c692 --- /dev/null +++ b/src/log/level.rs @@ -0,0 +1,11 @@ +impl From for crate::Level { + fn from(other: log::Level) -> Self { + match other { + log::Level::Trace => Self::Debug, + log::Level::Debug => Self::Info, + log::Level::Info => Self::Default, + log::Level::Warn => Self::Error, + log::Level::Error => Self::Fault, + } + } +} diff --git a/src/log/logger.rs b/src/log/logger.rs new file mode 100644 index 0000000..e3303ae --- /dev/null +++ b/src/log/logger.rs @@ -0,0 +1,76 @@ +use crate::os_log_string::OsLogString; + +use super::Logs; +use log::{LevelFilter, Log, Metadata, Record}; +use std::ffi::CString; + +pub struct OsLogger { + logs: Logs, + subsystem: CString, +} + +impl Log for OsLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + let max_level = self + .logs + .max_level(&self.subsystem, metadata.target()) + .unwrap_or_else(log::max_level); + + metadata.level() <= max_level + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + let category = record.target(); + + self.logs + .with_log(&self.subsystem, category, |(_level_filter, log)| { + #[cfg(feature = "categories")] + let message = std::format!("{}", record.args()); + + #[cfg(not(feature = "categories"))] + let message = std::format!("[{category}] {}", record.args()); + + log.with_level(record.level().into(), &message); + }); + } + } + + fn flush(&self) {} +} + +impl OsLogger { + /// Creates a new logger. You must also call `init` to finalize the set up. + /// By default the level filter will be set to `LevelFilter::Trace`. + pub fn new(subsystem: &S) -> Self { + subsystem.with_cstr(|s| { + let subsystem = subsystem.to_owned(); + + Self { + logs: Logs::new(subsystem), + subsystem: s.to_owned(), + } + }) + } + + /// Only levels at or above `level` will be logged. + pub fn level_filter(self, level: LevelFilter) -> Self { + log::set_max_level(level); + self + } + + /// Sets or updates the category's level filter. + #[allow(unused_mut)] + pub fn category_level_filter(mut self, category: &str, level: LevelFilter) -> Self { + self.logs + .with_log_mut(&self.subsystem, category, |(level_filter, _log)| { + *level_filter = Some(level); + }); + + self + } + + pub fn init(self) -> Result<(), log::SetLoggerError> { + log::set_boxed_logger(Box::new(self)) + } +} diff --git a/src/log/mod.rs b/src/log/mod.rs new file mode 100644 index 0000000..b727a45 --- /dev/null +++ b/src/log/mod.rs @@ -0,0 +1,14 @@ +mod level; +mod logger; + +#[cfg(feature = "categories")] +mod mutex_logs; +#[cfg(feature = "categories")] +pub use mutex_logs::Logs; + +#[cfg(not(feature = "categories"))] +mod prefixed_logs; +#[cfg(not(feature = "categories"))] +pub use prefixed_logs::Logs; + +pub use logger::OsLogger; diff --git a/src/log/mutex_logs.rs b/src/log/mutex_logs.rs new file mode 100644 index 0000000..8772251 --- /dev/null +++ b/src/log/mutex_logs.rs @@ -0,0 +1,64 @@ +use crate::os_log_string::OsLogString; +use crate::OsLog; +use log::LevelFilter; +use std::ffi::CStr; +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::{collections::HashMap, sync::Mutex}; + +pub struct Logs(Mutex, OsLog)>>); + +impl Logs { + pub fn new(_subsystem: &S) -> Self { + Self(Default::default()) + } + + pub fn max_level(&self, subsystem: &CStr, category: &str) -> Option { + let key = get_log_key(subsystem, category); + + self.0.lock().unwrap().get(&key).and_then(|pair| pair.0) + } + + pub fn with_log_mut, OsLog))>( + &self, + subsystem: &CStr, + category: &str, + f: F, + ) { + let key = get_log_key(subsystem, category); + + let mut lock = self.0.lock().unwrap(); + + let pair = lock + .entry(key) + .or_insert((None, OsLog::new(subsystem, category))); + + f(pair) + } + + pub fn with_log, OsLog))>( + &self, + subsystem: &CStr, + category: &str, + f: F, + ) { + let key = get_log_key(subsystem, category); + + let mut lock = self.0.lock().unwrap(); + + let pair = lock + .entry(key) + .or_insert((None, OsLog::new(subsystem, category))); + + f(pair) + } +} + +/// This is a hash of catgeory + subsystem +type LogKey = u64; + +fn get_log_key(subsystem: &CStr, category: &str) -> u64 { + let mut hasher = DefaultHasher::new(); + subsystem.hash(&mut hasher); + category.hash(&mut hasher); + hasher.finish() +} diff --git a/src/log/prefixed_logs.rs b/src/log/prefixed_logs.rs new file mode 100644 index 0000000..9b192ab --- /dev/null +++ b/src/log/prefixed_logs.rs @@ -0,0 +1,37 @@ +use crate::{os_log_string::OsLogString, OsLog}; +use log::LevelFilter; +use std::ffi::CStr; + +#[derive(Default)] +pub struct Logs((Option, OsLog)); + +impl Logs { + pub fn new(subsystem: &S) -> Self { + Self((None, OsLog::new(subsystem, "default"))) + } + + #[inline] + pub fn max_level(&self, _subsystem: &CStr, _category: &str) -> Option { + self.0 .0 + } + + #[inline] + pub fn with_log, OsLog))>( + &self, + _subsystem: &CStr, + _category: &str, + f: F, + ) { + f(&self.0) + } + + #[inline] + pub fn with_log_mut, OsLog))>( + &mut self, + _subsystem: &CStr, + _category: &str, + f: F, + ) { + f(&mut self.0) + } +} diff --git a/src/logger.rs b/src/logger.rs deleted file mode 100644 index c6464a2..0000000 --- a/src/logger.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::OsLog; -use dashmap::DashMap; -use log::{LevelFilter, Log, Metadata, Record}; - -pub struct OsLogger { - loggers: DashMap, OsLog)>, - subsystem: String, -} - -impl Log for OsLogger { - fn enabled(&self, metadata: &Metadata) -> bool { - let max_level = self - .loggers - .get(metadata.target()) - .and_then(|pair| pair.0) - .unwrap_or_else(log::max_level); - - metadata.level() <= max_level - } - - fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - let pair = self - .loggers - .entry(record.target().into()) - .or_insert((None, OsLog::new(&self.subsystem, record.target()))); - - let message = std::format!("{}", record.args()); - pair.1.with_level(record.level().into(), &message); - } - } - - fn flush(&self) {} -} - -impl OsLogger { - /// Creates a new logger. You must also call `init` to finalize the set up. - /// By default the level filter will be set to `LevelFilter::Trace`. - pub fn new(subsystem: &str) -> Self { - Self { - loggers: DashMap::new(), - subsystem: subsystem.to_string(), - } - } - - /// Only levels at or above `level` will be logged. - pub fn level_filter(self, level: LevelFilter) -> Self { - log::set_max_level(level); - self - } - - /// Sets or updates the category's level filter. - pub fn category_level_filter(self, category: &str, level: LevelFilter) -> Self { - self.loggers - .entry(category.into()) - .and_modify(|(existing_level, _)| *existing_level = Some(level)) - .or_insert((Some(level), OsLog::new(&self.subsystem, category))); - - self - } - - pub fn init(self) -> Result<(), log::SetLoggerError> { - log::set_boxed_logger(Box::new(self)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use log::{debug, error, info, trace, warn}; - - #[test] - fn test_basic_usage() { - OsLogger::new("com.example.oslog") - .level_filter(LevelFilter::Trace) - .category_level_filter("Settings", LevelFilter::Warn) - .category_level_filter("Database", LevelFilter::Error) - .category_level_filter("Database", LevelFilter::Trace) - .init() - .unwrap(); - - // This will not be logged because of its category's custom level filter. - info!(target: "Settings", "Info"); - - warn!(target: "Settings", "Warn"); - error!(target: "Settings", "Error"); - - trace!("Trace"); - debug!("Debug"); - info!("Info"); - warn!(target: "Database", "Warn"); - error!("Error"); - } -} diff --git a/src/minimal.rs b/src/minimal.rs new file mode 100644 index 0000000..e1bde1b --- /dev/null +++ b/src/minimal.rs @@ -0,0 +1,92 @@ +use crate::level::Level; +use crate::os_log_string::OsLogString; +use crate::sys::{ + os_log_create, os_log_t, os_log_type_enabled, os_release, wrapped_get_default_log, + wrapped_os_log_with_type, +}; +use std::ffi::c_void; + +pub struct OsLog { + inner: os_log_t, +} + +unsafe impl Send for OsLog {} +unsafe impl Sync for OsLog {} + +impl Drop for OsLog { + fn drop(&mut self) { + unsafe { + if self.inner != wrapped_get_default_log() { + os_release(self.inner as *mut c_void); + } + } + } +} + +impl Default for OsLog { + fn default() -> Self { + OsLog::global() + } +} + +impl OsLog { + #[inline] + pub fn new(subsystem: &S, category: &C) -> Self + where + S: OsLogString + ?Sized, + C: OsLogString + ?Sized, + { + let inner = subsystem.with_cstr(|s| { + category.with_cstr(|c| unsafe { os_log_create(s.as_ptr(), c.as_ptr()) }) + }); + + assert!(!inner.is_null(), "Unexpected null value from os_log_create"); + + Self { inner } + } + + #[inline] + pub fn global() -> Self { + let inner = unsafe { wrapped_get_default_log() }; + + assert!(!inner.is_null(), "Unexpected null value for OS_DEFAULT_LOG"); + + Self { inner } + } + + #[inline] + pub fn with_level(&self, level: Level, message: &M) { + message + .with_cstr(|m| unsafe { wrapped_os_log_with_type(self.inner, level as u8, m.as_ptr()) }) + } + + #[inline] + pub fn debug(&self, message: &M) { + self.with_level(Level::Debug, message); + } + + #[inline] + pub fn info(&self, message: &M) { + self.with_level(Level::Info, message); + } + + #[inline] + pub fn default(&self, message: &M) { + self.with_level(Level::Default, message); + } + + #[inline] + pub fn error(&self, message: &M) { + self.with_level(Level::Error, message); + } + + #[inline] + pub fn fault(&self, message: &M) { + self.with_level(Level::Fault, message); + } + + #[inline] + pub fn level_is_enabled(&self, level: Level) -> bool { + unsafe { os_log_type_enabled(self.inner, level as u8) } + } +} diff --git a/src/os_log_string.rs b/src/os_log_string.rs new file mode 100644 index 0000000..512bfa0 --- /dev/null +++ b/src/os_log_string.rs @@ -0,0 +1,47 @@ +use std::ffi::{CStr, CString}; + +pub trait OsLogString { + fn with_cstr(&self, f: F) -> R + where + F: FnOnce(&CStr) -> R; +} + +impl OsLogString for CString { + fn with_cstr(&self, f: F) -> R + where + F: FnOnce(&CStr) -> R, + { + f(self.as_c_str()) + } +} + +impl OsLogString for CStr { + fn with_cstr(&self, f: F) -> R + where + F: FnOnce(&CStr) -> R, + { + f(self) + } +} + +impl OsLogString for String { + fn with_cstr(&self, f: F) -> R + where + F: FnOnce(&CStr) -> R, + { + let s = CString::new(self.as_bytes()).unwrap_or_default(); + + f(s.as_c_str()) + } +} + +impl OsLogString for str { + fn with_cstr(&self, f: F) -> R + where + F: FnOnce(&CStr) -> R, + { + let s = CString::new(self).unwrap_or_default(); + + f(s.as_c_str()) + } +} diff --git a/src/sys.rs b/src/sys.rs index 24c8b6f..0b2cb75 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -29,11 +29,6 @@ extern "C" { extern "C" { pub fn wrapped_get_default_log() -> os_log_t; pub fn wrapped_os_log_with_type(log: os_log_t, log_type: os_log_type_t, message: *const c_char); - pub fn wrapped_os_log_debug(log: os_log_t, message: *const c_char); - pub fn wrapped_os_log_info(log: os_log_t, message: *const c_char); - pub fn wrapped_os_log_default(log: os_log_t, message: *const c_char); - pub fn wrapped_os_log_error(log: os_log_t, message: *const c_char); - pub fn wrapped_os_log_fault(log: os_log_t, message: *const c_char); } #[cfg(test)] @@ -58,32 +53,30 @@ mod tests { let message = CString::new("Hello!").unwrap(); unsafe { - wrapped_os_log_debug(wrapped_get_default_log(), message.as_ptr()); - wrapped_os_log_info(wrapped_get_default_log(), message.as_ptr()); - wrapped_os_log_default(wrapped_get_default_log(), message.as_ptr()); - wrapped_os_log_error(wrapped_get_default_log(), message.as_ptr()); - wrapped_os_log_fault(wrapped_get_default_log(), message.as_ptr()); - wrapped_os_log_with_type( wrapped_get_default_log(), OS_LOG_TYPE_DEBUG, message.as_ptr(), ); + wrapped_os_log_with_type( wrapped_get_default_log(), OS_LOG_TYPE_INFO, message.as_ptr(), ); + wrapped_os_log_with_type( wrapped_get_default_log(), OS_LOG_TYPE_DEFAULT, message.as_ptr(), ); + wrapped_os_log_with_type( wrapped_get_default_log(), OS_LOG_TYPE_ERROR, message.as_ptr(), ); + wrapped_os_log_with_type( wrapped_get_default_log(), OS_LOG_TYPE_FAULT, @@ -100,12 +93,6 @@ mod tests { let message = CString::new("Hello!").unwrap(); unsafe { - wrapped_os_log_debug(log, message.as_ptr()); - wrapped_os_log_info(log, message.as_ptr()); - wrapped_os_log_default(log, message.as_ptr()); - wrapped_os_log_error(log, message.as_ptr()); - wrapped_os_log_fault(log, message.as_ptr()); - wrapped_os_log_with_type(log, OS_LOG_TYPE_DEBUG, message.as_ptr()); wrapped_os_log_with_type(log, OS_LOG_TYPE_INFO, message.as_ptr()); wrapped_os_log_with_type(log, OS_LOG_TYPE_DEFAULT, message.as_ptr()); diff --git a/tests/logger.rs b/tests/logger.rs new file mode 100644 index 0000000..8f47e70 --- /dev/null +++ b/tests/logger.rs @@ -0,0 +1,25 @@ +use log::{debug, error, info, trace, warn, LevelFilter}; +use oslog::OsLogger; + +#[test] +fn test_basic_usage() { + OsLogger::new("com.example.oslog") + .level_filter(LevelFilter::Trace) + .category_level_filter("Settings", LevelFilter::Warn) + .category_level_filter("Database", LevelFilter::Error) + .category_level_filter("Database", LevelFilter::Trace) + .init() + .unwrap(); + + // This will not be logged because of its category's custom level filter. + info!(target: "Settings", "Info"); + + warn!(target: "Settings", "Warn"); + error!(target: "Settings", "Error"); + + trace!("Trace"); + debug!("Debug"); + info!("Info"); + warn!(target: "Database", "Warn"); + error!("Error"); +} diff --git a/tests/minimal.rs b/tests/minimal.rs new file mode 100644 index 0000000..17ab9bd --- /dev/null +++ b/tests/minimal.rs @@ -0,0 +1,45 @@ +use oslog::{Level, OsLog}; + +#[test] +fn test_subsystem_interior_null() { + let log = OsLog::new("com.example.oslog\0test", "category"); + log.with_level(Level::Debug, "Hi"); +} + +#[test] +fn test_category_interior_null() { + let log = OsLog::new("com.example.oslog", "category\0test"); + log.with_level(Level::Debug, "Hi"); +} + +#[test] +fn test_message_interior_null() { + let log = OsLog::new("com.example.oslog", "category"); + log.with_level(Level::Debug, "Hi\0test"); +} + +#[test] +fn test_message_emoji() { + let log = OsLog::new("com.example.oslog", "category"); + log.with_level(Level::Debug, "\u{1F601}"); +} + +#[test] +fn test_global_log_with_level() { + let log = OsLog::global(); + log.with_level(Level::Debug, "Debug"); + log.with_level(Level::Info, "Info"); + log.with_level(Level::Default, "Default"); + log.with_level(Level::Error, "Error"); + log.with_level(Level::Fault, "Fault"); +} + +#[test] +fn test_custom_log_with_level() { + let log = OsLog::new("com.example.oslog", "testing"); + log.with_level(Level::Debug, "Debug"); + log.with_level(Level::Info, "Info"); + log.with_level(Level::Default, "Default"); + log.with_level(Level::Error, "Error"); + log.with_level(Level::Fault, "Fault"); +} diff --git a/wrapper.c b/wrapper.c index 7a3510e..40e3968 100644 --- a/wrapper.c +++ b/wrapper.c @@ -7,23 +7,3 @@ os_log_t wrapped_get_default_log() { void wrapped_os_log_with_type(os_log_t log, os_log_type_t type, const char* message) { os_log_with_type(log, type, "%{public}s", message); } - -void wrapped_os_log_debug(os_log_t log, const char* message) { - os_log_debug(log, "%{public}s", message); -} - -void wrapped_os_log_info(os_log_t log, const char* message) { - os_log_info(log, "%{public}s", message); -} - -void wrapped_os_log_default(os_log_t log, const char* message) { - os_log(log, "%{public}s", message); -} - -void wrapped_os_log_error(os_log_t log, const char* message) { - os_log_error(log, "%{public}s", message); -} - -void wrapped_os_log_fault(os_log_t log, const char* message) { - os_log_fault(log, "%{public}s", message); -} \ No newline at end of file