Skip to content

Commit

Permalink
Rewrite to significantly improve performance and remove dashmap
Browse files Browse the repository at this point in the history
  • Loading branch information
steven-joruk committed Jul 29, 2024
1 parent f192e27 commit df48966
Show file tree
Hide file tree
Showing 15 changed files with 378 additions and 324 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jobs:
strategy:
matrix:
target: [x86_64-apple-darwin, aarch64-apple-darwin, aarch64-apple-ios]
features-flags: ["", "-Flog"]
steps:
- name: Checkout sources
uses: actions/checkout@v2
Expand All @@ -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
Expand All @@ -46,6 +47,7 @@ jobs:
with:
command: test


lints:
name: Lints
runs-on: macos-latest
Expand Down
9 changes: 4 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "oslog"
description = "A minimal safe wrapper around Apple's Logging system"
repository = "https://github.com/steven-joruk/oslog"
version = "0.2.0"
version = "1.0.0-rc.1"
authors = ["Steven Joruk <[email protected]>"]
edition = "2021"
license = "MIT"
Expand All @@ -11,14 +11,13 @@ keywords = ["log", "logging", "macos", "apple"]
categories = ["development-tools::debugging"]

[features]
default = ["logger"]
default = ["log"]

# Enables support for the `log` crate.
logger = ["dashmap", "log"]
# Enables support for the log crate.
log = ["dep: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"
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ default features.

When making use of targets (`info!(target: "t", "m");`), you should be aware
that a new log is allocated and stored in a map for the lifetime of the program.
I expect log allocations are extremely small, but haven't attempted to verify
it.
Apple also recommends against using dynamically generated subsystems or
categories when using Unified Logging directly, for the same reason.

## Logging example

This is behind the `logger` feature flag and is enabled by default.
This is behind the `log` feature flag and is enabled by default.

```rust
fn main() {
Expand All @@ -40,6 +40,11 @@ fn main() {
}
```

## Features

`log` enables [log](https://docs.rs/log/latest/log/) crate support via
`OsLogger`.

## Limitations

Most of Apple's logging related functions are macros that enable some
Expand All @@ -49,3 +54,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.
12 changes: 12 additions & 0 deletions src/level.rs
Original file line number Diff line number Diff line change
@@ -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,
}
193 changes: 9 additions & 184 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<log::Level> 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;
11 changes: 11 additions & 0 deletions src/log/level.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
impl From<log::Level> 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,
}
}
}
Loading

0 comments on commit df48966

Please sign in to comment.