From 0a865fa91e80f22db0fe864e90f4759808199d35 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Sat, 28 Nov 2020 00:54:29 +0100 Subject: [PATCH] Prepare for initial crates.io release --- .github/workflows/ci.yml | 22 +++++++---- .gitignore | 2 +- CHANGELOG.md | 11 ++++++ Cargo.toml | 7 +++- Makefile | 11 ++++++ README.md | 19 +++++++++ examples/around.rs | 5 ++- examples/log.rs | 3 ++ examples/try_log.rs | 3 ++ gate-tests/Cargo.toml | 2 +- gate-tests/src/main.rs | 8 +--- qemu-tests/.cargo/config | 4 +- qemu-tests/Cargo.toml | 2 - qemu-tests/src/main.rs | 18 +++++---- src/flushers.rs | 7 +++- src/hex.rs | 30 +++++++------- src/lib.rs | 54 +++++++++++++++++++------- src/logger.rs | 84 ++++++++++++++++++++++++++++------------ src/render.rs | 2 +- 19 files changed, 207 insertions(+), 87 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 Makefile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef37133..ab9291b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,8 @@ on: branches: [main] env: - QEMU_TARGET: thumbv8m.main-none-eabi + # need atomic CAS, so Cortex-M0/1 are out + QEMU_TARGET: thumbv7m-none-eabi jobs: test: @@ -27,14 +28,18 @@ jobs: profile: minimal toolchain: ${{ matrix.rust }} override: true + - name: Check that all crates build without warning run: RUSTFLAGS='--deny warnings' cargo check --all - - name: Run doc and lib tests - run: | - cargo test --lib - cargo test --doc - - name: Run examples as test (needs `std` feature) - run: cargo test --features std + shell: bash + + - name: Check clippy output (lenient mode) + # run: RUSTFLAGS='--deny warnings' cargo clippy --all + run: cargo clippy --all + shell: bash + + - name: Run non-QEMU tests + run: make non-qemu-tests qemu-test: strategy: @@ -43,6 +48,7 @@ jobs: - stable # ubuntu-latest still points to 18.04, which only has QEMU 2 runs-on: ubuntu-20.04 + steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -52,7 +58,7 @@ jobs: target: ${{ env.QEMU_TARGET }} override: true - - name: Install QEMU (need >= 4) + - name: Install QEMU (want >= 4) run: | sudo apt-get update -qq >/dev/null sudo apt-get install -qq qemu-system-arm >/dev/null diff --git a/.gitignore b/.gitignore index 6936990..fdc1c35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/target +**/target **/*.rs.bk Cargo.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2d7cde9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0-alpha.1] - 2020-11-28 + +Test the release process, meditate on the `local_*!` issue. diff --git a/Cargo.toml b/Cargo.toml index cf9f92e..8487b8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,10 @@ categories = ["algorithms", "development-tools", "embedded", "no-std"] keywords = ["log", "logging", "formatting"] edition = "2018" +[package.metadata.docs.rs] +all-features = true +targets = [] + [dependencies] cortex-m-semihosting = { version = "0.3.5", optional = true } hex_fmt = "0.3.0" @@ -23,7 +27,8 @@ insta = "1.3.0" [features] default = ["fallible", "immediate"] std = [] -example = ["std"] +example = ["std", "flushers"] +flushers = [] semihosting = ["cortex-m-semihosting"] fallible = [] immediate = [] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d0168ef --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +test: non-qemu-tests qemu-tests + +non-qemu-tests: + cargo test --lib + cargo test --doc + cargo test --examples --features flushers,std + $(MAKE) -C gate-tests + +qemu-tests: + $(MAKE) -C qemu-tests test + diff --git a/README.md b/README.md index a56028d..b2292ac 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,22 @@ # delog +[github](https://github.com/nickray/delog) +[docs.rs](https://docs.rs/delog) +[build status](https://github.com/nickray/delog/actions?query=branch%3Amain) + +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + + diff --git a/examples/around.rs b/examples/around.rs index 1d62d17..b5eb851 100644 --- a/examples/around.rs +++ b/examples/around.rs @@ -1,6 +1,9 @@ #[macro_use] extern crate delog; +#[cfg(not(all(feature = "flushers", feature = "std")))] +compile_error!("This example needs the `flushers` and `std` features"); + use delog::flushers::StdoutFlusher; delog!(Delogger, 25, StdoutFlusher); @@ -12,7 +15,7 @@ fn main() { let msg = "1234567890"; - (0..10).for_each(|i| { + (0..10).for_each(|_| { info!("{}", msg); Delogger::flush(); }); diff --git a/examples/log.rs b/examples/log.rs index b3e9954..378be41 100644 --- a/examples/log.rs +++ b/examples/log.rs @@ -1,6 +1,9 @@ #[macro_use] extern crate delog; +#[cfg(not(all(feature = "flushers", feature = "std")))] +compile_error!("This example needs the `flushers` and `std` features"); + use delog::flushers::StdoutFlusher; delog!(Delogger, 256, StdoutFlusher); diff --git a/examples/try_log.rs b/examples/try_log.rs index 5c8afff..6b6704c 100644 --- a/examples/try_log.rs +++ b/examples/try_log.rs @@ -1,5 +1,8 @@ use delog::{try_info, try_warn}; +#[cfg(not(all(feature = "flushers", feature = "std")))] +compile_error!("This example needs the `flushers` and `std` features"); + use delog::flushers::StdoutFlusher; delog::delog!(Delogger, 64, StdoutFlusher); diff --git a/gate-tests/Cargo.toml b/gate-tests/Cargo.toml index 59093c5..b115e6d 100644 --- a/gate-tests/Cargo.toml +++ b/gate-tests/Cargo.toml @@ -14,5 +14,5 @@ path = "lib-b" [dependencies.delog] path = ".." -features = ["std"] +features = ["std", "flushers"] diff --git a/gate-tests/src/main.rs b/gate-tests/src/main.rs index 25cd425..6e0e96e 100644 --- a/gate-tests/src/main.rs +++ b/gate-tests/src/main.rs @@ -11,11 +11,7 @@ fn main() { Delogger::init(delog::LevelFilter::Info, &FLUSHER).expect("all good"); lib_a::f(); lib_b::g(); - println!("log attempts: {}", delog::trylogger().unwrap().attempts()); - println!("log successes: {}", delog::trylogger().unwrap().attempts()); - println!("log flushes: {}", delog::trylogger().unwrap().flushes()); + println!("{:?}", delog::trylogger().unwrap().statistics()); Delogger::flush(); - println!("log attempts: {}", delog::trylogger().unwrap().attempts()); - println!("log successes: {}", delog::trylogger().unwrap().attempts()); - println!("log flushes: {}", delog::trylogger().unwrap().flushes()); + println!("{:?}", delog::trylogger().unwrap().statistics()); } diff --git a/qemu-tests/.cargo/config b/qemu-tests/.cargo/config index 20d1186..21249a5 100644 --- a/qemu-tests/.cargo/config +++ b/qemu-tests/.cargo/config @@ -1,7 +1,7 @@ [build] -target = "thumbv8m.main-none-eabi" +target = "thumbv7m-none-eabi" -[target.thumbv8m.main-none-eabi] +[target.thumbv7m-none-eabi] runner = "qemu-system-arm -cpu cortex-m33 -machine musca-b1 -nographic -semihosting-config enable=on,target=native -s -kernel" rustflags = [ diff --git a/qemu-tests/Cargo.toml b/qemu-tests/Cargo.toml index 89865eb..48e35fa 100644 --- a/qemu-tests/Cargo.toml +++ b/qemu-tests/Cargo.toml @@ -10,9 +10,7 @@ license = "Apache-2.0 OR MIT" cortex-m = "0.6.1" cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.5" -hex-literal = "0.2.1" panic-semihosting = { version = "0.5.3", features = ["exit"] } -# subtle = { version = "2.2", default-features = false } [dependencies.delog] path = ".." diff --git a/qemu-tests/src/main.rs b/qemu-tests/src/main.rs index 0842769..8006f99 100644 --- a/qemu-tests/src/main.rs +++ b/qemu-tests/src/main.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] -use cortex_m_semihosting::{debug, hprintln}; +use cortex_m_semihosting::debug; extern crate panic_semihosting; use cortex_m_rt::entry; @@ -22,12 +22,13 @@ delog!(Delogger, 256, SemihostingFlusher); static SEMIHOSTING_FLUSHER: SemihostingFlusher = SemihostingFlusher {}; fn test_runs() { - // do some serious work + // do some serious™ work warn!("This is a warning"); - info!(target: "!", "This is an IMMEDIATE information"); - info!("jeez '{:02X}'", delog::hex_str!(&[0xa1u8, 0xfF, 0x03])); - info!("heeb '{:#02X?}'", [0xa1u8, 0xfF, 0x03].as_ref()); - info!("heeg '{:02X?}'", [0xa1u8, 0xfF, 0x03].as_ref()); + info!(target: "!", "This is IMMEDIATE information"); + info_now!("This too"); + info!("hex_str '{}'", delog::hex_str!(&[0xa1u8, 0xfF, 0x03])); + info!("alternate debug '{:#02X?}'", [0xa1u8, 0xff, 0x03].as_ref()); + info!("regular debug '{:02X?}'", [0xA1u8, 0xFF, 0x03].as_ref()); // flush the logs Delogger::flush(); @@ -36,11 +37,12 @@ fn test_runs() { #[entry] fn main() -> ! { - Delogger::init(delog::LevelFilter::Info, &SEMIHOSTING_FLUSHER).ok(); + Delogger::init(delog::LevelFilter::Debug, &SEMIHOSTING_FLUSHER).ok(); test_runs(); - hprintln!("All tests passed").ok(); + debug_now!("{:?}", delog::trylogger().unwrap().statistics()); + info_now!("All tests passed"); debug::exit(debug::EXIT_SUCCESS); diff --git a/src/flushers.rs b/src/flushers.rs index c83e960..3c3ddad 100644 --- a/src/flushers.rs +++ b/src/flushers.rs @@ -1,7 +1,10 @@ //! Typical flushers in various environments. //! -//! Availability based on cargo flags, e.g. `std` gives stdout/stderr flushers, -//! while `semihosting` gives flushers to host's stdout/stderr. +//! An actual firmware will likely want to implement its own flusher. +//! +//! Availability based on cargo flags. The `flushers` feature must always be +//! selected. Additionally setting `std` gives stdout/stderr flushers, +//! while additionally setting `semihosting` gives flushers to host's stdout/stderr. #[cfg(any(feature = "std", test))] mod std; diff --git a/src/hex.rs b/src/hex.rs index 9db3fb6..f5ed57e 100644 --- a/src/hex.rs +++ b/src/hex.rs @@ -39,8 +39,6 @@ use core::marker::PhantomData; use core::fmt; -use hex_fmt; - /// re-export from `hex_fmt` /// pub use hex_fmt::HexFmt; @@ -52,6 +50,7 @@ pub use typenum::{consts, Unsigned}; /// A type that specifies a separator str. pub trait Separator { + /// The actual separator str. const SEPARATOR: &'static str; } @@ -76,6 +75,7 @@ where S: Separator, BytesPerBlock: Unsigned, { + /// The value to be formatted. pub value: &'a T, _separator: PhantomData, _block_size: PhantomData, @@ -182,91 +182,91 @@ macro_rules! hexstr { /// let hex_str = HexStr::<_, Pipe, consts::U3>(four_bytes); /// assert_eq!(format!("{}", hex_str), "07A1FF|C7"); /// ``` -pub fn HexStr<'a, T: ?Sized, S: Separator, B: Unsigned>(value: &'a T) -> HexStr<'a, T, S, B> { +pub fn HexStr(value: &T) -> HexStr { HexStr { value, _separator: PhantomData, _block_size: PhantomData } } /// blocks of 1 byte / 8 bits in hex, no space in between (e.g., `4A121387`) /// /// For ease of use, prefer the `hexstr!(value)` macro. -pub fn hexstr<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, NullSeparator, consts::U1> { +pub fn hexstr(value: &T) -> HexStr { HexStr(value) } /// synonym for `hex_str_1`, like ISO 7816 if enclosed in single quotes (`'8A 4F 12 AA'`). /// /// For ease of use, prefer the `hex_str!(value)` macro. -pub fn hex_str<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T> { +pub fn hex_str(value: &T) -> HexStr { HexStr(value) } /// blocks of 1 byte / 8 bits in hex, space in between (e.g., `4A 12 13 87`) /// /// For ease of use, prefer the `hex_str!(value)` macro. -pub fn hex_str_1<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, consts::U1> { +pub fn hex_str_1(value: &T) -> HexStr { HexStr(value) } /// blocks of 2 bytes / 16 bits in hex, space in between (e.g., `4A12 1387`) /// /// For ease of use, prefer the `hex_str!(value, 2)` macro. -pub fn hex_str_2<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, consts::U2> { +pub fn hex_str_2(value: &T) -> HexStr { HexStr(value) } /// blocks of 3 bytes / 24 bits in hex, space in between (e.g., `4A1213 871234 ABCD`) /// /// For ease of use, prefer the `hex_str!(value, 3)` macro. -pub fn hex_str_3<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, consts::U3> { +pub fn hex_str_3(value: &T) -> HexStr { HexStr(value) } /// blocks of 4 bytes / 32 bits in hex, space in between (e.g., `4A121387 1234ABCD`) /// /// For ease of use, prefer the `hex_str!(value, 4)` macro. -pub fn hex_str_4<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, typeint!(4)> { +pub fn hex_str_4(value: &T) -> HexStr { HexStr(value) } /// blocks of 5 bytes / 40 bits in hex, space in between (e.g., `4A12138712 34ABCD`) /// /// For ease of use, prefer the `hex_str!(value, 5)` macro. -pub fn hex_str_5<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, consts::U5> { +pub fn hex_str_5(value: &T) -> HexStr { HexStr(value) } /// blocks of 8 bytes / 64 bits in hex, space in between /// /// For ease of use, prefer the `hex_str!(value, 8)` macro. -pub fn hex_str_8<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, consts::U8> { +pub fn hex_str_8(value: &T) -> HexStr { HexStr(value) } /// blocks of 16 bytes / 128 bits in hex, space in between /// /// For ease of use, prefer the `hex_str!(value, 16)` macro. -pub fn hex_str_16<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, consts::U16> { +pub fn hex_str_16(value: &T) -> HexStr { HexStr(value) } /// blocks of 20 bytes / 160 bits in hex, space in between /// /// For ease of use, prefer the `hex_str!(value, 20)` macro. -pub fn hex_str_20<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, consts::U20> { +pub fn hex_str_20(value: &T) -> HexStr { HexStr(value) } /// blocks of 32 bytes / 256 bits in hex, space in between /// /// For ease of use, prefer the `hex_str!(value, 32)` macro. -pub fn hex_str_32<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, consts::U32> { +pub fn hex_str_32(value: &T) -> HexStr { HexStr(value) } /// blocks of 64 bytes / 512 bits in hex, space in between /// /// For ease of use, prefer the `hex_str!(value, 64)` macro. -pub fn hex_str_64<'a, T: ?Sized>(value: &'a T) -> HexStr<'a, T, SpaceSeparator, consts::U64> { +pub fn hex_str_64(value: &T) -> HexStr { HexStr(value) } diff --git a/src/lib.rs b/src/lib.rs index cfac284..069f185 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,8 @@ //! - **compatibility with the standard `core::fmt` traits and the standard `log` library API**. //! This means that, while libraries may "upgrade" their logging capabilities by using `delog` //! as drop-in replacement for their logging calls (see below), any existing library that already -//! uses `log` is compatible. This, for us, is a huge win as opposed to using up "weirdness -//! budget" for something as trivial and throw-away as simple logging. +//! uses `log` is compatible. This, for our use cases, is a huge win, as opposed to using up "weirdness +//! budget" and requiring custom tooling for something that is often trivial, throw-away, simple logging. //! - it follows that one can easily drop a `trace!("{:?}", &suspicious_object)` call at any time for //! any object that has a (possibly automatically derived) `Debug` trait implementation – without //! passing around structures and keeping on top of lifetimes. @@ -23,25 +23,48 @@ //! with "!" as parameter, or using the additional `immediate_info!` and friends macros. //! - ability to set log levels *per library, at compile-time*. This can be easily retro-fitted //! on existing `log`-based libraries, by adding the requisite features in `Cargo.toml` and -//! replacing `log` with `delog`. +//! replacing `log` with `delog` (see `gate-tests` for examples of this). //! - the outcome is that one can leave useful logging calls in the library code, only to activate -//! them in targeted ways, exactly as needed. +//! them in targeted ways at build time, exactly as needed. //! - helper macros to easily output binary byte arrays and slices in hexadecimal representations, //! which wrap the data in newtypes with custom `fmt::UpperHex` etc. implementations. //! //! **Non-goals**: //! -//! - ultimate speed or code size: Our intention are "normal" logs, not the use of "logging" for streaming -//! binary data to the host. While admittedly the `core::fmt`-ing facilities are not as efficient -//! as one may hope, in our use cases we have sufficient flash and RAM to use these (and some -//! hope that, someday, eventually, maybe, the formatting machinery will be revisited and +//! - ultimate speed or code size: Our intention are "normal" logs, not the use of logging/tracing to +//! for stream binary data to the host. While admittedly the `core::fmt`-ing facilities are not as +//! efficient as one might hope, in our use cases we have sufficient flash and RAM to use these (and +//! some hope that, someday, eventually, maybe, the formatting machinery will be revisited and //! improved at the root level, namely the language itself.) //! //! That said, we believe there is opportunity to extend `delog` in the `defmt` direction by //! using, e.g., the `fmt::Binary` trait, newtypes and sentinel values to embed raw binary -//! represenations of data in abnormally time-critical situations without formatting, deferring +//! representations of data in time-critical situations without formatting, deferring //! the extraction and actual formatting to some host-side mechanism. +//! +//! ## Features +//! The `flushers` and `semihosting` features mostly exist to share code within the examples, +//! including the `example` feature. Without them, dependencies are quite minimal, and compilation fast. +//! +//! The `fallible` and `immediate` features (default on) activate the `try_*!` and `*_now!` macros, respectively. +//! +//! ## Warning +//! The current circular buffer implementation (v0.1.0) is definitely unsound on desktop. +//! For embedded use, atomics are required (so no Cortex-M0/M1, and no plans to support non-atomic +//! platforms, which are likely to also be too resource-constrained to support the bloat inherent +//! in `core::fmt`). While we think the implemented circular buffer algorithm works for the "nested interrupt" +//! setup of NVICs, it has not been tested much. +//! The hope is that the worst case scenario is some slightly messed up log outputs. +//! +//! ## Outlook +//! We plan to iterate towards a v0.2.0 soon, making use of a separate "flusher" for the +//! "immediate" logging path. For instance, when logging via serial-over-USB, one might want immediate +//! logs to pend a separate RTIC interrupt handler that blocks until the logs are pushed and read +//! (allowing one to debug the boot process of a firmware), or one might want to just write to RTT +//! (or even semihosting xD) for these, during development. +//! +#![deny(missing_docs)] #![cfg_attr(not(any(feature = "std", test)), no_std)] use core::fmt; @@ -55,7 +78,7 @@ pub use log::{debug, error, info, log, log_enabled, trace, warn}; #[cfg(feature = "example")] pub mod example; -#[cfg(any(feature="std", feature="semihosting", test))] +#[cfg(all(feature = "flushers", any(feature="std", feature="semihosting", test)))] pub mod flushers; pub mod hex; @@ -64,23 +87,24 @@ mod log_macros_global; mod log_macros_local; mod logger; -pub use logger::{Delogger, Statistics, TryLog, dequeue, enqueue, try_enqueue}; +pub use logger::{Delogger, Statistics, TryLog, TryLogWithStatistics, dequeue, enqueue, try_enqueue}; pub mod render; -/// A way to pass on logs, user supplied +/// A way to pass on logs, user supplied. /// /// In embedded, this is intended to pend an interrupt /// to send the logs off via (USB) serial, semihosting, or similar. /// /// On PC, typical implemenation will just println! or eprintln! pub trait Flusher: core::fmt::Debug + Send { + /// Implementor must handle passed log `&str` in some hopefully useful way. fn flush(&self, logs: &str); } -static mut LOGGER: Option<&'static dyn logger::TryLog> = None; +static mut LOGGER: Option<&'static dyn logger::TryLogWithStatistics> = None; -/// Returns a reference to the logger (as `TryLog` implementation) -pub fn trylogger() -> &'static mut Option<&'static dyn logger::TryLog> { +/// Returns a reference to the logger (as `TryLogWithStatistics` implementation) +pub fn trylogger() -> &'static mut Option<&'static dyn logger::TryLogWithStatistics> { unsafe { &mut LOGGER } } diff --git a/src/logger.rs b/src/logger.rs index 2569ddf..37ad93c 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -3,6 +3,7 @@ use core::sync::atomic::{AtomicUsize, Ordering}; /// Semi-abstract characterization of the deferred loggers that the `delog!` macro produces. /// +/// # Safety /// This trait is markes "unsafe" to signal that users should never (need to) "write their own", /// but always go through the `delog!` macro. /// @@ -10,27 +11,43 @@ use core::sync::atomic::{AtomicUsize, Ordering}; /// implementation, not with this direct access to implementation details. pub unsafe trait Delogger: log::Log + crate::TryLog { + /// the underlying buffer fn buffer(&self) -> &'static mut [u8]; - // #[cfg(feature = "statistics")] + /// How often was one of the logging macros called. fn log_attempt_count(&self) -> &'static AtomicUsize; - // #[cfg(feature = "statistics")] + /// How often was one of the logging macros called without early exit (e.g., buffer not full) fn log_success_count(&self) -> &'static AtomicUsize; + /// How often was the flusher called. fn log_flush_count(&self) -> &'static AtomicUsize; + /// How many bytes were flushed so far. fn read(&self) -> &'static AtomicUsize; + /// How many bytes were logged so far. fn written(&self) -> &'static AtomicUsize; + /// How many characters were claimed so far. fn claimed(&self) -> &'static AtomicUsize; + /// Call the flusher. fn flush(&self, logs: &str); + /// Actually render the arguments (via internal static buffer). fn render(&self, args: &fmt::Arguments) -> &'static [u8]; + /// Capacity of circular buffer. fn capacity(&self) -> usize { self.buffer().len() } } #[derive(Clone, Copy, Debug)] +/// Statistics on logger usage. pub struct Statistics { + /// How often was one of the logging macros called. pub attempts: usize, + /// How often was one of the logging macros called without early exit (e.g., buffer not full) pub successes: usize, + /// How often was the flusher called. pub flushes: usize, + /// How many bytes were flushed so far. + pub read: usize, + /// How many bytes were logged so far. + pub written: usize, } /// Fallible, panic-free version of the `log::Log` trait. @@ -41,15 +58,33 @@ pub struct Statistics { /// would be using the fallible macros, and if not, they most likely do **not** /// want to crash. pub trait TryLog: log::Log { + /// Fallible logging call (fails when buffer is full) fn try_log(&self, _: &log::Record) -> core::result::Result<(), ()>; - fn statistics(&self) -> Statistics; +} - // #[cfg(feature = "statistics")] +/// TryLog with some usage statistics on top. +pub trait TryLogWithStatistics: TryLog { + /// Read out statistics on logger usage. + fn statistics(&self) -> Statistics { + Statistics { + attempts: self.attempts(), + successes: self.successes(), + flushes: self.flushes(), + read: self.read(), + written: self.written(), + } + } + + /// How often was one of the logging macros called. fn attempts(&self) -> usize; - // #[cfg(feature = "statistics")] + /// How often was one of the logging macros called without early exit (e.g., buffer not full) fn successes(&self) -> usize; - // #[cfg(feature = "statistics")] + /// How often was the flusher called. fn flushes(&self) -> usize; + /// How many bytes were flushed so far. + fn read(&self) -> usize; + /// How many bytes were logged so far. + fn written(&self) -> usize; } /// Generate a deferred logger with specified capacity and flushing mechanism. @@ -64,8 +99,10 @@ macro_rules! delog { ($logger:ident, $capacity:expr, $flusher:ty) => { #[derive(Clone, Copy)] + /// Generated deferred logging implementation. pub struct $logger { flusher: &'static $flusher, + // immediate_flusher: &'static $flusher, } // log::Log implementations are required to be Send + Sync @@ -100,11 +137,12 @@ macro_rules! delog { // use $crate::Delogger; unsafe { $crate::try_enqueue(*self, record) } } - // #[cfg(feature = "statistics")] + } + + impl $crate::TryLogWithStatistics for $logger { fn attempts(&self) -> usize { $crate::Delogger::log_attempt_count(self).load(core::sync::atomic::Ordering::SeqCst) } - // #[cfg(feature = "statistics")] fn successes(&self) -> usize { $crate::Delogger::log_success_count(self).load(core::sync::atomic::Ordering::SeqCst) } @@ -113,16 +151,15 @@ macro_rules! delog { $crate::Delogger::log_flush_count(self).load(core::sync::atomic::Ordering::SeqCst) } - fn statistics(&self) -> $crate::Statistics { - $crate::Statistics { - attempts: self.attempts(), - successes: self.successes(), - flushes: self.flushes(), - } + fn read(&self) -> usize { + $crate::Delogger::read(self).load(core::sync::atomic::Ordering::SeqCst) + } + fn written(&self) -> usize { + $crate::Delogger::written(self).load(core::sync::atomic::Ordering::SeqCst) } - } + #[allow(missing_docs)] impl $logger { pub fn init(level: $crate::upstream::LevelFilter, flusher: &'static $flusher) -> Result<(), ()> { use core::sync::atomic::{self, AtomicBool, AtomicUsize, Ordering}; @@ -133,6 +170,7 @@ macro_rules! delog { .compare_exchange_weak(false, true, Ordering::AcqRel, Ordering::Acquire).is_ok() { + // let logger = Self { flusher, immediate_flusher: flusher }; let logger = Self { flusher }; Self::get().replace(logger); $crate::trylogger().replace(Self::get().as_ref().unwrap()); @@ -169,21 +207,18 @@ macro_rules! delog { self.flusher.flush(logs) } - // #[cfg(feature = "statistics")] fn log_attempt_count(&self) -> &'static core::sync::atomic::AtomicUsize { use core::sync::atomic::AtomicUsize; static LOG_ATTEMPT_COUNT: AtomicUsize = AtomicUsize::new(0); &LOG_ATTEMPT_COUNT } - // #[cfg(feature = "statistics")] fn log_success_count(&self) -> &'static core::sync::atomic::AtomicUsize { use core::sync::atomic::AtomicUsize; static LOG_SUCCESS_COUNT: AtomicUsize = AtomicUsize::new(0); &LOG_SUCCESS_COUNT } - // #[cfg(feature = "statistics")] fn log_flush_count(&self) -> &'static core::sync::atomic::AtomicUsize { use core::sync::atomic::AtomicUsize; static LOG_FLUSH_COUNT: AtomicUsize = AtomicUsize::new(0); @@ -220,6 +255,7 @@ macro_rules! delog { /// The core "write to circular buffer" method. Marked unsafe to discourage use! /// +/// # Safety /// Unfortunately exposed for all to see, as the `delog!` macro needs access to it to /// implement the logger at call site. Hence marked as unsafe. pub unsafe fn enqueue(delogger: impl Delogger, record: &log::Record) { @@ -228,6 +264,7 @@ pub unsafe fn enqueue(delogger: impl Delogger, record: &log::Record) { /// The fallible "write to circular buffer" method. Marked unsafe to discourage use! /// +/// # Safety /// Unfortunately exposed for all to see, as the `delog!` macro needs access to it to /// implement the logger at call site. Hence marked as unsafe. /// @@ -247,16 +284,13 @@ pub unsafe fn enqueue(delogger: impl Delogger, record: &log::Record) { pub unsafe fn try_enqueue(delogger: impl Delogger, record: &log::Record) -> core::result::Result<(), ()> { // keep track of how man logs were attempted - // #[cfg(feature = "statistics")] delogger.log_attempt_count().fetch_add(1, Ordering::SeqCst); if record.target() == "!" { - // todo: proper "fast path" / immediate mode + // todo: possibly use separate immediate_flusher let input = delogger.render(record.args()); let input = unsafe { core::str::from_utf8_unchecked(input) }; Delogger::flush(&delogger, input); - // println!("{}", record.args()); - // #[cfg(feature = "statistics")] delogger.log_success_count().fetch_add(1, Ordering::SeqCst); return Ok(()); } @@ -327,15 +361,17 @@ pub unsafe fn try_enqueue(delogger: impl Delogger, record: &log::Record) -> core } } + delogger.log_success_count().fetch_add(1, Ordering::SeqCst); Ok(()) } /// The core "read from circular buffer" method. Marked unsafe to discourage use! /// +/// # Safety /// Unfortunately exposed for all to see, as the `delog!` macro needs access to it to /// implement the logger at call site. Hence marked as unsafe. #[allow(unused_unsafe)] -pub unsafe fn dequeue<'b>(delogger: impl Delogger, buf: &'b mut [u8]) -> &'b str +pub unsafe fn dequeue(delogger: impl Delogger, buf: &mut [u8]) -> &str { delogger.log_flush_count().fetch_add(1, Ordering::SeqCst); // we control the inputs, so we know this is a valid string @@ -344,7 +380,7 @@ pub unsafe fn dequeue<'b>(delogger: impl Delogger, buf: &'b mut [u8]) -> &'b str /// Copy out the contents of the `Logger` ring buffer into the given buffer, /// updating `read` to make space for new log data -fn drain_as_bytes<'b>(delogger: impl Delogger, buf: &'b mut [u8]) -> &'b [u8] { +fn drain_as_bytes(delogger: impl Delogger, buf: &mut [u8]) -> &[u8] { unsafe { let read = delogger.read().load(Ordering::SeqCst); let written = delogger.written().load(Ordering::SeqCst); diff --git a/src/render.rs b/src/render.rs index 5b6ca5c..e72f4e0 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,7 +3,7 @@ use core::{cmp, fmt}; /// For some reason, there seems to be no existing method to easily render -/// fmt;:Arguments in a pre-allocated byte array. +/// fmt::Arguments in a pre-allocated byte array. /// /// That is what this does. pub fn render_arguments<'a>(buf: &'a mut [u8], args: fmt::Arguments) -> &'a [u8] {