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
+[
](https://github.com/nickray/delog)
+[
](https://docs.rs/delog)
+[
](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] {