Prepare for initial crates.io release

This commit is contained in:
Nicolas Stalder
2020-11-28 02:44:20 +01:00
parent 0e6917fecf
commit 0a865fa91e
19 changed files with 207 additions and 87 deletions
+14 -8
View File
@@ -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
+1 -1
View File
@@ -1,3 +1,3 @@
/target
**/target
**/*.rs.bk
Cargo.lock
+11
View File
@@ -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.
+6 -1
View File
@@ -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 = []
+11
View File
@@ -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
+19
View File
@@ -1,3 +1,22 @@
# delog
[<img alt="github" src="https://img.shields.io/badge/github-nickray/delog-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/nickray/delog)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-delog-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/delog)
[<img alt="build status" src="https://img.shields.io/github/workflow/status/nickray/delog/CI/main?style=for-the-badge" height="20">](https://github.com/nickray/delog/actions?query=branch%3Amain)
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
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.
</sub>
+4 -1
View File
@@ -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();
});
+3
View File
@@ -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);
+3
View File
@@ -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);
+1 -1
View File
@@ -14,5 +14,5 @@ path = "lib-b"
[dependencies.delog]
path = ".."
features = ["std"]
features = ["std", "flushers"]
+2 -6
View File
@@ -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());
}
+2 -2
View File
@@ -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 = [
-2
View File
@@ -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 = ".."
+10 -8
View File
@@ -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);
+5 -2
View File
@@ -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;
+15 -15
View File
@@ -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<S>,
_block_size: PhantomData<BytesPerBlock>,
@@ -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<T: ?Sized, S: Separator, B: Unsigned>(value: &T) -> HexStr<T, S, B> {
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<T: ?Sized>(value: &T) -> HexStr<T, NullSeparator, consts::U1> {
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<T: ?Sized>(value: &T) -> HexStr<T> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, consts::U1> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, consts::U2> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, consts::U3> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, typeint!(4)> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, consts::U5> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, consts::U8> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, consts::U16> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, consts::U20> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, consts::U32> {
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<T: ?Sized>(value: &T) -> HexStr<T, SpaceSeparator, consts::U64> {
HexStr(value)
}
+39 -15
View File
@@ -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 }
}
+60 -24
View File
@@ -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);
+1 -1
View File
@@ -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] {