mirror of
https://github.com/trussed-dev/delog.git
synced 2026-06-20 04:16:34 -07:00
Prepare for initial crates.io release
This commit is contained in:
@@ -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
@@ -1,3 +1,3 @@
|
||||
/target
|
||||
**/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
|
||||
@@ -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
@@ -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 = []
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -14,5 +14,5 @@ path = "lib-b"
|
||||
|
||||
[dependencies.delog]
|
||||
path = ".."
|
||||
features = ["std"]
|
||||
features = ["std", "flushers"]
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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] {
|
||||
|
||||
Reference in New Issue
Block a user