From 25ca46671d8b52f233d54e247c642c24c3ba1dec Mon Sep 17 00:00:00 2001 From: Yuri6037 Date: Sun, 30 Oct 2022 13:46:07 +0100 Subject: [PATCH 01/64] Added support for GitHub Actions --- .github/workflows/development.yml | 141 ++++++++++++++++++++++++++++++ .github/workflows/release.yml | 48 ++++++++++ 2 files changed, 189 insertions(+) create mode 100644 .github/workflows/development.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml new file mode 100644 index 0000000..64e8000 --- /dev/null +++ b/.github/workflows/development.yml @@ -0,0 +1,141 @@ +name: Development + +on: + - push + - workflow_dispatch + +jobs: + test-build: + name: Build & Test + strategy: + matrix: + os: + - ubuntu-20.04 + - macos-11 + - windows-2019 + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Build + run: cargo build --all-features + - name: Test + uses: bp3d-actions/cargo@main + with: + check-name: cargo test (${{ matrix.os }}) + command: test + args: --all-features --no-fail-fast + token: ${{ secrets.GITHUB_TOKEN }} + + clippy: + name: Check | Clippy + if: ${{ always() }} + needs: test-build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Run check + uses: bp3d-actions/clippy-check@main + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features + + audit: + name: Check | Audit + if: ${{ always() }} + needs: test-build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Install Audit Tool + run: cargo install cargo-audit + - name: Run check + uses: bp3d-actions/audit-check@main + with: + token: ${{ secrets.GITHUB_TOKEN }} + + fmt: + name: Format Code + if: ${{ always() && github.ref != 'refs/heads/master' }} + needs: + - clippy + - audit + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Run code formatter + uses: bp3d-actions/rustfmt-check@main + with: + token: ${{ secrets.GITHUB_TOKEN }} + + version: + name: Get Version + needs: test-build + runs-on: ubuntu-latest + outputs: + name: ${{ steps.version.outputs.name }} + version: ${{ steps.version.outputs.version }} + isnew: ${{ steps.version.outputs.isnew }} + ispre: ${{ steps.version.outputs.ispre }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Get Version + id: version + uses: bp3d-actions/cargo-version@main + with: + mode: get + token: ${{ secrets.GITHUB_TOKEN }} + + create-pre-release: + name: Create Pre Release + needs: version + if: github.ref == 'refs/heads/develop' && needs.version.outputs.isnew == 'true' && needs.version.outputs.ispre == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Setup cargo + run: cargo login ${{ secrets.RELEASE_TOKEN }} + - name: Publish + run: cargo publish + - name: Create + uses: ncipollo/release-action@main + with: + tag: ${{ needs.version.outputs.version }} + commit: ${{ github.ref }} + prerelease: true + name: ${{ needs.version.outputs.name }} release ${{ needs.version.outputs.version }} + body: "[Link to crates.io](https://crates.io/crates/${{ needs.version.outputs.name }})" + + create-release-pr: + name: Create Release Pull Request + needs: version + if: needs.version.outputs.isnew == 'true' && needs.version.outputs.ispre == 'false' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Create Pull Request + uses: repo-sync/pull-request@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + destination_branch: master + pr_title: Release ${{ needs.version.outputs.version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cf2e432 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,48 @@ +name: Release + +on: + push: + branches: + - master + +jobs: + version: + name: Get Version + runs-on: ubuntu-latest + outputs: + name: ${{ steps.version.outputs.name }} + version: ${{ steps.version.outputs.version }} + isnew: ${{ steps.version.outputs.isnew }} + ispre: ${{ steps.version.outputs.ispre }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Get Version + id: version + uses: bp3d-actions/cargo-version@main + with: + mode: get + token: ${{ secrets.GITHUB_TOKEN }} + + create-release: + name: Release + needs: version + if: needs.version.outputs.isnew == 'true' && needs.version.outputs.ispre == 'false' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Setup cargo + run: cargo login ${{ secrets.RELEASE_TOKEN }} + - name: Publish + run: cargo publish + - name: Create + uses: ncipollo/release-action@main + with: + tag: ${{ needs.version.outputs.version }} + commit: ${{ github.ref }} + prerelease: false + name: ${{ needs.version.outputs.name }} release ${{ needs.version.outputs.version }} + body: "[Link to crates.io](https://crates.io/crates/${{ needs.version.outputs.name }})" From ba50c1dafdd04b73c070554b4cf69c4987e624ba Mon Sep 17 00:00:00 2001 From: Yuri6037 Date: Sun, 30 Oct 2022 13:46:07 +0100 Subject: [PATCH 02/64] Removed support for GitLab CI --- .gitlab-ci.yml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index e0f98db..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,7 +0,0 @@ -include: - - project: bp3d/cargo-gitlab - file: lib-template.yml - -variables: - CRATE_NAME: bp3d-logger - PROJECT_NAME: Logger From 0f4ef2e20dcf935e94cbdf23191f420fbae18713 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Oct 2022 12:52:40 +0000 Subject: [PATCH 03/64] Format Rust code using rustfmt --- src/backend.rs | 41 +++++++++++++++++++++++++++-------------- src/easy_termcolor.rs | 27 +++++++++++++++++++++------ src/lib.rs | 12 ++++++------ 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index c7a22de..b3d7331 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -26,6 +26,9 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use crate::easy_termcolor::{color, EasyTermColor}; +use crate::Colors; +use atty::Stream; use log::Level; use std::collections::HashMap; use std::fmt::Display; @@ -34,10 +37,7 @@ use std::fs::{File, OpenOptions}; use std::io::{BufWriter, Write}; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; -use atty::Stream; use termcolor::{ColorChoice, ColorSpec, StandardStream}; -use crate::Colors; -use crate::easy_termcolor::{color, EasyTermColor}; pub trait Backend { type Error: Display; @@ -57,19 +57,32 @@ pub static ENABLE_STDOUT: AtomicBool = AtomicBool::new(true); pub struct StdBackend { smart_stderr: bool, - colors: Colors + colors: Colors, } fn write_msg(stream: StandardStream, target: &str, msg: &str, level: Level) { let t = ColorSpec::new().set_bold(true).clone(); - EasyTermColor(stream).write('<').color(t).write(target).reset().write("> ") - .write('[').color(color(level)).write(level).reset().write(']') - .write(format!(" {}", msg)).lf(); + EasyTermColor(stream) + .write('<') + .color(t) + .write(target) + .reset() + .write("> ") + .write('[') + .color(color(level)) + .write(level) + .reset() + .write(']') + .write(format!(" {}", msg)) + .lf(); } impl StdBackend { pub fn new(smart_stderr: bool, colors: Colors) -> StdBackend { - StdBackend { smart_stderr, colors } + StdBackend { + smart_stderr, + colors, + } } fn get_stream(&self, level: Level) -> Stream { @@ -77,8 +90,8 @@ impl StdBackend { false => Stream::Stdout, true => match level { Level::Error => Stream::Stderr, - _ => Stream::Stdout - } + _ => Stream::Stdout, + }, } } } @@ -95,20 +108,20 @@ impl Backend for StdBackend { let use_termcolor = match self.colors { Colors::Disabled => false, Colors::Enabled => true, - Colors::Auto => atty::is(stream) + Colors::Auto => atty::is(stream), }; match use_termcolor { true => { let val = match stream { Stream::Stderr => StandardStream::stderr(ColorChoice::Always), - _ => StandardStream::stdout(ColorChoice::Always) + _ => StandardStream::stdout(ColorChoice::Always), }; write_msg(val, target, msg, level); - }, + } false => { match stream { Stream::Stderr => eprintln!("<{}> [{}] {}", target, level, msg), - _ => println!("<{}> [{}] {}", target, level, msg) + _ => println!("<{}> [{}] {}", target, level, msg), }; } }; diff --git a/src/easy_termcolor.rs b/src/easy_termcolor.rs index 5413a13..3bf6a46 100644 --- a/src/easy_termcolor.rs +++ b/src/easy_termcolor.rs @@ -26,8 +26,8 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::fmt::Display; use log::Level; +use std::fmt::Display; use termcolor::{Color, ColorSpec}; pub struct EasyTermColor(pub T); @@ -56,10 +56,25 @@ impl EasyTermColor { pub fn color(level: Level) -> ColorSpec { match level { - Level::Error => ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true).clone(), - Level::Warn => ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true).clone(), - Level::Info => ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true).clone(), - Level::Debug => ColorSpec::new().set_fg(Some(Color::Blue)).set_bold(true).clone(), - Level::Trace => ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true).clone() + Level::Error => ColorSpec::new() + .set_fg(Some(Color::Red)) + .set_bold(true) + .clone(), + Level::Warn => ColorSpec::new() + .set_fg(Some(Color::Yellow)) + .set_bold(true) + .clone(), + Level::Info => ColorSpec::new() + .set_fg(Some(Color::Green)) + .set_bold(true) + .clone(), + Level::Debug => ColorSpec::new() + .set_fg(Some(Color::Blue)) + .set_bold(true) + .clone(), + Level::Trace => ColorSpec::new() + .set_fg(Some(Color::Cyan)) + .set_bold(true) + .clone(), } } diff --git a/src/lib.rs b/src/lib.rs index 2fe19d2..d4ff517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,16 +31,16 @@ #![allow(clippy::needless_doctest_main)] mod backend; -mod internal; mod easy_termcolor; +mod internal; +use crate::backend::ENABLE_STDOUT; use bp3d_fs::dirs::App; use crossbeam_channel::Receiver; use log::{Level, Log}; use once_cell::sync::Lazy; use std::path::PathBuf; use std::sync::atomic::Ordering; -use crate::backend::ENABLE_STDOUT; /// Represents a log message in the [LogBuffer](crate::LogBuffer). #[derive(Clone)] @@ -96,7 +96,7 @@ pub enum Colors { /// Color printing is automatic (if current terminal is a tty, print with colors, otherwise /// print without colors). - Auto + Auto, } impl Default for Colors { @@ -172,7 +172,7 @@ impl Default for Logger { colors: Colors::default(), smart_stderr: true, std: None, - file: None + file: None, } } } @@ -230,8 +230,8 @@ impl Logger { /// application, only that calling this function may be slow due to thread management. pub fn start(self) -> Guard { let _ = log::set_logger(&*BP3D_LOGGER); // Ignore the error - // (we can't do anything if there's already a logger set; - // unfortunately that is a limitation of the log crate) + // (we can't do anything if there's already a logger set; + // unfortunately that is a limitation of the log crate) BP3D_LOGGER.start_new_thread(self); // Re-start the logging thread with the new configuration. BP3D_LOGGER.enable(true); // Enable logging. From 7bc62cb12d5a116e0591234455865f2843288eae Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 4 Feb 2023 11:40:59 +0100 Subject: [PATCH 04/64] Replaced time_tz by chrono + rewrite logging system to buffer all formatting --- Cargo.toml | 5 +-- src/backend.rs | 6 +-- src/internal.rs | 62 +++++++++++----------------- src/lib.rs | 22 +++------- src/log_msg.rs | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 61 deletions(-) create mode 100644 src/log_msg.rs diff --git a/Cargo.toml b/Cargo.toml index d1a5d95..050614a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-logger" -version = "1.1.0" +version = "2.0.0-pre.1.0.0" authors = ["Yuri Edward "] edition = "2021" description = "A flexible Log implementation intended to be used with BP3D software." @@ -17,8 +17,7 @@ log = "0.4.14" bp3d-fs = "1.0.0" crossbeam-channel = "0.5.2" once_cell = "1.10.0" -time = { version = "0.3.7", features = ["formatting", "macros"] } -time-tz = { version = "0.3.1", features = ["system"] } +chrono = "0.4.23" termcolor = "1.1.3" atty = "0.2.14" diff --git a/src/backend.rs b/src/backend.rs index b3d7331..e690b37 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2021, BlockProject 3D +// Copyright (c) 2023, BlockProject 3D // // All rights reserved. // @@ -72,8 +72,8 @@ fn write_msg(stream: StandardStream, target: &str, msg: &str, level: Level) { .color(color(level)) .write(level) .reset() - .write(']') - .write(format!(" {}", msg)) + .write("] ") + .write(msg) .lf(); } diff --git a/src/internal.rs b/src/internal.rs index 9f8c091..54c83ed 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2021, BlockProject 3D +// Copyright (c) 2023, BlockProject 3D // // All rights reserved. // @@ -32,16 +32,15 @@ use crossbeam_channel::{bounded, Receiver, Sender}; use log::{Level, Log, Metadata, Record}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; -use time::macros::format_description; -use time::OffsetDateTime; -use time_tz::OffsetDateTimeExt; +use chrono::Local; +use std::fmt::Write; -const BUF_SIZE: usize = 128; // The maximum count of log messages in the channel. +const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. enum Command { Flush, Log(LogMsg), - Terminate, + Terminate } fn log( @@ -73,8 +72,11 @@ fn exec_commad(cmd: Command, logger: &mut Logger) -> bool { } false } - Command::Log(LogMsg { target, msg, level }) => { - if let Err(e) = log(logger.file.as_mut(), &target, &msg, level) { + Command::Log(buffer) => { + let target = buffer.target(); + let msg = buffer.msg(); + let level = buffer.level(); + if let Err(e) = log(logger.file.as_mut(), target, msg, level) { let _ = log( logger.std.as_mut(), "bp3d-logger", @@ -82,20 +84,20 @@ fn exec_commad(cmd: Command, logger: &mut Logger) -> bool { Level::Error, ); } - let _ = log(logger.std.as_mut(), &target, &msg, level); + let _ = log(logger.std.as_mut(), target, msg, level); false } } } pub struct LoggerImpl { - thread: Mutex>>, send_ch: Sender, recv_ch: Receiver, + enabled: AtomicBool, log_buffer_send_ch: Sender, log_buffer_recv_ch: Receiver, enable_log_buffer: AtomicBool, - enabled: AtomicBool, + thread: Mutex>> } impl LoggerImpl { @@ -177,17 +179,13 @@ impl LoggerImpl { // This cannot panic as send_ch is owned by LoggerImpl which is intended // to be statically allocated. self.send_ch - .send(Command::Log(LogMsg { - level: Level::Error, - msg: "The logging thread has panicked!".into(), - target: "bp3d-logger".into(), - })) + .send(Command::Log(LogMsg::from_msg("bp3d-logger", Level::Error, "The logging thread has panicked!"))) .unwrap_unchecked(); } } } - pub fn low_level_log(&self, msg: LogMsg) { + pub fn low_level_log(&self, msg: &LogMsg) { if self.enable_log_buffer.load(Ordering::Acquire) { unsafe { // This cannot panic as both send_ch and log_buffer_send_ch are owned by LoggerImpl @@ -195,17 +193,18 @@ impl LoggerImpl { self.send_ch .send(Command::Log(msg.clone())) .unwrap_unchecked(); - self.log_buffer_send_ch.send(msg).unwrap_unchecked(); + self.log_buffer_send_ch.send(msg.clone()).unwrap_unchecked(); } } else { unsafe { // This cannot panic as send_ch is owned by LoggerImpl which is intended // to be statically allocated. - self.send_ch.send(Command::Log(msg)).unwrap_unchecked(); + self.send_ch.send(Command::Log(msg.clone())).unwrap_unchecked(); } } } + #[inline] pub fn is_enabled(&self) -> bool { self.enabled.load(Ordering::Acquire) } @@ -232,26 +231,11 @@ impl Log for LoggerImpl { return; } let (target, module) = extract_target_module(record); - //In the future attempt to not update all the time https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=17c218f318826f55ab64535bfcd28ec6 - let system_tz = - time_tz::system::get_timezone().unwrap_or(time_tz::timezones::db::us::CENTRAL); - let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); - // is very unlikely to occur (only possibility is a weird io error). - let formatted = OffsetDateTime::now_utc() - .to_timezone(system_tz) - .format(format) - .unwrap_or_else(|_| "".into()); - let msg = LogMsg { - msg: format!( - "({}) {}: {}", - formatted, - module.unwrap_or("main"), - record.args() - ), - target: target.into(), - level: record.level(), - }; - self.low_level_log(msg); + let time = Local::now(); + let formatted = time.format("%a %b %d %Y %I:%M:%S %P"); + let mut msg = LogMsg::new(target, record.level()); + let _ = write!(msg, "({}) {}: {}", formatted, module.unwrap_or("main"), record.args()); + self.low_level_log(&msg); } fn flush(&self) { diff --git a/src/lib.rs b/src/lib.rs index d4ff517..244931f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2021, BlockProject 3D +// Copyright (c) 2023, BlockProject 3D // // All rights reserved. // @@ -33,27 +33,17 @@ mod backend; mod easy_termcolor; mod internal; +mod log_msg; use crate::backend::ENABLE_STDOUT; use bp3d_fs::dirs::App; use crossbeam_channel::Receiver; -use log::{Level, Log}; +use log::Log; use once_cell::sync::Lazy; use std::path::PathBuf; use std::sync::atomic::Ordering; -/// Represents a log message in the [LogBuffer](crate::LogBuffer). -#[derive(Clone)] -pub struct LogMsg { - /// The message string. - pub msg: String, - - /// The crate name that issued this log. - pub target: String, - - /// The log level. - pub level: Level, -} +pub use log_msg::LogMsg; /// The log buffer type. pub type LogBuffer = Receiver; @@ -207,7 +197,7 @@ impl Logger { /// Enables file logging to the given application. /// - /// The application is given as a reference to [GetLogs](crate::GetLogs) to allow obtaining + /// The application is given as a reference to [GetLogs](GetLogs) to allow obtaining /// a log directory from various sources. /// /// If the log directory could not be found the function prints an error to stderr. @@ -299,7 +289,7 @@ pub fn get_log_buffer() -> LogBuffer { /// - For stdout/stderr backend the format is \[level\] msg /// - For file backend the format is \[level\] msg and the message is recorded in the file /// corresponding to the log target. -pub fn raw_log(msg: LogMsg) { +pub fn raw_log(msg: &LogMsg) { BP3D_LOGGER.low_level_log(msg) } diff --git a/src/log_msg.rs b/src/log_msg.rs new file mode 100644 index 0000000..52153f4 --- /dev/null +++ b/src/log_msg.rs @@ -0,0 +1,107 @@ +// Copyright (c) 2023, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::fmt::{Error, Write}; +use std::mem::MaybeUninit; +use log::Level; + +// Limit the size of the target string to 16 bytes. +const LOG_TARGET_SIZE: usize = 16; +// Size of the control fields of the log message structure: +// sizeof Level + 1 byte for target_len + sizeof msg_len +const LOG_CONTROL_SIZE: usize = std::mem::size_of::() + std::mem::size_of::() + 1; +// Limit the size of the log message string so that the size of the log structure is LOG_BUFFER_SIZE +const LOG_MSG_SIZE: usize = LOG_BUFFER_SIZE - LOG_TARGET_SIZE - LOG_CONTROL_SIZE; +const LOG_BUFFER_SIZE: usize = 1024; + +#[derive(Clone)] +pub struct LogMsg { + buffer: [MaybeUninit; LOG_BUFFER_SIZE], + level: Level, + msg_len: u32, + target_len: u8 +} + +impl LogMsg { + pub fn new(target: &str, level: Level) -> LogMsg { + let len = std::cmp::min(LOG_TARGET_SIZE, target.as_bytes().len()); + let mut buffer = LogMsg { + buffer: unsafe { MaybeUninit::uninit().assume_init() }, + target_len: len as _, + msg_len: len as _, + level + }; + unsafe { + std::ptr::copy_nonoverlapping(target.as_bytes().as_ptr(), std::mem::transmute(buffer.buffer.as_mut_ptr()), len); + } + buffer + } + + pub fn clear(&mut self) { + self.msg_len = self.target_len as _; + } + + pub fn from_msg(target: &str, level: Level, msg: &str) -> LogMsg { + let mut ads = Self::new(target, level); + unsafe { ads.write(msg.as_bytes()) }; + ads + } + + // SAFETY: BufLogMsg must always contain valid UTF-8 so ensure that buf only contains valid UTF-8 data. + pub unsafe fn write(&mut self, buf: &[u8]) -> usize { + let len = std::cmp::min(buf.len(), LOG_MSG_SIZE - self.msg_len as usize); + if len > 0 { + std::ptr::copy_nonoverlapping(buf.as_ptr(), std::mem::transmute(self.buffer.as_mut_ptr().offset(self.msg_len as _)), len); + self.msg_len += len as u32; //The length is always less than 2^32. + } + len + } + + pub fn target(&self) -> &str { + // SAFEY: This is always safe because BufLogMsg is always UTF-8. + unsafe { std::str::from_utf8_unchecked(std::mem::transmute(&self.buffer[..self.target_len as _])) } + } + + pub fn msg(&self) -> &str { + // SAFEY: This is always safe because BufLogMsg is always UTF-8. + unsafe { std::str::from_utf8_unchecked(std::mem::transmute(&self.buffer[self.target_len as _..self.msg_len as _])) } + } + + pub fn level(&self) -> Level { + self.level + } +} + +impl Write for LogMsg { + fn write_str(&mut self, s: &str) -> Result<(), Error> { + unsafe { + self.write(s.as_bytes()); + } + Ok(()) + } +} From d7f9e8666cc24c0784698a5950074bbac841996e Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 4 Feb 2023 12:49:08 +0100 Subject: [PATCH 05/64] Updated version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 050614a..5503d90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-logger" -version = "2.0.0-pre.1.0.0" +version = "2.0.0-pre.2.0.0" authors = ["Yuri Edward "] edition = "2021" description = "A flexible Log implementation intended to be used with BP3D software." From 86350b657fc32f33dcfe8e74f9464a6ab8458b4d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 4 Feb 2023 11:52:48 +0000 Subject: [PATCH 06/64] Format Rust code using rustfmt --- src/internal.rs | 26 +++++++++++++++++++------- src/log_msg.rs | 28 +++++++++++++++++++++------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/internal.rs b/src/internal.rs index 54c83ed..c9ca4cc 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -28,19 +28,19 @@ use crate::backend::Backend; use crate::{LogMsg, Logger}; +use chrono::Local; use crossbeam_channel::{bounded, Receiver, Sender}; use log::{Level, Log, Metadata, Record}; +use std::fmt::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; -use chrono::Local; -use std::fmt::Write; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. enum Command { Flush, Log(LogMsg), - Terminate + Terminate, } fn log( @@ -97,7 +97,7 @@ pub struct LoggerImpl { log_buffer_send_ch: Sender, log_buffer_recv_ch: Receiver, enable_log_buffer: AtomicBool, - thread: Mutex>> + thread: Mutex>>, } impl LoggerImpl { @@ -179,7 +179,11 @@ impl LoggerImpl { // This cannot panic as send_ch is owned by LoggerImpl which is intended // to be statically allocated. self.send_ch - .send(Command::Log(LogMsg::from_msg("bp3d-logger", Level::Error, "The logging thread has panicked!"))) + .send(Command::Log(LogMsg::from_msg( + "bp3d-logger", + Level::Error, + "The logging thread has panicked!", + ))) .unwrap_unchecked(); } } @@ -199,7 +203,9 @@ impl LoggerImpl { unsafe { // This cannot panic as send_ch is owned by LoggerImpl which is intended // to be statically allocated. - self.send_ch.send(Command::Log(msg.clone())).unwrap_unchecked(); + self.send_ch + .send(Command::Log(msg.clone())) + .unwrap_unchecked(); } } } @@ -234,7 +240,13 @@ impl Log for LoggerImpl { let time = Local::now(); let formatted = time.format("%a %b %d %Y %I:%M:%S %P"); let mut msg = LogMsg::new(target, record.level()); - let _ = write!(msg, "({}) {}: {}", formatted, module.unwrap_or("main"), record.args()); + let _ = write!( + msg, + "({}) {}: {}", + formatted, + module.unwrap_or("main"), + record.args() + ); self.low_level_log(&msg); } diff --git a/src/log_msg.rs b/src/log_msg.rs index 52153f4..0080e27 100644 --- a/src/log_msg.rs +++ b/src/log_msg.rs @@ -26,9 +26,9 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use log::Level; use std::fmt::{Error, Write}; use std::mem::MaybeUninit; -use log::Level; // Limit the size of the target string to 16 bytes. const LOG_TARGET_SIZE: usize = 16; @@ -44,7 +44,7 @@ pub struct LogMsg { buffer: [MaybeUninit; LOG_BUFFER_SIZE], level: Level, msg_len: u32, - target_len: u8 + target_len: u8, } impl LogMsg { @@ -54,10 +54,14 @@ impl LogMsg { buffer: unsafe { MaybeUninit::uninit().assume_init() }, target_len: len as _, msg_len: len as _, - level + level, }; unsafe { - std::ptr::copy_nonoverlapping(target.as_bytes().as_ptr(), std::mem::transmute(buffer.buffer.as_mut_ptr()), len); + std::ptr::copy_nonoverlapping( + target.as_bytes().as_ptr(), + std::mem::transmute(buffer.buffer.as_mut_ptr()), + len, + ); } buffer } @@ -76,7 +80,11 @@ impl LogMsg { pub unsafe fn write(&mut self, buf: &[u8]) -> usize { let len = std::cmp::min(buf.len(), LOG_MSG_SIZE - self.msg_len as usize); if len > 0 { - std::ptr::copy_nonoverlapping(buf.as_ptr(), std::mem::transmute(self.buffer.as_mut_ptr().offset(self.msg_len as _)), len); + std::ptr::copy_nonoverlapping( + buf.as_ptr(), + std::mem::transmute(self.buffer.as_mut_ptr().offset(self.msg_len as _)), + len, + ); self.msg_len += len as u32; //The length is always less than 2^32. } len @@ -84,12 +92,18 @@ impl LogMsg { pub fn target(&self) -> &str { // SAFEY: This is always safe because BufLogMsg is always UTF-8. - unsafe { std::str::from_utf8_unchecked(std::mem::transmute(&self.buffer[..self.target_len as _])) } + unsafe { + std::str::from_utf8_unchecked(std::mem::transmute(&self.buffer[..self.target_len as _])) + } } pub fn msg(&self) -> &str { // SAFEY: This is always safe because BufLogMsg is always UTF-8. - unsafe { std::str::from_utf8_unchecked(std::mem::transmute(&self.buffer[self.target_len as _..self.msg_len as _])) } + unsafe { + std::str::from_utf8_unchecked(std::mem::transmute( + &self.buffer[self.target_len as _..self.msg_len as _], + )) + } } pub fn level(&self) -> Level { From b05170b621116fc0502763bc883600ef7f8104be Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 4 Feb 2023 21:01:48 +0100 Subject: [PATCH 07/64] Improved performance of the logging system by moving the log buffer to the async logging thread --- Cargo.toml | 3 +- src/internal.rs | 147 +++++++++++++++++++++++++++++------------------- src/lib.rs | 12 ++-- 3 files changed, 97 insertions(+), 65 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5503d90..fe540e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-logger" -version = "2.0.0-pre.2.0.0" +version = "2.0.0-pre.3.0.0" authors = ["Yuri Edward "] edition = "2021" description = "A flexible Log implementation intended to be used with BP3D software." @@ -16,6 +16,7 @@ categories = [] log = "0.4.14" bp3d-fs = "1.0.0" crossbeam-channel = "0.5.2" +crossbeam-queue = "0.3.8" once_cell = "1.10.0" chrono = "0.4.23" termcolor = "1.1.3" diff --git a/src/internal.rs b/src/internal.rs index 54c83ed..51f06bf 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -31,16 +31,19 @@ use crate::{LogMsg, Logger}; use crossbeam_channel::{bounded, Receiver, Sender}; use log::{Level, Log, Metadata, Record}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use chrono::Local; use std::fmt::Write; +use crossbeam_queue::ArrayQueue; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. enum Command { Flush, Log(LogMsg), - Terminate + Terminate, + EnableLogBuffer, + DisableLogBuffer } fn log( @@ -56,61 +59,95 @@ fn log( } } -fn exec_commad(cmd: Command, logger: &mut Logger) -> bool { - match cmd { - Command::Terminate => true, - Command::Flush => { - if let Some(file) = &mut logger.file { - if let Err(e) = file.flush() { +struct Thread { + logger: Logger, + recv_ch: Receiver, + enable_log_buffer: bool, + log_buffer: Arc> +} + +impl Thread { + pub fn new(logger: Logger, recv_ch: Receiver, log_buffer: Arc>) -> Thread { + Thread { + logger, + recv_ch, + enable_log_buffer: false, + log_buffer + } + } + + fn exec_commad(&mut self, cmd: Command) -> bool { + match cmd { + Command::Terminate => true, + Command::Flush => { + if let Some(file) = &mut self.logger.file { + if let Err(e) = file.flush() { + let _ = log( + self.logger.std.as_mut(), + "bp3d-logger", + &format!("Could not flush file backend: {}", e), + Level::Error, + ); + } + } + false + } + Command::Log(buffer) => { + let target = buffer.target(); + let msg = buffer.msg(); + let level = buffer.level(); + if let Err(e) = log(self.logger.file.as_mut(), target, msg, level) { let _ = log( - logger.std.as_mut(), + self.logger.std.as_mut(), "bp3d-logger", - &format!("Could not flush file backend: {}", e), + &format!("Could not write to file backend: {}", e), Level::Error, ); } + let _ = log(self.logger.std.as_mut(), target, msg, level); + if self.enable_log_buffer { + self.log_buffer.force_push(buffer); + } + false + } + Command::EnableLogBuffer => { + self.enable_log_buffer = true; + false + }, + Command::DisableLogBuffer => { + self.enable_log_buffer = false; + false } - false } - Command::Log(buffer) => { - let target = buffer.target(); - let msg = buffer.msg(); - let level = buffer.level(); - if let Err(e) = log(logger.file.as_mut(), target, msg, level) { - let _ = log( - logger.std.as_mut(), - "bp3d-logger", - &format!("Could not write to file backend: {}", e), - Level::Error, - ); + } + + pub fn run(mut self) { + while let Ok(v) = self.recv_ch.recv() { + let flag = self.exec_commad(v); + if flag { + // The thread has requested to exit itself; drop out of the main loop. + break; } - let _ = log(logger.std.as_mut(), target, msg, level); - false } } } pub struct LoggerImpl { send_ch: Sender, - recv_ch: Receiver, enabled: AtomicBool, - log_buffer_send_ch: Sender, - log_buffer_recv_ch: Receiver, - enable_log_buffer: AtomicBool, + recv_ch: Receiver, + log_buffer: Arc>, thread: Mutex>> } impl LoggerImpl { pub fn new() -> LoggerImpl { let (send_ch, recv_ch) = bounded(BUF_SIZE); - let (log_buffer_send_ch, log_buffer_recv_ch) = bounded(BUF_SIZE); LoggerImpl { thread: Mutex::new(None), send_ch, recv_ch, - log_buffer_send_ch, - log_buffer_recv_ch, - enable_log_buffer: AtomicBool::new(false), + log_buffer: Arc::new(ArrayQueue::new(BUF_SIZE)), enabled: AtomicBool::new(false), } } @@ -120,15 +157,23 @@ impl LoggerImpl { } pub fn enable_log_buffer(&self, flag: bool) { - self.enable_log_buffer.store(flag, Ordering::Release); + unsafe { + if flag { + self.send_ch.send(Command::EnableLogBuffer).unwrap_unchecked(); + } else { + self.send_ch.send(Command::DisableLogBuffer).unwrap_unchecked(); + } + } } + #[inline] pub fn clear_log_buffer(&self) { - while self.log_buffer_recv_ch.try_recv().is_ok() {} //Clear the entire log buffer. + while self.log_buffer.pop().is_some() {} //Clear the entire log buffer. } - pub fn get_log_buffer(&self) -> Receiver { - self.log_buffer_recv_ch.clone() + #[inline] + pub fn read_log(&self) -> Option { + self.log_buffer.pop() } pub fn terminate(&self) { @@ -162,15 +207,10 @@ impl LoggerImpl { } } let recv_ch = self.recv_ch.clone(); + let log_buffer = self.log_buffer.clone(); *thread = Some(std::thread::spawn(move || { - let mut logger = logger; - while let Ok(v) = recv_ch.recv() { - let flag = exec_commad(v, &mut logger); - if flag { - // The thread has requested to exit itself; drop out of the main loop. - break; - } - } + let thread = Thread::new(logger, recv_ch, log_buffer); + thread.run(); })); } if flag { @@ -186,21 +226,10 @@ impl LoggerImpl { } pub fn low_level_log(&self, msg: &LogMsg) { - if self.enable_log_buffer.load(Ordering::Acquire) { - unsafe { - // This cannot panic as both send_ch and log_buffer_send_ch are owned by LoggerImpl - // which is intended to be statically allocated. - self.send_ch - .send(Command::Log(msg.clone())) - .unwrap_unchecked(); - self.log_buffer_send_ch.send(msg.clone()).unwrap_unchecked(); - } - } else { - unsafe { - // This cannot panic as send_ch is owned by LoggerImpl which is intended - // to be statically allocated. - self.send_ch.send(Command::Log(msg.clone())).unwrap_unchecked(); - } + unsafe { + // This cannot panic as send_ch is owned by LoggerImpl which is intended + // to be statically allocated. + self.send_ch.send(Command::Log(msg.clone())).unwrap_unchecked(); } } diff --git a/src/lib.rs b/src/lib.rs index 244931f..7461dad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,8 +143,10 @@ impl Default for Colors { /// bp3d_logger::enable_log_buffer(); // Enable log redirect pump into application channel. /// //... application code with log redirect pump. /// info!("Example message"); -/// let l = bp3d_logger::get_log_buffer().recv().unwrap();// Capture the last log message. -/// println!("Last log message: {}", l.msg); +/// bp3d_logger::flush(); +/// let l = bp3d_logger::read_log().unwrap();// Capture the last log message. +/// //We can't test for equality because log messages contains a timestamp... +/// assert!(l.msg().ends_with("Example message")); /// bp3d_logger::disable_log_buffer(); /// //... application code without log redirect pump. /// } @@ -278,9 +280,9 @@ pub fn disable_stdout() { ENABLE_STDOUT.store(false, Ordering::Release); } -/// Returns the buffer from the log redirect pump. -pub fn get_log_buffer() -> LogBuffer { - BP3D_LOGGER.get_log_buffer() +/// Attempts to extract one log message from the buffer. +pub fn read_log() -> Option { + BP3D_LOGGER.read_log() } /// Low-level log function. This injects log messages directly into the logging thread channel. From e706d9ddf6b4da4f9fe50087b819df0684e6b13c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 4 Feb 2023 20:13:13 +0000 Subject: [PATCH 08/64] Format Rust code using rustfmt --- src/internal.rs | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/internal.rs b/src/internal.rs index 9481b47..9b0a20d 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -28,13 +28,13 @@ use crate::backend::Backend; use crate::{LogMsg, Logger}; +use chrono::Local; use crossbeam_channel::{bounded, Receiver, Sender}; +use crossbeam_queue::ArrayQueue; use log::{Level, Log, Metadata, Record}; +use std::fmt::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use chrono::Local; -use std::fmt::Write; -use crossbeam_queue::ArrayQueue; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. @@ -43,7 +43,7 @@ enum Command { Log(LogMsg), Terminate, EnableLogBuffer, - DisableLogBuffer + DisableLogBuffer, } fn log( @@ -63,16 +63,20 @@ struct Thread { logger: Logger, recv_ch: Receiver, enable_log_buffer: bool, - log_buffer: Arc> + log_buffer: Arc>, } impl Thread { - pub fn new(logger: Logger, recv_ch: Receiver, log_buffer: Arc>) -> Thread { + pub fn new( + logger: Logger, + recv_ch: Receiver, + log_buffer: Arc>, + ) -> Thread { Thread { logger, recv_ch, enable_log_buffer: false, - log_buffer + log_buffer, } } @@ -113,7 +117,7 @@ impl Thread { Command::EnableLogBuffer => { self.enable_log_buffer = true; false - }, + } Command::DisableLogBuffer => { self.enable_log_buffer = false; false @@ -137,7 +141,7 @@ pub struct LoggerImpl { enabled: AtomicBool, recv_ch: Receiver, log_buffer: Arc>, - thread: Mutex>> + thread: Mutex>>, } impl LoggerImpl { @@ -159,9 +163,13 @@ impl LoggerImpl { pub fn enable_log_buffer(&self, flag: bool) { unsafe { if flag { - self.send_ch.send(Command::EnableLogBuffer).unwrap_unchecked(); + self.send_ch + .send(Command::EnableLogBuffer) + .unwrap_unchecked(); } else { - self.send_ch.send(Command::DisableLogBuffer).unwrap_unchecked(); + self.send_ch + .send(Command::DisableLogBuffer) + .unwrap_unchecked(); } } } @@ -233,7 +241,9 @@ impl LoggerImpl { unsafe { // This cannot panic as send_ch is owned by LoggerImpl which is intended // to be statically allocated. - self.send_ch.send(Command::Log(msg.clone())).unwrap_unchecked(); + self.send_ch + .send(Command::Log(msg.clone())) + .unwrap_unchecked(); } } @@ -267,7 +277,13 @@ impl Log for LoggerImpl { let time = Local::now(); let formatted = time.format("%a %b %d %Y %I:%M:%S %P"); let mut msg = LogMsg::new(target, record.level()); - let _ = write!(msg, "({}) {}: {}", formatted, module.unwrap_or("main"), record.args()); + let _ = write!( + msg, + "({}) {}: {}", + formatted, + module.unwrap_or("main"), + record.args() + ); self.low_level_log(&msg); } From a8f122592bc7f9429a3df83ad6b49138cdf8bf6e Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sun, 5 Feb 2023 09:53:47 +0100 Subject: [PATCH 09/64] Updated version information --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fe540e5..486676e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-logger" -version = "2.0.0-pre.3.0.0" +version = "2.0.0-rc.1.0.0" authors = ["Yuri Edward "] edition = "2021" description = "A flexible Log implementation intended to be used with BP3D software." From 22be54703bfcd4954fc18913a12ecdfe2e19431c Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sun, 5 Feb 2023 10:36:45 +0100 Subject: [PATCH 10/64] Improved order and size of the LogMsg structure fields --- src/log_msg.rs | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/log_msg.rs b/src/log_msg.rs index 0080e27..6b588f2 100644 --- a/src/log_msg.rs +++ b/src/log_msg.rs @@ -33,18 +33,43 @@ use std::mem::MaybeUninit; // Limit the size of the target string to 16 bytes. const LOG_TARGET_SIZE: usize = 16; // Size of the control fields of the log message structure: -// sizeof Level + 1 byte for target_len + sizeof msg_len -const LOG_CONTROL_SIZE: usize = std::mem::size_of::() + std::mem::size_of::() + 1; +// sizeof msg_len + 1 byte for target_len + 1 byte for level +const LOG_CONTROL_SIZE: usize = std::mem::size_of::() + 2; // Limit the size of the log message string so that the size of the log structure is LOG_BUFFER_SIZE const LOG_MSG_SIZE: usize = LOG_BUFFER_SIZE - LOG_TARGET_SIZE - LOG_CONTROL_SIZE; const LOG_BUFFER_SIZE: usize = 1024; +#[inline] +fn log_to_u8(level: Level) -> u8 { + match level { + Level::Error => 0, + Level::Warn => 1, + Level::Info => 2, + Level::Debug => 3, + Level::Trace => 4 + } +} + +#[inline] +fn u8_to_log(l: u8) -> Level { + match l { + 0 => Level::Error, + 1 => Level::Warn, + 3 => Level::Debug, + 4 => Level::Trace, + _ => Level::Info + } +} + #[derive(Clone)] +// This repr is to force msg_len, level and target_len to appear first so that we don't +// have to mov something after the 1K buffer. +#[repr(C)] pub struct LogMsg { - buffer: [MaybeUninit; LOG_BUFFER_SIZE], - level: Level, msg_len: u32, + level: u8, target_len: u8, + buffer: [MaybeUninit; LOG_MSG_SIZE + LOG_TARGET_SIZE] } impl LogMsg { @@ -54,7 +79,7 @@ impl LogMsg { buffer: unsafe { MaybeUninit::uninit().assume_init() }, target_len: len as _, msg_len: len as _, - level, + level: log_to_u8(level), }; unsafe { std::ptr::copy_nonoverlapping( @@ -66,6 +91,7 @@ impl LogMsg { buffer } + #[inline] pub fn clear(&mut self) { self.msg_len = self.target_len as _; } @@ -90,6 +116,7 @@ impl LogMsg { len } + #[inline] pub fn target(&self) -> &str { // SAFEY: This is always safe because BufLogMsg is always UTF-8. unsafe { @@ -97,6 +124,7 @@ impl LogMsg { } } + #[inline] pub fn msg(&self) -> &str { // SAFEY: This is always safe because BufLogMsg is always UTF-8. unsafe { @@ -106,8 +134,9 @@ impl LogMsg { } } + #[inline] pub fn level(&self) -> Level { - self.level + u8_to_log(self.level) } } From 5b7362c0d0d9e7b6bea4d23425a8d9f9142121da Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sun, 5 Feb 2023 11:05:01 +0100 Subject: [PATCH 11/64] Added missing docs --- src/lib.rs | 4 +++ src/log_msg.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7461dad..92c7076 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,10 @@ // some context to not make it confusing. #![allow(clippy::needless_doctest_main)] +#![warn(missing_docs)] + +//! An async flexible Log implementation intended to be used with BP3D software. + mod backend; mod easy_termcolor; mod internal; diff --git a/src/log_msg.rs b/src/log_msg.rs index 6b588f2..641800b 100644 --- a/src/log_msg.rs +++ b/src/log_msg.rs @@ -61,9 +61,25 @@ fn u8_to_log(l: u8) -> Level { } } +/// A log message. +/// +/// This structure uses a large 1K buffer which stores the entire log message to improve +/// performance. +/// +/// The repr(C) is used to force the control fields (msg_len, level and target_len) to be before +/// the message buffer and avoid large movs when setting control fields. +/// +/// # Examples +/// +/// ``` +/// use log::Level; +/// use bp3d_logger::LogMsg; +/// use std::fmt::Write; +/// let mut msg = LogMsg::new("test", Level::Info); +/// let _ = write!(msg, "This is a formatted message {}", 42); +/// assert_eq!(msg.msg(), "This is a formatted message 42"); +/// ``` #[derive(Clone)] -// This repr is to force msg_len, level and target_len to appear first so that we don't -// have to mov something after the 1K buffer. #[repr(C)] pub struct LogMsg { msg_len: u32, @@ -73,6 +89,24 @@ pub struct LogMsg { } impl LogMsg { + /// Creates a new instance of log message buffer. + /// + /// # Arguments + /// + /// * `target`: the target name this log comes from. + /// * `level`: the [Level](Level) of the log message. + /// + /// returns: LogMsg + /// + /// # Examples + /// + /// ``` + /// use log::Level; + /// use bp3d_logger::LogMsg; + /// let msg = LogMsg::new("test", Level::Info); + /// assert_eq!(msg.target(), "test"); + /// assert_eq!(msg.level(), Level::Info); + /// ``` pub fn new(target: &str, level: Level) -> LogMsg { let len = std::cmp::min(LOG_TARGET_SIZE, target.as_bytes().len()); let mut buffer = LogMsg { @@ -91,18 +125,68 @@ impl LogMsg { buffer } + /// Clears the log message but keep the target and the level. + /// + /// # Examples + /// + /// ``` + /// use log::Level; + /// use bp3d_logger::LogMsg; + /// let mut msg = LogMsg::from_msg("test", Level::Info, "this is a test"); + /// msg.clear(); + /// assert_eq!(msg.msg(), ""); + /// assert_eq!(msg.target(), "test"); + /// assert_eq!(msg.level(), Level::Info); + /// ``` #[inline] pub fn clear(&mut self) { self.msg_len = self.target_len as _; } + /// Auto-creates a new log message with a pre-defined string message. + /// + /// This function is the same as calling [write](LogMsg::write) after [new](LogMsg::new). + /// + /// # Arguments + /// + /// * `target`: the target name this log comes from. + /// * `level`: the [Level](Level) of the log message. + /// * `msg`: the message string. + /// + /// returns: LogMsg + /// + /// # Examples + /// + /// ``` + /// use log::Level; + /// use bp3d_logger::LogMsg; + /// let mut msg = LogMsg::from_msg("test", Level::Info, "this is a test"); + /// assert_eq!(msg.target(), "test"); + /// assert_eq!(msg.level(), Level::Info); + /// assert_eq!(msg.msg(), "this is a test"); + /// ``` pub fn from_msg(target: &str, level: Level, msg: &str) -> LogMsg { let mut ads = Self::new(target, level); unsafe { ads.write(msg.as_bytes()) }; ads } - // SAFETY: BufLogMsg must always contain valid UTF-8 so ensure that buf only contains valid UTF-8 data. + /// Appends a raw byte buffer at the end of the message buffer. + /// + /// Returns the number of bytes written. + /// + /// # Arguments + /// + /// * `buf`: the raw byte buffer to append. + /// + /// returns: usize + /// + /// # Safety + /// + /// * [LogMsg](LogMsg) contains only valid UTF-8 strings so buf must contain only valid UTF-8 + /// bytes. + /// * If buf contains invalid UTF-8 bytes, further operations on the log message buffer may + /// result in UB. pub unsafe fn write(&mut self, buf: &[u8]) -> usize { let len = std::cmp::min(buf.len(), LOG_MSG_SIZE - self.msg_len as usize); if len > 0 { @@ -116,6 +200,7 @@ impl LogMsg { len } + /// Returns the target name this log comes from. #[inline] pub fn target(&self) -> &str { // SAFEY: This is always safe because BufLogMsg is always UTF-8. @@ -124,6 +209,7 @@ impl LogMsg { } } + /// Returns the log message as a string. #[inline] pub fn msg(&self) -> &str { // SAFEY: This is always safe because BufLogMsg is always UTF-8. @@ -134,6 +220,7 @@ impl LogMsg { } } + /// Returns the level of this log message. #[inline] pub fn level(&self) -> Level { u8_to_log(self.level) From 37606251dea4fd63403eba5d9612611f4e137f77 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 10:11:16 +0000 Subject: [PATCH 12/64] Format Rust code using rustfmt --- src/lib.rs | 1 - src/log_msg.rs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 92c7076..84706e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,6 @@ // The reason why this is needed is because the 3 examples of usage of the Logger struct requires // some context to not make it confusing. #![allow(clippy::needless_doctest_main)] - #![warn(missing_docs)] //! An async flexible Log implementation intended to be used with BP3D software. diff --git a/src/log_msg.rs b/src/log_msg.rs index 641800b..f13b6b1 100644 --- a/src/log_msg.rs +++ b/src/log_msg.rs @@ -46,7 +46,7 @@ fn log_to_u8(level: Level) -> u8 { Level::Warn => 1, Level::Info => 2, Level::Debug => 3, - Level::Trace => 4 + Level::Trace => 4, } } @@ -57,7 +57,7 @@ fn u8_to_log(l: u8) -> Level { 1 => Level::Warn, 3 => Level::Debug, 4 => Level::Trace, - _ => Level::Info + _ => Level::Info, } } @@ -85,7 +85,7 @@ pub struct LogMsg { msg_len: u32, level: u8, target_len: u8, - buffer: [MaybeUninit; LOG_MSG_SIZE + LOG_TARGET_SIZE] + buffer: [MaybeUninit; LOG_MSG_SIZE + LOG_TARGET_SIZE], } impl LogMsg { From 8433b04d84593aa4326473fc9aae6caf50b4056f Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Tue, 1 Aug 2023 20:42:52 +0200 Subject: [PATCH 13/64] Replaced bp3d-fs by bp3d-os --- Cargo.toml | 2 +- src/lib.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 486676e..061ae40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ categories = [] [dependencies] log = "0.4.14" -bp3d-fs = "1.0.0" +bp3d-os = { version = "1.0.0-rc.1.0.0", features=["dirs"] } crossbeam-channel = "0.5.2" crossbeam-queue = "0.3.8" once_cell = "1.10.0" diff --git a/src/lib.rs b/src/lib.rs index 84706e0..79db955 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,7 +39,7 @@ mod internal; mod log_msg; use crate::backend::ENABLE_STDOUT; -use bp3d_fs::dirs::App; +use bp3d_os::dirs::App; use crossbeam_channel::Receiver; use log::Log; use once_cell::sync::Lazy; @@ -51,7 +51,7 @@ pub use log_msg::LogMsg; /// The log buffer type. pub type LogBuffer = Receiver; -/// Trait to allow getting a log directory from either a bp3d_fs::dirs::App or a String. +/// Trait to allow getting a log directory from either a bp3d_os::dirs::App or a String. pub trait GetLogs { /// Gets the log directory as a PathBuf. /// @@ -67,14 +67,14 @@ impl<'a> GetLogs for &'a String { impl<'a, 'b> GetLogs for &'a App<'b> { fn get_logs(self) -> Option { - self.get_logs().map(|v| v.into()).ok() + self.get_logs().map(|v| v.as_ref().into()) } } impl<'a> GetLogs for &'a str { fn get_logs(self) -> Option { let app = App::new(self); - app.get_logs().map(|v| v.into()).ok() + app.get_logs().map(|v| v.as_ref().into()) } } From b8cdcb632a2e5475c2986fb4fc5ff193d000a6c8 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Tue, 1 Aug 2023 23:55:33 +0200 Subject: [PATCH 14/64] Added support for auto-creating log directory --- Cargo.toml | 2 +- src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 061ae40..92d932c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ categories = [] [dependencies] log = "0.4.14" -bp3d-os = { version = "1.0.0-rc.1.0.0", features=["dirs"] } +bp3d-os = { version = "1.0.0-rc.1.1.0", features=["dirs"] } crossbeam-channel = "0.5.2" crossbeam-queue = "0.3.8" once_cell = "1.10.0" diff --git a/src/lib.rs b/src/lib.rs index 79db955..deef19f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,14 +67,14 @@ impl<'a> GetLogs for &'a String { impl<'a, 'b> GetLogs for &'a App<'b> { fn get_logs(self) -> Option { - self.get_logs().map(|v| v.as_ref().into()) + self.get_logs().map(|v| v.create())?.ok().map(|v| v.into()) } } impl<'a> GetLogs for &'a str { fn get_logs(self) -> Option { let app = App::new(self); - app.get_logs().map(|v| v.as_ref().into()) + app.get_logs().map(|v| v.create())?.ok().map(|v| v.into()) } } From 15b098f7989440ae837aa2db19bbff16c1a17f4c Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Tue, 8 Aug 2023 14:18:15 +0200 Subject: [PATCH 15/64] Fixed doc --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index deef19f..a704bcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -291,7 +291,7 @@ pub fn read_log() -> Option { /// Low-level log function. This injects log messages directly into the logging thread channel. /// /// This function applies basic formatting depending on the backend: -/// - For stdout/stderr backend the format is \[level\] msg +/// - For stdout/stderr backend the format is \ \[level\] msg /// - For file backend the format is \[level\] msg and the message is recorded in the file /// corresponding to the log target. pub fn raw_log(msg: &LogMsg) { From 2dadee9a73a546ed7ac463fc5f5ace9167c77ba7 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Wed, 9 Aug 2023 14:26:38 +0200 Subject: [PATCH 16/64] Reverted chrono back to time thanks to new bp3d-os time feature --- Cargo.toml | 4 ++-- src/internal.rs | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 92d932c..b4af5fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,11 @@ categories = [] [dependencies] log = "0.4.14" -bp3d-os = { version = "1.0.0-rc.1.1.0", features=["dirs"] } +bp3d-os = { version = "1.0.0-rc.1.2.0", features=["dirs", "time"] } crossbeam-channel = "0.5.2" crossbeam-queue = "0.3.8" once_cell = "1.10.0" -chrono = "0.4.23" +time = { version = "0.3.0", features = ["formatting", "macros"] } termcolor = "1.1.3" atty = "0.2.14" diff --git a/src/internal.rs b/src/internal.rs index 9b0a20d..5851ce9 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -28,10 +28,12 @@ use crate::backend::Backend; use crate::{LogMsg, Logger}; -use chrono::Local; +use bp3d_os::time::LocalOffsetDateTime; use crossbeam_channel::{bounded, Receiver, Sender}; use crossbeam_queue::ArrayQueue; use log::{Level, Log, Metadata, Record}; +use time::OffsetDateTime; +use time::macros::format_description; use std::fmt::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; @@ -274,8 +276,9 @@ impl Log for LoggerImpl { return; } let (target, module) = extract_target_module(record); - let time = Local::now(); - let formatted = time.format("%a %b %d %Y %I:%M:%S %P"); + let time = OffsetDateTime::now_local(); + let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); + let formatted = time.unwrap_or_else(OffsetDateTime::now_utc).format(format).unwrap_or_default(); let mut msg = LogMsg::new(target, record.level()); let _ = write!( msg, From 25f4a87953e6f43756b6b8c20b1c9ad18f4cf415 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:30:12 +0000 Subject: [PATCH 17/64] Format Rust code using rustfmt --- src/internal.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/internal.rs b/src/internal.rs index 5851ce9..548cfb7 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -32,11 +32,11 @@ use bp3d_os::time::LocalOffsetDateTime; use crossbeam_channel::{bounded, Receiver, Sender}; use crossbeam_queue::ArrayQueue; use log::{Level, Log, Metadata, Record}; -use time::OffsetDateTime; -use time::macros::format_description; use std::fmt::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; +use time::macros::format_description; +use time::OffsetDateTime; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. @@ -278,7 +278,10 @@ impl Log for LoggerImpl { let (target, module) = extract_target_module(record); let time = OffsetDateTime::now_local(); let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); - let formatted = time.unwrap_or_else(OffsetDateTime::now_utc).format(format).unwrap_or_default(); + let formatted = time + .unwrap_or_else(OffsetDateTime::now_utc) + .format(format) + .unwrap_or_default(); let mut msg = LogMsg::new(target, record.level()); let _ = write!( msg, From 19659754dfdab9e58a9a40717f8befb5ee3972f8 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Wed, 9 Aug 2023 14:55:24 +0200 Subject: [PATCH 18/64] Attempt at fixing weird illegal instruction --- src/internal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal.rs b/src/internal.rs index 5851ce9..2d48cf3 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -276,7 +276,7 @@ impl Log for LoggerImpl { return; } let (target, module) = extract_target_module(record); - let time = OffsetDateTime::now_local(); + let time = Some(OffsetDateTime::now_utc()); let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); let formatted = time.unwrap_or_else(OffsetDateTime::now_utc).format(format).unwrap_or_default(); let mut msg = LogMsg::new(target, record.level()); From e717322f789ab160571258cb0d2f7cf72001fdfe Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Wed, 9 Aug 2023 15:01:03 +0200 Subject: [PATCH 19/64] Making sure GetTimeZoneInformation causes illegal instruction on win 2019 --- src/internal.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/internal.rs b/src/internal.rs index 7d7ef19..5851ce9 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -32,11 +32,11 @@ use bp3d_os::time::LocalOffsetDateTime; use crossbeam_channel::{bounded, Receiver, Sender}; use crossbeam_queue::ArrayQueue; use log::{Level, Log, Metadata, Record}; +use time::OffsetDateTime; +use time::macros::format_description; use std::fmt::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use time::macros::format_description; -use time::OffsetDateTime; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. @@ -276,12 +276,9 @@ impl Log for LoggerImpl { return; } let (target, module) = extract_target_module(record); - let time = Some(OffsetDateTime::now_utc()); + let time = OffsetDateTime::now_local(); let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); - let formatted = time - .unwrap_or_else(OffsetDateTime::now_utc) - .format(format) - .unwrap_or_default(); + let formatted = time.unwrap_or_else(OffsetDateTime::now_utc).format(format).unwrap_or_default(); let mut msg = LogMsg::new(target, record.level()); let _ = write!( msg, From 22268f5dee2b2538bc94b984015d1c1e0c61c7ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:05:06 +0000 Subject: [PATCH 20/64] Format Rust code using rustfmt --- src/internal.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/internal.rs b/src/internal.rs index 5851ce9..548cfb7 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -32,11 +32,11 @@ use bp3d_os::time::LocalOffsetDateTime; use crossbeam_channel::{bounded, Receiver, Sender}; use crossbeam_queue::ArrayQueue; use log::{Level, Log, Metadata, Record}; -use time::OffsetDateTime; -use time::macros::format_description; use std::fmt::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; +use time::macros::format_description; +use time::OffsetDateTime; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. @@ -278,7 +278,10 @@ impl Log for LoggerImpl { let (target, module) = extract_target_module(record); let time = OffsetDateTime::now_local(); let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); - let formatted = time.unwrap_or_else(OffsetDateTime::now_utc).format(format).unwrap_or_default(); + let formatted = time + .unwrap_or_else(OffsetDateTime::now_utc) + .format(format) + .unwrap_or_default(); let mut msg = LogMsg::new(target, record.level()); let _ = write!( msg, From 9ab1a8bf3dda4e2f2c14495f2fc66fbbae28e3b2 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Wed, 9 Aug 2023 21:48:15 +0200 Subject: [PATCH 21/64] Updated to new patched rc 1.2.1 of bp3d-os --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b4af5fe..2b1a99e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ categories = [] [dependencies] log = "0.4.14" -bp3d-os = { version = "1.0.0-rc.1.2.0", features=["dirs", "time"] } +bp3d-os = { version = "1.0.0-rc.1.2.1", features=["dirs", "time"] } crossbeam-channel = "0.5.2" crossbeam-queue = "0.3.8" once_cell = "1.10.0" From 7d8330096624fde98d750c6b6a67599c0cffa5a0 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Wed, 9 Aug 2023 22:12:08 +0200 Subject: [PATCH 22/64] Removed atty, now replaced by std::io::IsTerminal --- Cargo.toml | 1 - src/backend.rs | 19 ++++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b1a99e..54cb708 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,5 @@ crossbeam-queue = "0.3.8" once_cell = "1.10.0" time = { version = "0.3.0", features = ["formatting", "macros"] } termcolor = "1.1.3" -atty = "0.2.14" [features] diff --git a/src/backend.rs b/src/backend.rs index e690b37..0070ff0 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -28,13 +28,12 @@ use crate::easy_termcolor::{color, EasyTermColor}; use crate::Colors; -use atty::Stream; use log::Level; use std::collections::HashMap; use std::fmt::Display; use std::fmt::Formatter; use std::fs::{File, OpenOptions}; -use std::io::{BufWriter, Write}; +use std::io::{BufWriter, Write, IsTerminal}; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use termcolor::{ColorChoice, ColorSpec, StandardStream}; @@ -77,6 +76,20 @@ fn write_msg(stream: StandardStream, target: &str, msg: &str, level: Level) { .lf(); } +enum Stream { + Stdout, + Stderr +} + +impl Stream { + pub fn isatty(&self) -> bool { + match self { + Stream::Stdout => std::io::stdout().is_terminal(), + Stream::Stderr => std::io::stderr().is_terminal(), + } + } +} + impl StdBackend { pub fn new(smart_stderr: bool, colors: Colors) -> StdBackend { StdBackend { @@ -108,7 +121,7 @@ impl Backend for StdBackend { let use_termcolor = match self.colors { Colors::Disabled => false, Colors::Enabled => true, - Colors::Auto => atty::is(stream), + Colors::Auto => stream.isatty(), }; match use_termcolor { true => { From a4c996cbaa17749122f102a93a081a5466d48594 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Wed, 9 Aug 2023 22:15:09 +0200 Subject: [PATCH 23/64] Added disable for large_enum_variant --- src/internal.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/internal.rs b/src/internal.rs index 548cfb7..1bf22b2 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -40,6 +40,10 @@ use time::OffsetDateTime; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. +//Disable large_enum_variant as using a Box will inevitably cause a small allocation on a critical path, +//allocating in a critical code path will most likely result in degraded performance. +//And yes, logging is a critical path when using bp3d-tracing. +#[allow(clippy::large_enum_variant)] enum Command { Flush, Log(LogMsg), From c79ce6e2c53bee81cdf7c98032a4dd54b77c4abe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 20:19:29 +0000 Subject: [PATCH 24/64] Format Rust code using rustfmt --- src/backend.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 0070ff0..196465b 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -33,7 +33,7 @@ use std::collections::HashMap; use std::fmt::Display; use std::fmt::Formatter; use std::fs::{File, OpenOptions}; -use std::io::{BufWriter, Write, IsTerminal}; +use std::io::{BufWriter, IsTerminal, Write}; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use termcolor::{ColorChoice, ColorSpec, StandardStream}; @@ -78,7 +78,7 @@ fn write_msg(stream: StandardStream, target: &str, msg: &str, level: Level) { enum Stream { Stdout, - Stderr + Stderr, } impl Stream { From edddda35aeb7717bf8e95cadfe6c3b75ba4ec812 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Thu, 13 Jun 2024 20:48:22 +0200 Subject: [PATCH 25/64] Initial re-write and removed dependency to log --- Cargo.toml | 3 +- src/backend.rs | 16 +-- src/builder.rs | 178 +++++++++++++++++++++++++++++++ src/easy_termcolor.rs | 8 +- src/internal.rs | 206 ++++++++++++++++-------------------- src/level.rs | 84 +++++++++++++++ src/lib.rs | 236 ++---------------------------------------- src/log_msg.rs | 46 ++------ src/util.rs | 114 ++++++++++++++++++++ 9 files changed, 503 insertions(+), 388 deletions(-) create mode 100644 src/builder.rs create mode 100644 src/level.rs create mode 100644 src/util.rs diff --git a/Cargo.toml b/Cargo.toml index 54cb708..0742b6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Yuri Edward "] edition = "2021" description = "A flexible Log implementation intended to be used with BP3D software." license = "BSD-3-Clause" -repository = "https://gitlab.com/bp3d/logger" +repository = "https://github.com/BlockProject3D/debug.logger" readme = "./README.MD" keywords = [] categories = [] @@ -13,7 +13,6 @@ categories = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -log = "0.4.14" bp3d-os = { version = "1.0.0-rc.1.2.1", features=["dirs", "time"] } crossbeam-channel = "0.5.2" crossbeam-queue = "0.3.8" diff --git a/src/backend.rs b/src/backend.rs index 196465b..dc6984b 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2023, BlockProject 3D +// Copyright (c) 2024, BlockProject 3D // // All rights reserved. // @@ -27,14 +27,14 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::easy_termcolor::{color, EasyTermColor}; -use crate::Colors; -use log::Level; +use crate::{Colors, Level}; use std::collections::HashMap; use std::fmt::Display; use std::fmt::Formatter; use std::fs::{File, OpenOptions}; use std::io::{BufWriter, IsTerminal, Write}; use std::path::PathBuf; +use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use termcolor::{ColorChoice, ColorSpec, StandardStream}; @@ -52,11 +52,10 @@ impl Display for DummyError { } } -pub static ENABLE_STDOUT: AtomicBool = AtomicBool::new(true); - pub struct StdBackend { smart_stderr: bool, colors: Colors, + enable: Arc } fn write_msg(stream: StandardStream, target: &str, msg: &str, level: Level) { @@ -95,9 +94,14 @@ impl StdBackend { StdBackend { smart_stderr, colors, + enable: Arc::new(AtomicBool::new(true)) } } + pub fn get_enable(&self) -> Arc { + self.enable.clone() + } + fn get_stream(&self, level: Level) -> Stream { match self.smart_stderr { false => Stream::Stdout, @@ -113,7 +117,7 @@ impl Backend for StdBackend { type Error = DummyError; fn write(&mut self, target: &str, msg: &str, level: Level) -> Result<(), Self::Error> { - if !ENABLE_STDOUT.load(Ordering::Acquire) { + if !self.enable.load(Ordering::Acquire) { // Skip logging if temporarily disabled. return Ok(()); } diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..d0864aa --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,178 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{backend, GetLogs}; +use crate::internal::Logger; + +/// Enum of the different color settings when printing to stdout/stderr. +#[derive(Debug, Copy, Clone)] +pub enum Colors { + /// Color printing is always enabled. + Enabled, + + /// Color printing is always disabled. + Disabled, + + /// Color printing is automatic (if current terminal is a tty, print with colors, otherwise + /// print without colors). + Auto, +} + +impl Default for Colors { + fn default() -> Self { + Self::Disabled + } +} + +/// The base logger builder/initializer. +/// +/// # Examples +/// +/// The following example shows basic initialization of this logger. +/// ``` +/// use bp3d_logger::with_logger; +/// use bp3d_logger::{Builder, Level, LogMsg}; +/// +/// fn main() { +/// let logger = Builder::new().add_stdout().add_file("my-app").start(); +/// logger.checked_log(&LogMsg::from_msg("bp3d-logger", Level::Info, "Example message")); +/// } +/// ``` +/// +/// The following example shows initialization of this logger and use of the log buffer. +/// ``` +/// use bp3d_logger::{Builder, Level, LogMsg}; +/// +/// fn main() { +/// let logger = Builder::new().add_stdout().start(); +/// logger.enable_log_buffer(true); // Enable log redirect pump into application channel. +/// +/// //... application code with log redirect pump./// +/// logger.checked_log(&LogMsg::from_msg("bp3d-logger", Level::Info, "Example message")); +/// logger.enable(false); +/// logger.raw_log(&LogMsg::from_msg("bp3d-logger", Level::Info, "Example message 1")); +/// logger.enable(true); +/// +/// logger.flush(); +/// let l = logger.read_log().unwrap(); // Capture the last log message. +/// // We can't test for equality because log messages contains a timestamp... +/// assert!(l.msg().ends_with("Example message")); +/// let l = logger.read_log().unwrap(); +/// assert!(l.msg().ends_with("Example message 1")); +/// logger.enable_log_buffer(false); +/// //... application code without log redirect pump. +/// } +/// ``` +pub struct Builder { + pub(crate) colors: Colors, + pub(crate) smart_stderr: bool, + pub(crate) std: Option, + pub(crate) file: Option, + pub(crate) buf_size: Option +} + +impl Default for Builder { + fn default() -> Self { + Self { + colors: Colors::default(), + smart_stderr: true, + std: None, + file: None, + buf_size: None + } + } +} + +impl Builder { + /// Creates a new instance of a logger builder. + pub fn new() -> Builder { + Builder::default() + } + + /// Sets the colors state when logging to stdout/stderr. + /// + /// The default behavior is to disable colors. + pub fn colors(mut self, state: Colors) -> Self { + self.colors = state; + self + } + + /// Enables or disables automatic redirection of error logs to stderr. + /// + /// The default for this flag is true. + pub fn smart_stderr(mut self, flag: bool) -> Self { + self.smart_stderr = flag; + self + } + + /// Sets the buffer size. + /// + /// # Arguments + /// + /// * `buf_size`: the buffer size. + /// + /// returns: Builder + pub fn buffer_size(mut self, buf_size: usize) -> Self { + self.buf_size = Some(buf_size); + self + } + + /// Enables stdout logging. + pub fn add_stdout(mut self) -> Self { + self.std = Some(backend::StdBackend::new(self.smart_stderr, self.colors)); + self + } + + /// Enables file logging to the given application. + /// + /// The application is given as a reference to [GetLogs](GetLogs) to allow obtaining + /// a log directory from various sources. + /// + /// If the log directory could not be found the function prints an error to stderr. + pub fn add_file(mut self, app: T) -> Self { + if let Some(logs) = app.get_logs() { + self.file = Some(backend::FileBackend::new(logs)); + } else { + eprintln!("Failed to obtain application log directory"); + } + self + } + + /// Initializes the log implementation with this current configuration. + /// + /// NOTE: This returns an instance of [Logger](Logger) which is the main entry point for all + /// logging based operations. This instance also acts as a guard to flush all log buffers + /// before returning. It is necessary to flush log buffers because this implementation + /// uses threads to avoid blocking the main thread when issuing logs. + /// + /// NOTE 2: There are no safety concerns with running twice this function in the same + /// application, only that calling this function may be slow due to thread management. + pub fn start(self) -> Logger { + Logger::new(self) + } +} diff --git a/src/easy_termcolor.rs b/src/easy_termcolor.rs index 3bf6a46..053f090 100644 --- a/src/easy_termcolor.rs +++ b/src/easy_termcolor.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2021, BlockProject 3D +// Copyright (c) 2024, BlockProject 3D // // All rights reserved. // @@ -26,9 +26,9 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use log::Level; use std::fmt::Display; use termcolor::{Color, ColorSpec}; +use crate::Level; pub struct EasyTermColor(pub T); @@ -56,6 +56,10 @@ impl EasyTermColor { pub fn color(level: Level) -> ColorSpec { match level { + Level::None => ColorSpec::new() + .set_fg(Some(Color::White)) + .set_bold(true) + .clone(), Level::Error => ColorSpec::new() .set_fg(Some(Color::Red)) .set_bold(true) diff --git a/src/internal.rs b/src/internal.rs index 1bf22b2..db6fdd6 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2023, BlockProject 3D +// Copyright (c) 2024, BlockProject 3D // // All rights reserved. // @@ -27,16 +27,12 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::backend::Backend; -use crate::{LogMsg, Logger}; -use bp3d_os::time::LocalOffsetDateTime; +use crate::{LogMsg, Builder, Level}; use crossbeam_channel::{bounded, Receiver, Sender}; use crossbeam_queue::ArrayQueue; -use log::{Level, Log, Metadata, Record}; -use std::fmt::Write; +use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; -use time::macros::format_description; -use time::OffsetDateTime; +use std::sync::Arc; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. @@ -66,7 +62,7 @@ fn log( } struct Thread { - logger: Logger, + logger: Builder, recv_ch: Receiver, enable_log_buffer: bool, log_buffer: Arc>, @@ -74,7 +70,7 @@ struct Thread { impl Thread { pub fn new( - logger: Logger, + logger: Builder, recv_ch: Receiver, log_buffer: Arc>, ) -> Thread { @@ -142,30 +138,54 @@ impl Thread { } } -pub struct LoggerImpl { +/// The main Logger type allows to control the entire logger state and submit messages for logging. +pub struct Logger { send_ch: Sender, enabled: AtomicBool, - recv_ch: Receiver, + enable_stdout: Option>, log_buffer: Arc>, - thread: Mutex>>, + thread: ManuallyDrop>, } -impl LoggerImpl { - pub fn new() -> LoggerImpl { - let (send_ch, recv_ch) = bounded(BUF_SIZE); - LoggerImpl { - thread: Mutex::new(None), +//TODO: Implement log Level checking for Logger2.checked_log and support setting/getting current log level. +//TODO: Implement support for multiple custom log backends. + +impl Logger { + pub(crate) fn new(builder: Builder) -> Logger { + let buf_size = builder.buf_size.unwrap_or(BUF_SIZE); + let (send_ch, recv_ch) = bounded(buf_size); + let log_buffer = Arc::new(ArrayQueue::new(buf_size)); + let recv_ch1 = recv_ch.clone(); + let log_buffer1 = log_buffer.clone(); + let enable_stdout = builder.std.as_ref().map(|v| v.get_enable()); + let thread = std::thread::spawn(move || { + let thread = Thread::new(builder, recv_ch1, log_buffer1); + thread.run(); + }); + Logger { + thread: ManuallyDrop::new(thread), send_ch, - recv_ch, - log_buffer: Arc::new(ArrayQueue::new(BUF_SIZE)), - enabled: AtomicBool::new(false), + log_buffer, + enabled: AtomicBool::new(true), + enable_stdout } } + /// Enables the stdout/stderr logger. + /// + /// # Arguments + /// + /// * `flag`: true to enable stdout, false to disable stdout. + pub fn enable_stdout(&self, flag: bool) { + self.enable_stdout.as_ref().map(|v| v.store(flag, Ordering::Release)); + } + + /// Enables this logger. pub fn enable(&self, flag: bool) { self.enabled.store(flag, Ordering::Release); } + /// Enables the log redirect pump. pub fn enable_log_buffer(&self, flag: bool) { unsafe { if flag { @@ -176,74 +196,34 @@ impl LoggerImpl { self.send_ch .send(Command::DisableLogBuffer) .unwrap_unchecked(); + self.clear_log_buffer(); } } } + /// Clears the log buffer. #[inline] pub fn clear_log_buffer(&self) { while self.log_buffer.pop().is_some() {} //Clear the entire log buffer. } + /// Attempts to extract one log message from the buffer. #[inline] pub fn read_log(&self) -> Option { self.log_buffer.pop() } - pub fn terminate(&self) { - // This should never panic as there's no way another call would have panicked! - let mut thread = self.thread.lock().unwrap(); - if let Some(handle) = thread.take() { - // This cannot panic as send_ch is owned by LoggerImpl which is intended - // to be statically allocated. - unsafe { - self.send_ch.send(Command::Flush).unwrap_unchecked(); - self.send_ch.send(Command::Terminate).unwrap_unchecked(); - } - // Join the logging thread; this will lock until the thread is completely terminated. - handle.join().unwrap(); - } - } - - pub fn start_new_thread(&self, logger: Logger) { - let mut flag = false; - { - // This should never panic as there's no way another call would have panicked! - let mut thread = self.thread.lock().unwrap(); - if let Some(handle) = thread.take() { - // This cannot panic as send_ch is owned by LoggerImpl which is intended - // to be statically allocated. - unsafe { - self.send_ch.send(Command::Terminate).unwrap_unchecked(); - } - if handle.join().is_err() { - flag = true; - } - } - let recv_ch = self.recv_ch.clone(); - let log_buffer = self.log_buffer.clone(); - *thread = Some(std::thread::spawn(move || { - let thread = Thread::new(logger, recv_ch, log_buffer); - thread.run(); - })); - } - if flag { - // Somehow the previous thread has panicked; log that panic... - unsafe { - // This cannot panic as send_ch is owned by LoggerImpl which is intended - // to be statically allocated. - self.send_ch - .send(Command::Log(LogMsg::from_msg( - "bp3d-logger", - Level::Error, - "The logging thread has panicked!", - ))) - .unwrap_unchecked(); - } - } - } - - pub fn low_level_log(&self, msg: &LogMsg) { + /// Low-level log function. This injects log messages directly into the logging thread channel. + /// + /// This function applies basic formatting depending on the backend: + /// - For stdout/stderr backend the format is \ \[level\] msg. + /// - For file backend the format is \[level\] msg and the message is recorded in the file + /// corresponding to the log target. + /// + /// WARNING: For optimization reasons, this function does not check and thus does not honor + /// the enabled flag. For a checked log function, use [checked_log](Self::checked_log). + #[inline] + pub fn raw_log(&self, msg: &LogMsg) { unsafe { // This cannot panic as send_ch is owned by LoggerImpl which is intended // to be statically allocated. @@ -253,51 +233,25 @@ impl LoggerImpl { } } + /// Checked log function. This injects log messages into the logging thread channel only if + /// this logger is enabled. + /// + /// This function calls the [raw_log](Self::raw_log) function only when this logger is enabled. #[inline] - pub fn is_enabled(&self) -> bool { - self.enabled.load(Ordering::Acquire) - } -} - -fn extract_target_module<'a>(record: &'a Record) -> (&'a str, Option<&'a str>) { - let base_string = record.module_path().unwrap_or_else(|| record.target()); - let target = base_string - .find("::") - .map(|v| &base_string[..v]) - .unwrap_or(base_string); - let module = base_string.find("::").map(|v| &base_string[(v + 2)..]); - (target, module) -} - -impl Log for LoggerImpl { - fn enabled(&self, _: &Metadata) -> bool { - self.is_enabled() + pub fn checked_log(&self, msg: &LogMsg) { + if self.is_enabled() { + self.raw_log(msg); + } } - fn log(&self, record: &Record) { - // Apparently the log crate is defective: the enabled function is ignored... - if !self.enabled(record.metadata()) { - return; - } - let (target, module) = extract_target_module(record); - let time = OffsetDateTime::now_local(); - let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); - let formatted = time - .unwrap_or_else(OffsetDateTime::now_utc) - .format(format) - .unwrap_or_default(); - let mut msg = LogMsg::new(target, record.level()); - let _ = write!( - msg, - "({}) {}: {}", - formatted, - module.unwrap_or("main"), - record.args() - ); - self.low_level_log(&msg); + /// Returns true if the logger is currently enabled and is capturing log messages. + #[inline] + pub fn is_enabled(&self) -> bool { + self.enabled.load(Ordering::Acquire) } - fn flush(&self) { + /// Flushes all pending messages. + pub fn flush(&self) { if !self.is_enabled() { return; } @@ -309,3 +263,25 @@ impl Log for LoggerImpl { } } } + +impl Drop for Logger { + fn drop(&mut self) { + // Disable this Logger. + self.enable(false); + + // Disable the log buffer (this automatically clears it). + self.enable_log_buffer(false); + + // Send termination command and join with logging thread. + // This cannot panic as send_ch is owned by LoggerImpl which is intended + // to be statically allocated. + unsafe { + self.send_ch.send(Command::Flush).unwrap_unchecked(); + self.send_ch.send(Command::Terminate).unwrap_unchecked(); + } + + // Join the logging thread; this will lock until the thread is completely terminated. + let thread = unsafe { ManuallyDrop::into_inner(std::ptr::read(&self.thread)) }; + thread.join().unwrap(); + } +} diff --git a/src/level.rs b/src/level.rs new file mode 100644 index 0000000..2060460 --- /dev/null +++ b/src/level.rs @@ -0,0 +1,84 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::fmt::{Display, Formatter}; + +/// An enum representing the available verbosity levels of the logger. +#[repr(u8)] +#[derive(Clone, PartialEq, Copy, Ord, PartialOrd, Eq, Debug, Hash)] +pub enum Level { + /// The "none" level. + /// + /// This level is used to disable logging. + None = 0, + + /// The "error" level. + /// + /// Designates very serious errors. + // This way these line up with the discriminants for LevelFilter below + // This works because Rust treats field-less enums the same way as C does: + // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-field-less-enumerations + Error = 1, + + /// The "warn" level. + /// + /// Designates hazardous situations. + Warn = 2, + + /// The "info" level. + /// + /// Designates useful information. + Info = 3, + + /// The "debug" level. + /// + /// Designates lower priority information. + Debug = 4, + + /// The "trace" level. + /// + /// Designates very low priority, often extremely verbose, information. + Trace = 5 +} + +static LOG_LEVEL_NAMES: [&str; 6] = ["OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"]; + +impl Level { + /// Returns the string representation of the `Level`. + /// + /// This returns the same string as the `fmt::Display` implementation. + pub fn as_str(&self) -> &'static str { + LOG_LEVEL_NAMES[*self as usize] + } +} + +impl Display for Level { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} diff --git a/src/lib.rs b/src/lib.rs index a704bcd..1c968b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2023, BlockProject 3D +// Copyright (c) 2024, BlockProject 3D // // All rights reserved. // @@ -31,22 +31,24 @@ #![allow(clippy::needless_doctest_main)] #![warn(missing_docs)] -//! An async flexible Log implementation intended to be used with BP3D software. +//! An async flexible logger framework designed for BP3D software. mod backend; mod easy_termcolor; mod internal; mod log_msg; +pub mod util; +mod builder; +mod level; -use crate::backend::ENABLE_STDOUT; use bp3d_os::dirs::App; use crossbeam_channel::Receiver; -use log::Log; -use once_cell::sync::Lazy; use std::path::PathBuf; -use std::sync::atomic::Ordering; pub use log_msg::LogMsg; +pub use builder::*; +pub use internal::Logger; +pub use level::Level; /// The log buffer type. pub type LogBuffer = Receiver; @@ -78,170 +80,6 @@ impl<'a> GetLogs for &'a str { } } -/// Enum of the different color settings when printing to stdout/stderr. -#[derive(Debug, Copy, Clone)] -pub enum Colors { - /// Color printing is always enabled. - Enabled, - - /// Color printing is always disabled. - Disabled, - - /// Color printing is automatic (if current terminal is a tty, print with colors, otherwise - /// print without colors). - Auto, -} - -impl Default for Colors { - fn default() -> Self { - Self::Disabled - } -} - -/// The base logger builder/initializer. -/// -/// # Examples -/// -/// The following example shows basic initialization of this logger. -/// ``` -/// use bp3d_logger::Logger; -/// use log::info; -/// use log::LevelFilter; -/// -/// fn main() { -/// let _guard = Logger::new().add_stdout().add_file("my-app").start(); -/// log::set_max_level(LevelFilter::Info); -/// //... -/// info!("Example message"); -/// } -/// ``` -/// -/// The following example shows initialization of this logger with a return value. -/// ``` -/// use bp3d_logger::Logger; -/// use bp3d_logger::with_logger; -/// use log::info; -/// use log::LevelFilter; -/// -/// fn main() { -/// let code = with_logger(Logger::new().add_stdout().add_file("my-app"), || { -/// log::set_max_level(LevelFilter::Info); -/// //... -/// info!("Example message"); -/// 0 -/// }); -/// std::process::exit(code); -/// } -/// ``` -/// -/// The following example shows initialization of this logger and use of the log buffer. -/// ``` -/// use bp3d_logger::Logger; -/// use log::info; -/// use log::LevelFilter; -/// -/// fn main() { -/// let _guard = Logger::new().add_stdout().add_file("my-app").start(); -/// log::set_max_level(LevelFilter::Info); -/// bp3d_logger::enable_log_buffer(); // Enable log redirect pump into application channel. -/// //... application code with log redirect pump. -/// info!("Example message"); -/// bp3d_logger::flush(); -/// let l = bp3d_logger::read_log().unwrap();// Capture the last log message. -/// //We can't test for equality because log messages contains a timestamp... -/// assert!(l.msg().ends_with("Example message")); -/// bp3d_logger::disable_log_buffer(); -/// //... application code without log redirect pump. -/// } -/// ``` -pub struct Logger { - colors: Colors, - smart_stderr: bool, - std: Option, - file: Option, -} - -impl Default for Logger { - fn default() -> Self { - Self { - colors: Colors::default(), - smart_stderr: true, - std: None, - file: None, - } - } -} - -impl Logger { - /// Creates a new instance of a logger builder. - pub fn new() -> Logger { - Logger::default() - } - - /// Sets the colors state when logging to stdout/stderr. - /// - /// The default behavior is to disable colors. - pub fn colors(mut self, state: Colors) -> Self { - self.colors = state; - self - } - - /// Enables or disables automatic redirection of error logs to stderr. - /// - /// The default for this flag is true. - pub fn smart_stderr(mut self, flag: bool) -> Self { - self.smart_stderr = flag; - self - } - - /// Enables stdout logging. - pub fn add_stdout(mut self) -> Self { - self.std = Some(backend::StdBackend::new(self.smart_stderr, self.colors)); - self - } - - /// Enables file logging to the given application. - /// - /// The application is given as a reference to [GetLogs](GetLogs) to allow obtaining - /// a log directory from various sources. - /// - /// If the log directory could not be found the function prints an error to stderr. - pub fn add_file(mut self, app: T) -> Self { - if let Some(logs) = app.get_logs() { - self.file = Some(backend::FileBackend::new(logs)); - } else { - eprintln!("Failed to obtain application log directory"); - } - self - } - - /// Initializes the log implementation with this current configuration. - /// - /// NOTE: This returns a guard to flush all log buffers before returning. It is - /// necessary to flush log buffers because this implementation uses threads - /// to avoid blocking the main thread when issuing logs. - /// - /// NOTE 2: There are no safety concerns with running twice this function in the same - /// application, only that calling this function may be slow due to thread management. - pub fn start(self) -> Guard { - let _ = log::set_logger(&*BP3D_LOGGER); // Ignore the error - // (we can't do anything if there's already a logger set; - // unfortunately that is a limitation of the log crate) - - BP3D_LOGGER.start_new_thread(self); // Re-start the logging thread with the new configuration. - BP3D_LOGGER.enable(true); // Enable logging. - Guard - } - - /// Initializes the log implementation with this current configuration. - /// - /// NOTE: Since version 1.1.0 this is a redirect to bp3d_logger::with_logger. - #[deprecated(since = "1.1.0", note = "please use bp3d_logger::with_logger")] - pub fn run R>(self, f: F) -> R { - with_logger(self, f) - } -} - /// Represents a logger guard. /// /// WARNING: Once this guard is dropped messages are no longer captured. @@ -249,68 +87,12 @@ pub struct Guard; impl Drop for Guard { fn drop(&mut self) { - // Disable the logger so further log requests are dropped. - BP3D_LOGGER.enable(false); - // Send termination command and join with logging thread. - BP3D_LOGGER.terminate(); - // Disable log buffer. - BP3D_LOGGER.enable_log_buffer(false); - // Clear by force all content of in memory log buffer. - BP3D_LOGGER.clear_log_buffer(); } } -static BP3D_LOGGER: Lazy = Lazy::new(internal::LoggerImpl::new); - -/// Enables the log redirect pump. -pub fn enable_log_buffer() { - BP3D_LOGGER.enable_log_buffer(true); -} - -/// Disables the log redirect pump. -pub fn disable_log_buffer() { - BP3D_LOGGER.enable_log_buffer(false); - BP3D_LOGGER.clear_log_buffer(); -} - -/// Enables the stdout/stderr logger. -pub fn enable_stdout() { - ENABLE_STDOUT.store(true, Ordering::Release); -} - -/// Disables the stdout/stderr logger. -pub fn disable_stdout() { - ENABLE_STDOUT.store(false, Ordering::Release); -} - -/// Attempts to extract one log message from the buffer. -pub fn read_log() -> Option { - BP3D_LOGGER.read_log() -} - -/// Low-level log function. This injects log messages directly into the logging thread channel. -/// -/// This function applies basic formatting depending on the backend: -/// - For stdout/stderr backend the format is \ \[level\] msg -/// - For file backend the format is \[level\] msg and the message is recorded in the file -/// corresponding to the log target. -pub fn raw_log(msg: &LogMsg) { - BP3D_LOGGER.low_level_log(msg) -} - -/// Shortcut to the flush command to avoid having to call behind the dyn interface. -pub fn flush() { - BP3D_LOGGER.flush(); -} - -/// Returns true if the logger is currently enabled and is capturing log messages. -pub fn enabled() -> bool { - BP3D_LOGGER.is_enabled() -} - /// Runs a closure in scope of a logger configuration, then free the given logger configuration /// and return closure result. -pub fn with_logger R>(logger: Logger, f: F) -> R { +pub fn with_logger R>(logger: Builder, f: F) -> R { let _guard = logger.start(); f() } diff --git a/src/log_msg.rs b/src/log_msg.rs index f13b6b1..3484ba0 100644 --- a/src/log_msg.rs +++ b/src/log_msg.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2023, BlockProject 3D +// Copyright (c) 2024, BlockProject 3D // // All rights reserved. // @@ -26,9 +26,9 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use log::Level; use std::fmt::{Error, Write}; use std::mem::MaybeUninit; +use crate::Level; // Limit the size of the target string to 16 bytes. const LOG_TARGET_SIZE: usize = 16; @@ -39,28 +39,6 @@ const LOG_CONTROL_SIZE: usize = std::mem::size_of::() + 2; const LOG_MSG_SIZE: usize = LOG_BUFFER_SIZE - LOG_TARGET_SIZE - LOG_CONTROL_SIZE; const LOG_BUFFER_SIZE: usize = 1024; -#[inline] -fn log_to_u8(level: Level) -> u8 { - match level { - Level::Error => 0, - Level::Warn => 1, - Level::Info => 2, - Level::Debug => 3, - Level::Trace => 4, - } -} - -#[inline] -fn u8_to_log(l: u8) -> Level { - match l { - 0 => Level::Error, - 1 => Level::Warn, - 3 => Level::Debug, - 4 => Level::Trace, - _ => Level::Info, - } -} - /// A log message. /// /// This structure uses a large 1K buffer which stores the entire log message to improve @@ -72,8 +50,7 @@ fn u8_to_log(l: u8) -> Level { /// # Examples /// /// ``` -/// use log::Level; -/// use bp3d_logger::LogMsg; +/// use bp3d_logger::{Level, LogMsg}; /// use std::fmt::Write; /// let mut msg = LogMsg::new("test", Level::Info); /// let _ = write!(msg, "This is a formatted message {}", 42); @@ -83,7 +60,7 @@ fn u8_to_log(l: u8) -> Level { #[repr(C)] pub struct LogMsg { msg_len: u32, - level: u8, + level: Level, target_len: u8, buffer: [MaybeUninit; LOG_MSG_SIZE + LOG_TARGET_SIZE], } @@ -101,8 +78,7 @@ impl LogMsg { /// # Examples /// /// ``` - /// use log::Level; - /// use bp3d_logger::LogMsg; + /// use bp3d_logger::{Level, LogMsg}; /// let msg = LogMsg::new("test", Level::Info); /// assert_eq!(msg.target(), "test"); /// assert_eq!(msg.level(), Level::Info); @@ -113,7 +89,7 @@ impl LogMsg { buffer: unsafe { MaybeUninit::uninit().assume_init() }, target_len: len as _, msg_len: len as _, - level: log_to_u8(level), + level, }; unsafe { std::ptr::copy_nonoverlapping( @@ -130,8 +106,7 @@ impl LogMsg { /// # Examples /// /// ``` - /// use log::Level; - /// use bp3d_logger::LogMsg; + /// use bp3d_logger::{Level, LogMsg}; /// let mut msg = LogMsg::from_msg("test", Level::Info, "this is a test"); /// msg.clear(); /// assert_eq!(msg.msg(), ""); @@ -158,8 +133,7 @@ impl LogMsg { /// # Examples /// /// ``` - /// use log::Level; - /// use bp3d_logger::LogMsg; + /// use bp3d_logger::{LogMsg, Level}; /// let mut msg = LogMsg::from_msg("test", Level::Info, "this is a test"); /// assert_eq!(msg.target(), "test"); /// assert_eq!(msg.level(), Level::Info); @@ -212,7 +186,7 @@ impl LogMsg { /// Returns the log message as a string. #[inline] pub fn msg(&self) -> &str { - // SAFEY: This is always safe because BufLogMsg is always UTF-8. + // SAFETY: This is always safe because LogMsg is always UTF-8. unsafe { std::str::from_utf8_unchecked(std::mem::transmute( &self.buffer[self.target_len as _..self.msg_len as _], @@ -223,7 +197,7 @@ impl LogMsg { /// Returns the level of this log message. #[inline] pub fn level(&self) -> Level { - u8_to_log(self.level) + self.level } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..c4e4237 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,114 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Logging utilities. + +use bp3d_os::time::LocalOffsetDateTime; +use time::macros::format_description; +use time::OffsetDateTime; +use crate::LogMsg; + +/// Extracts the target name and the module path (without the target name) from a full module path string. +/// +/// # Arguments +/// +/// * `base_string`: a full module path string (ex: bp3d_logger::util::extract_target_module). +/// +/// returns: (&str, &str) +pub fn extract_target_module(base_string: &str) -> (&str, &str) { + let target = base_string + .find("::") + .map(|v| &base_string[..v]) + .unwrap_or(base_string); + let module = base_string.find("::").map(|v| &base_string[(v + 2)..]); + (target, module.unwrap_or("main")) +} + +/// Unsafe [Write](std::io::Write) wrapper for [LogMsg]. +/// +/// This utility is provided for interactions with foreign APIs that only supports writing through +/// the io::Write interface and are GUARANTEED to result in UTF-8 data (ex: time crate). +pub struct IoWrapper<'a>(&'a mut LogMsg); + +impl<'a> IoWrapper<'a> { + /// Creates a new [Write](std::io::Write) wrapper for [LogMsg]. + /// + /// Safety + /// + /// Subsequent calls to [Write](std::io::Write) must result in a valid UTF-8 string + /// once a call to [msg](LogMsg::msg) is made. + pub unsafe fn new(msg: &'a mut LogMsg) -> Self { + Self(msg) + } + + /// Extracts the underlying [LogMsg] from this wrapper. + /// + /// Safety + /// + /// The content of the underlying [LogMsg] MUST be a valid UTF-8 string. + pub unsafe fn into_inner(self) -> &'a mut LogMsg { + self.0 + } +} + +impl<'a> std::io::Write for IoWrapper<'a> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + unsafe { Ok(self.0.write(buf)) } + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +/// Write time information into the given [LogMsg]. +/// +/// # Arguments +/// +/// * `msg`: the [LogMsg] to write time information to. +/// * `time`: the time to write. +/// +/// returns: () +pub fn write_time(msg: &mut LogMsg, time: OffsetDateTime) { + unsafe { msg.write(b"(") }; + let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); + let mut wrapper = unsafe { IoWrapper::new(msg) }; + let _ = time.format_into(&mut wrapper, format); + unsafe { msg.write(b") ") }; +} + +/// Adds the current time to the given [LogMsg]. +/// +/// # Arguments +/// +/// * `msg`: the [LogMsg] to write time information to. +/// +/// returns: () +pub fn add_time(msg: &mut LogMsg) { + write_time(msg, OffsetDateTime::now_local().unwrap_or_else(OffsetDateTime::now_utc)) +} From 660a415ac8394d5c407ade25ab794f6be4d30067 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 17 Jun 2024 06:06:41 +0200 Subject: [PATCH 26/64] Added support for custom log handlers --- Cargo.toml | 3 +- src/builder.rs | 45 ++++--- src/handler/file.rs | 97 +++++++++++++++ src/handler/mod.rs | 88 ++++++++++++++ src/{backend.rs => handler/stdout.rs} | 147 +++++++++-------------- src/internal.rs | 87 ++++++-------- src/lib.rs | 4 +- src/log_msg.rs | 162 +++++++++++++++++++------- src/util.rs | 75 ++++-------- 9 files changed, 452 insertions(+), 256 deletions(-) create mode 100644 src/handler/file.rs create mode 100644 src/handler/mod.rs rename src/{backend.rs => handler/stdout.rs} (53%) diff --git a/Cargo.toml b/Cargo.toml index 0742b6b..9383c09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,8 @@ categories = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bp3d-os = { version = "1.0.0-rc.1.2.1", features=["dirs", "time"] } +bp3d-os = { version = "1.0.0-rc.3.0.2", features=["dirs", "time"] } +bp3d-util = { version = "1.0.0", features = ["format"] } crossbeam-channel = "0.5.2" crossbeam-queue = "0.3.8" once_cell = "1.10.0" diff --git a/src/builder.rs b/src/builder.rs index d0864aa..83a9ddf 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -26,7 +26,8 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{backend, GetLogs}; +use crate::GetLogs; +use crate::handler::{FileHandler, Handler, StdHandler}; use crate::internal::Logger; /// Enum of the different color settings when printing to stdout/stderr. @@ -55,27 +56,26 @@ impl Default for Colors { /// /// The following example shows basic initialization of this logger. /// ``` -/// use bp3d_logger::with_logger; -/// use bp3d_logger::{Builder, Level, LogMsg}; +/// use bp3d_logger::{Builder, Level, LogMsg, Location}; /// /// fn main() { /// let logger = Builder::new().add_stdout().add_file("my-app").start(); -/// logger.checked_log(&LogMsg::from_msg("bp3d-logger", Level::Info, "Example message")); +/// logger.log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message")); /// } /// ``` /// /// The following example shows initialization of this logger and use of the log buffer. /// ``` -/// use bp3d_logger::{Builder, Level, LogMsg}; +/// use bp3d_logger::{Builder, Level, LogMsg, Location}; /// /// fn main() { /// let logger = Builder::new().add_stdout().start(); /// logger.enable_log_buffer(true); // Enable log redirect pump into application channel. /// /// //... application code with log redirect pump./// -/// logger.checked_log(&LogMsg::from_msg("bp3d-logger", Level::Info, "Example message")); +/// logger.log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message")); /// logger.enable(false); -/// logger.raw_log(&LogMsg::from_msg("bp3d-logger", Level::Info, "Example message 1")); +/// logger.raw_log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message 1")); /// logger.enable(true); /// /// logger.flush(); @@ -91,9 +91,8 @@ impl Default for Colors { pub struct Builder { pub(crate) colors: Colors, pub(crate) smart_stderr: bool, - pub(crate) std: Option, - pub(crate) file: Option, - pub(crate) buf_size: Option + pub(crate) buf_size: Option, + pub(crate) handlers: Vec> } impl Default for Builder { @@ -101,9 +100,8 @@ impl Default for Builder { Self { colors: Colors::default(), smart_stderr: true, - std: None, - file: None, - buf_size: None + buf_size: None, + handlers: Vec::new() } } } @@ -142,10 +140,23 @@ impl Builder { self } + /// Adds a new log [Handler](Handler). + /// + /// # Arguments + /// + /// * `handler`: the new handler implementation to add. + /// + /// returns: Builder + pub fn add_handler(mut self, handler: T) -> Self { + self.handlers.push(Box::new(handler)); + self + } + /// Enables stdout logging. pub fn add_stdout(mut self) -> Self { - self.std = Some(backend::StdBackend::new(self.smart_stderr, self.colors)); - self + let motherfuckingrust = self.smart_stderr; + let motherfuckingrust1 = self.colors; + self.add_handler(StdHandler::new(motherfuckingrust, motherfuckingrust1)) } /// Enables file logging to the given application. @@ -156,11 +167,11 @@ impl Builder { /// If the log directory could not be found the function prints an error to stderr. pub fn add_file(mut self, app: T) -> Self { if let Some(logs) = app.get_logs() { - self.file = Some(backend::FileBackend::new(logs)); + self.add_handler(FileHandler::new(logs)) } else { eprintln!("Failed to obtain application log directory"); + self } - self } /// Initializes the log implementation with this current configuration. diff --git a/src/handler/file.rs b/src/handler/file.rs new file mode 100644 index 0000000..cefb7c4 --- /dev/null +++ b/src/handler/file.rs @@ -0,0 +1,97 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::collections::HashMap; +use std::fs::{File, OpenOptions}; +use std::io::{BufWriter, Write}; +use std::path::PathBuf; +use bp3d_util::format::{FixedBufStr, IoToFmt}; +use time::format_description::well_known::Iso8601; +use time::OffsetDateTime; +use crate::handler::{Flag, Handler}; +use crate::{Level, Location, LogMsg}; + +/// A file handler which writes log messages into different files each named by the target name. +pub struct FileHandler { + targets: HashMap>, + path: PathBuf, +} + +impl FileHandler { + /// Creates a new [FileHandler](FileHandler). + /// + /// # Arguments + /// + /// * `path`: the path to the base folder which should contain logs. + /// + /// returns: FileHandler + pub fn new(path: PathBuf) -> FileHandler { + FileHandler { + targets: HashMap::new(), + path, + } + } + + fn get_create_open_file( + &mut self, + target: &str, + ) -> Result<&mut BufWriter, std::io::Error> { + if self.targets.get(target).is_none() { + let f = OpenOptions::new() + .append(true) + .create(true) + .open(self.path.join(format!("{}.log", target)))?; + self.targets.insert(target.into(), BufWriter::new(f)); + } + unsafe { + // This cannot never fail because None is captured and initialized by the if block. + Ok(self.targets.get_mut(target).unwrap_unchecked()) + } + } +} + +impl Handler for FileHandler { + fn install(&mut self, _: &Flag) { + } + + fn write(&mut self, msg: &LogMsg) { + let (target, module) = msg.location().get_target_module(); + let mut wrapper = IoToFmt::new(FixedBufStr::<128>::new()); + let _ = msg.time().format_into(&mut wrapper, &Iso8601::DEFAULT); + let time_str = wrapper.into_inner(); + if let Ok(file) = self.get_create_open_file(target) { + let _ = writeln!(file, "[{}] ({}) {}: {}", msg.level(), time_str.str(), module, msg.msg()); + } + } + + fn flush(&mut self) { + for v in self.targets.values_mut() { + let _ = v.flush(); + } + } +} diff --git a/src/handler/mod.rs b/src/handler/mod.rs new file mode 100644 index 0000000..93d7f25 --- /dev/null +++ b/src/handler/mod.rs @@ -0,0 +1,88 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! The log handler system, with default provided handlers. + +mod stdout; +mod file; + +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use time::OffsetDateTime; +use crate::{Level, Location, LogMsg}; + +/// A dynamic atomic flag. +#[derive(Clone)] +pub struct Flag(Arc); + +impl Flag { + + /// Creates a new flag. + /// + /// # Arguments + /// + /// * `initial`: the initial value of this flag. + /// + /// returns: Flag + pub fn new(initial: bool) -> Self { + Self(Arc::new(AtomicBool::new(initial))) + } + + /// Returns true if this flag is ON, false otherwise. + pub fn is_enabled(&self) -> bool { + self.0.load(Ordering::Acquire) + } + + /// Sets this flag. + pub fn set(&self, flag: bool) { + self.0.store(flag, Ordering::Release); + } +} + +/// The main handler trait. +pub trait Handler: Send { + /// Called when the handler is installed in the async logging thread. + /// + /// # Arguments + /// + /// * `enable_stdout`: boolean flag to know if printing to stdout is allowed. + fn install(&mut self, enable_stdout: &Flag); + + /// Called when a message is being written. + /// + /// # Arguments + /// + /// * `msg`: the log message which was emitted as a [LogMsg](LogMsg). + fn write(&mut self, msg: &LogMsg); + + /// Called when the flush command is received in the async logging thread. + fn flush(&mut self); +} + +pub use file::FileHandler; +pub use stdout::StdHandler; diff --git a/src/backend.rs b/src/handler/stdout.rs similarity index 53% rename from src/backend.rs rename to src/handler/stdout.rs index dc6984b..38c9420 100644 --- a/src/backend.rs +++ b/src/handler/stdout.rs @@ -26,40 +26,37 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::easy_termcolor::{color, EasyTermColor}; -use crate::{Colors, Level}; -use std::collections::HashMap; -use std::fmt::Display; -use std::fmt::Formatter; -use std::fs::{File, OpenOptions}; -use std::io::{BufWriter, IsTerminal, Write}; -use std::path::PathBuf; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::io::IsTerminal; +use std::mem::MaybeUninit; +use bp3d_os::time::LocalUtcOffset; +use bp3d_util::format::FixedBufStr; use termcolor::{ColorChoice, ColorSpec, StandardStream}; +use time::{OffsetDateTime, UtcOffset}; +use crate::{Colors, Level, Location, LogMsg}; +use crate::easy_termcolor::{color, EasyTermColor}; +use crate::handler::{Flag, Handler}; +use crate::util::write_time; -pub trait Backend { - type Error: Display; - fn write(&mut self, target: &str, msg: &str, level: Level) -> Result<(), Self::Error>; - fn flush(&mut self) -> Result<(), Self::Error>; -} - -pub struct DummyError(); - -impl Display for DummyError { - fn fmt(&self, _: &mut Formatter<'_>) -> std::fmt::Result { - todo!() // Panic (DummyError is by definition the error that never occurs)! - } -} - -pub struct StdBackend { +/// A simple stdout/stderr handler which redirects error messages to stderr and other messages to +/// stdout. +pub struct StdHandler { smart_stderr: bool, colors: Colors, - enable: Arc + enable: MaybeUninit } -fn write_msg(stream: StandardStream, target: &str, msg: &str, level: Level) { +fn format_time_str(time: &OffsetDateTime) -> FixedBufStr<128> { + let offset = UtcOffset::local_offset_at(*time); + let time = offset.map(|v| time.to_offset(v)).unwrap_or(*time); + let mut time_str = FixedBufStr::<128>::new(); + write_time(&mut time_str, time); + time_str +} + +fn write_msg(stream: StandardStream, location: &Location, time: &OffsetDateTime, msg: &str, level: Level) { + let (target, module) = location.get_target_module(); let t = ColorSpec::new().set_bold(true).clone(); + let time_str = format_time_str(time); EasyTermColor(stream) .write('<') .color(t) @@ -71,6 +68,10 @@ fn write_msg(stream: StandardStream, target: &str, msg: &str, level: Level) { .write(level) .reset() .write("] ") + .write(time_str.str()) + .write(" ") + .write(module) + .write(": ") .write(msg) .lf(); } @@ -89,19 +90,23 @@ impl Stream { } } -impl StdBackend { - pub fn new(smart_stderr: bool, colors: Colors) -> StdBackend { - StdBackend { +impl StdHandler { + /// Creates a new [StdHandler](StdHandler). + /// + /// # Arguments + /// + /// * `smart_stderr`: true to enable redirecting error logs to stderr, false otherwise. + /// * `colors`: the printing color policy. + /// + /// returns: StdHandler + pub fn new(smart_stderr: bool, colors: Colors) -> StdHandler { + StdHandler { smart_stderr, colors, - enable: Arc::new(AtomicBool::new(true)) + enable: MaybeUninit::uninit() } } - pub fn get_enable(&self) -> Arc { - self.enable.clone() - } - fn get_stream(&self, level: Level) -> Stream { match self.smart_stderr { false => Stream::Stdout, @@ -113,15 +118,17 @@ impl StdBackend { } } -impl Backend for StdBackend { - type Error = DummyError; +impl Handler for StdHandler { + fn install(&mut self, enable_stdout: &Flag) { + self.enable.write(enable_stdout.clone()); + } - fn write(&mut self, target: &str, msg: &str, level: Level) -> Result<(), Self::Error> { - if !self.enable.load(Ordering::Acquire) { + fn write(&mut self, msg: &LogMsg) { + if !unsafe { self.enable.assume_init_ref().is_enabled() } { // Skip logging if temporarily disabled. - return Ok(()); + return; } - let stream = self.get_stream(level); + let stream = self.get_stream(msg.level()); let use_termcolor = match self.colors { Colors::Disabled => false, Colors::Enabled => true, @@ -133,65 +140,19 @@ impl Backend for StdBackend { Stream::Stderr => StandardStream::stderr(ColorChoice::Always), _ => StandardStream::stdout(ColorChoice::Always), }; - write_msg(val, target, msg, level); + write_msg(val, msg.location(), msg.time(), msg.msg(), msg.level()); } false => { + let (target, module) = msg.location().get_target_module(); + let time_str = format_time_str(msg.time()); match stream { - Stream::Stderr => eprintln!("<{}> [{}] {}", target, level, msg), - _ => println!("<{}> [{}] {}", target, level, msg), + Stream::Stderr => eprintln!("<{}> [{}] {} {}: {}", target, msg.level(), time_str.str(), module, msg.msg()), + _ => println!("<{}> [{}] {} {}: {}", target, msg.level(), time_str.str(), module, msg.msg()) }; } }; - Ok(()) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } -} - -pub struct FileBackend { - targets: HashMap>, - path: PathBuf, -} - -impl FileBackend { - pub fn new(path: PathBuf) -> FileBackend { - FileBackend { - targets: HashMap::new(), - path, - } } - fn get_create_open_file( - &mut self, - target: &str, - ) -> Result<&mut BufWriter, std::io::Error> { - if self.targets.get(target).is_none() { - let f = OpenOptions::new() - .append(true) - .create(true) - .open(self.path.join(format!("{}.log", target)))?; - self.targets.insert(target.into(), BufWriter::new(f)); - } - unsafe { - // This cannot never fail because None is captured and initialized by the if block. - Ok(self.targets.get_mut(target).unwrap_unchecked()) - } - } -} - -impl Backend for FileBackend { - type Error = std::io::Error; - - fn write(&mut self, target: &str, msg: &str, level: Level) -> Result<(), Self::Error> { - writeln!(self.get_create_open_file(target)?, "[{}] {}", level, msg) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - for v in self.targets.values_mut() { - v.flush()?; - } - Ok(()) + fn flush(&mut self) { } } diff --git a/src/internal.rs b/src/internal.rs index db6fdd6..854bf17 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -26,13 +26,13 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::backend::Backend; -use crate::{LogMsg, Builder, Level}; +use crate::{LogMsg, Builder}; use crossbeam_channel::{bounded, Receiver, Sender}; use crossbeam_queue::ArrayQueue; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use crate::handler::{Flag, Handler}; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. @@ -48,35 +48,25 @@ enum Command { DisableLogBuffer, } -fn log( - backend: Option<&mut T>, - target: &str, - msg: &str, - level: Level, -) -> Result<(), T::Error> { - if let Some(back) = backend { - back.write(target, msg, level) - } else { - Ok(()) - } -} - struct Thread { - logger: Builder, + handlers: Vec>, recv_ch: Receiver, + enable_stdout: Flag, enable_log_buffer: bool, log_buffer: Arc>, } impl Thread { pub fn new( - logger: Builder, + builder: Builder, recv_ch: Receiver, log_buffer: Arc>, + enable_stdout: Flag ) -> Thread { Thread { - logger, + handlers: builder.handlers, recv_ch, + enable_stdout, enable_log_buffer: false, log_buffer, } @@ -86,33 +76,14 @@ impl Thread { match cmd { Command::Terminate => true, Command::Flush => { - if let Some(file) = &mut self.logger.file { - if let Err(e) = file.flush() { - let _ = log( - self.logger.std.as_mut(), - "bp3d-logger", - &format!("Could not flush file backend: {}", e), - Level::Error, - ); - } + for v in &mut self.handlers { + v.flush(); } false } Command::Log(buffer) => { - let target = buffer.target(); - let msg = buffer.msg(); - let level = buffer.level(); - if let Err(e) = log(self.logger.file.as_mut(), target, msg, level) { - let _ = log( - self.logger.std.as_mut(), - "bp3d-logger", - &format!("Could not write to file backend: {}", e), - Level::Error, - ); - } - let _ = log(self.logger.std.as_mut(), target, msg, level); - if self.enable_log_buffer { - self.log_buffer.force_push(buffer); + for v in &mut self.handlers { + v.write(&buffer); } false } @@ -128,6 +99,9 @@ impl Thread { } pub fn run(mut self) { + for v in &mut self.handlers { + v.install(&self.enable_stdout); + } while let Ok(v) = self.recv_ch.recv() { let flag = self.exec_commad(v); if flag { @@ -142,7 +116,7 @@ impl Thread { pub struct Logger { send_ch: Sender, enabled: AtomicBool, - enable_stdout: Option>, + enable_stdout: Flag, log_buffer: Arc>, thread: ManuallyDrop>, } @@ -157,9 +131,10 @@ impl Logger { let log_buffer = Arc::new(ArrayQueue::new(buf_size)); let recv_ch1 = recv_ch.clone(); let log_buffer1 = log_buffer.clone(); - let enable_stdout = builder.std.as_ref().map(|v| v.get_enable()); + let enable_stdout = Flag::new(true); + let enable_stdout1 = enable_stdout.clone(); let thread = std::thread::spawn(move || { - let thread = Thread::new(builder, recv_ch1, log_buffer1); + let thread = Thread::new(builder, recv_ch1, log_buffer1, enable_stdout1); thread.run(); }); Logger { @@ -177,7 +152,7 @@ impl Logger { /// /// * `flag`: true to enable stdout, false to disable stdout. pub fn enable_stdout(&self, flag: bool) { - self.enable_stdout.as_ref().map(|v| v.store(flag, Ordering::Release)); + self.enable_stdout.set(flag); } /// Enables this logger. @@ -220,8 +195,9 @@ impl Logger { /// - For file backend the format is \[level\] msg and the message is recorded in the file /// corresponding to the log target. /// - /// WARNING: For optimization reasons, this function does not check and thus does not honor - /// the enabled flag. For a checked log function, use [checked_log](Self::checked_log). + /// WARNING: For optimization reasons, this function does not check and thus does neither honor + /// the enabled flag nor the current log level. For a checked log function, + /// use [checked_log](Self::log). #[inline] pub fn raw_log(&self, msg: &LogMsg) { unsafe { @@ -233,12 +209,12 @@ impl Logger { } } - /// Checked log function. This injects log messages into the logging thread channel only if + /// Main log function. This injects log messages into the logging thread channel only if /// this logger is enabled. /// /// This function calls the [raw_log](Self::raw_log) function only when this logger is enabled. #[inline] - pub fn checked_log(&self, msg: &LogMsg) { + pub fn log(&self, msg: &LogMsg) { if self.is_enabled() { self.raw_log(msg); } @@ -285,3 +261,16 @@ impl Drop for Logger { thread.join().unwrap(); } } + +#[cfg(test)] +mod tests { + use crate::Builder; + + fn ensure_send_sync(_: T) {} + + #[test] + fn basic_test() { + let logger = Builder::new().start(); + ensure_send_sync(logger); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1c968b7..da0d105 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,19 +33,19 @@ //! An async flexible logger framework designed for BP3D software. -mod backend; mod easy_termcolor; mod internal; mod log_msg; pub mod util; mod builder; mod level; +pub mod handler; use bp3d_os::dirs::App; use crossbeam_channel::Receiver; use std::path::PathBuf; -pub use log_msg::LogMsg; +pub use log_msg::{LogMsg, Location}; pub use builder::*; pub use internal::Logger; pub use level::Level; diff --git a/src/log_msg.rs b/src/log_msg.rs index 3484ba0..847693d 100644 --- a/src/log_msg.rs +++ b/src/log_msg.rs @@ -28,17 +28,66 @@ use std::fmt::{Error, Write}; use std::mem::MaybeUninit; +use time::OffsetDateTime; use crate::Level; +use crate::util::extract_target_module; -// Limit the size of the target string to 16 bytes. -const LOG_TARGET_SIZE: usize = 16; // Size of the control fields of the log message structure: -// sizeof msg_len + 1 byte for target_len + 1 byte for level -const LOG_CONTROL_SIZE: usize = std::mem::size_of::() + 2; +// 40 bytes of Location structure (&'static str is 16 bytes) + 16 bytes of OffsetDateTime + 4 bytes of msg len + 1 byte of Level + 3 bytes of padding +const LOG_CONTROL_SIZE: usize = 40 + 16 + 4 + 1 + 3; // Limit the size of the log message string so that the size of the log structure is LOG_BUFFER_SIZE -const LOG_MSG_SIZE: usize = LOG_BUFFER_SIZE - LOG_TARGET_SIZE - LOG_CONTROL_SIZE; +const LOG_MSG_SIZE: usize = LOG_BUFFER_SIZE - LOG_CONTROL_SIZE; const LOG_BUFFER_SIZE: usize = 1024; +/// The context of a log message. +#[derive(Clone, Copy)] +pub struct Location { + module_path: &'static str, + file: &'static str, + line: u32 +} + +impl Location { + /// Creates a new instance of a log message location. + /// + /// This function is const to let the caller store location structures in statics. + /// + /// # Arguments + /// + /// * `module_path`: the module path obtained from the [module_path](module_path) macro. + /// * `file`: the source file obtained from the [file](file) macro. + /// * `line`: the line number in the source file obtained from the [line](line) macro. + /// + /// returns: Metadata + pub const fn new(module_path: &'static str, file: &'static str, line: u32) -> Self { + Self { + module_path, + file, + line + } + } + + /// The module path which issued this log message. + pub fn module_path(&self) -> &'static str { + self.module_path + } + + /// The source file which issued this log message. + pub fn file(&self) -> &'static str { + self.file + } + + /// The line in the source file which issued this log message. + pub fn line(&self) -> u32 { + self.line + } + + /// Extracts the target name and the module name from the module path. + pub fn get_target_module(&self) -> (&'static str, &'static str) { + extract_target_module(self.module_path) + } +} + /// A log message. /// /// This structure uses a large 1K buffer which stores the entire log message to improve @@ -50,19 +99,20 @@ const LOG_BUFFER_SIZE: usize = 1024; /// # Examples /// /// ``` -/// use bp3d_logger::{Level, LogMsg}; +/// use bp3d_logger::{Level, Location, LogMsg}; /// use std::fmt::Write; -/// let mut msg = LogMsg::new("test", Level::Info); +/// let mut msg = LogMsg::new(Location::new("test", "file.c", 1), Level::Info); /// let _ = write!(msg, "This is a formatted message {}", 42); /// assert_eq!(msg.msg(), "This is a formatted message 42"); /// ``` #[derive(Clone)] #[repr(C)] pub struct LogMsg { + location: Location, + time: OffsetDateTime, msg_len: u32, level: Level, - target_len: u8, - buffer: [MaybeUninit; LOG_MSG_SIZE + LOG_TARGET_SIZE], + buffer: [MaybeUninit; LOG_MSG_SIZE], } impl LogMsg { @@ -70,7 +120,28 @@ impl LogMsg { /// /// # Arguments /// - /// * `target`: the target name this log comes from. + /// * `location`: the location this message comes from. + /// * `level`: the [Level](Level) of the log message. + /// + /// returns: LogMsg + /// + /// # Examples + /// + /// ``` + /// use bp3d_logger::{Level, Location, LogMsg}; + /// let msg = LogMsg::new(Location::new("test", "file.c", 1), Level::Info); + /// assert_eq!(msg.location().module_path(), "test"); + /// assert_eq!(msg.level(), Level::Info); + /// ``` + pub fn new(location: Location, level: Level) -> LogMsg { + LogMsg::with_time(location, OffsetDateTime::now_utc(), level) + } + + /// Creates a new instance of log message buffer. + /// + /// # Arguments + /// + /// * `location`: the location this message comes from. /// * `level`: the [Level](Level) of the log message. /// /// returns: LogMsg @@ -78,27 +149,20 @@ impl LogMsg { /// # Examples /// /// ``` - /// use bp3d_logger::{Level, LogMsg}; - /// let msg = LogMsg::new("test", Level::Info); - /// assert_eq!(msg.target(), "test"); + /// use time::macros::datetime; + /// use bp3d_logger::{Level, Location, LogMsg}; + /// let msg = LogMsg::with_time(Location::new("test", "file.c", 1), datetime!(1999-1-1 0:0 UTC), Level::Info); + /// assert_eq!(msg.location().module_path(), "test"); /// assert_eq!(msg.level(), Level::Info); /// ``` - pub fn new(target: &str, level: Level) -> LogMsg { - let len = std::cmp::min(LOG_TARGET_SIZE, target.as_bytes().len()); - let mut buffer = LogMsg { + pub fn with_time(location: Location, time: OffsetDateTime, level: Level) -> LogMsg { + LogMsg { + location, + time, buffer: unsafe { MaybeUninit::uninit().assume_init() }, - target_len: len as _, - msg_len: len as _, + msg_len: 0, level, - }; - unsafe { - std::ptr::copy_nonoverlapping( - target.as_bytes().as_ptr(), - std::mem::transmute(buffer.buffer.as_mut_ptr()), - len, - ); } - buffer } /// Clears the log message but keep the target and the level. @@ -106,16 +170,27 @@ impl LogMsg { /// # Examples /// /// ``` - /// use bp3d_logger::{Level, LogMsg}; - /// let mut msg = LogMsg::from_msg("test", Level::Info, "this is a test"); + /// use bp3d_logger::{Level, Location, LogMsg}; + /// let mut msg = LogMsg::from_msg(Location::new("test", "file.c", 1), Level::Info, "this is a test"); /// msg.clear(); /// assert_eq!(msg.msg(), ""); - /// assert_eq!(msg.target(), "test"); + /// assert_eq!(msg.location().module_path(), "test"); /// assert_eq!(msg.level(), Level::Info); /// ``` #[inline] pub fn clear(&mut self) { - self.msg_len = self.target_len as _; + self.msg_len = 0; + } + + /// Replaces the time contained in this log message. + /// + /// # Arguments + /// + /// * `time`: the new [OffsetDateTime](OffsetDateTime). + /// + /// returns: () + pub fn set_time(&mut self, time: OffsetDateTime) { + self.time = time; } /// Auto-creates a new log message with a pre-defined string message. @@ -133,14 +208,14 @@ impl LogMsg { /// # Examples /// /// ``` - /// use bp3d_logger::{LogMsg, Level}; - /// let mut msg = LogMsg::from_msg("test", Level::Info, "this is a test"); - /// assert_eq!(msg.target(), "test"); + /// use bp3d_logger::{LogMsg, Level, Location}; + /// let mut msg = LogMsg::from_msg(Location::new("test", "file.c", 1), Level::Info, "this is a test"); + /// assert_eq!(msg.location().module_path(), "test"); /// assert_eq!(msg.level(), Level::Info); /// assert_eq!(msg.msg(), "this is a test"); /// ``` - pub fn from_msg(target: &str, level: Level, msg: &str) -> LogMsg { - let mut ads = Self::new(target, level); + pub fn from_msg(location: Location, level: Level, msg: &str) -> LogMsg { + let mut ads = Self::new(location, level); unsafe { ads.write(msg.as_bytes()) }; ads } @@ -174,13 +249,16 @@ impl LogMsg { len } - /// Returns the target name this log comes from. + /// Returns the location the log message comes from. #[inline] - pub fn target(&self) -> &str { - // SAFEY: This is always safe because BufLogMsg is always UTF-8. - unsafe { - std::str::from_utf8_unchecked(std::mem::transmute(&self.buffer[..self.target_len as _])) - } + pub fn location(&self) -> &Location { + &self.location + } + + /// Returns the time of this log message. + #[inline] + pub fn time(&self) -> &OffsetDateTime { + &self.time } /// Returns the log message as a string. @@ -189,7 +267,7 @@ impl LogMsg { // SAFETY: This is always safe because LogMsg is always UTF-8. unsafe { std::str::from_utf8_unchecked(std::mem::transmute( - &self.buffer[self.target_len as _..self.msg_len as _], + &self.buffer[..self.msg_len as _], )) } } diff --git a/src/util.rs b/src/util.rs index c4e4237..d870914 100644 --- a/src/util.rs +++ b/src/util.rs @@ -28,7 +28,9 @@ //! Logging utilities. +use std::fmt::Write; use bp3d_os::time::LocalOffsetDateTime; +use bp3d_util::format::IoToFmt; use time::macros::format_description; use time::OffsetDateTime; use crate::LogMsg; @@ -49,66 +51,35 @@ pub fn extract_target_module(base_string: &str) -> (&str, &str) { (target, module.unwrap_or("main")) } -/// Unsafe [Write](std::io::Write) wrapper for [LogMsg]. -/// -/// This utility is provided for interactions with foreign APIs that only supports writing through -/// the io::Write interface and are GUARANTEED to result in UTF-8 data (ex: time crate). -pub struct IoWrapper<'a>(&'a mut LogMsg); - -impl<'a> IoWrapper<'a> { - /// Creates a new [Write](std::io::Write) wrapper for [LogMsg]. - /// - /// Safety - /// - /// Subsequent calls to [Write](std::io::Write) must result in a valid UTF-8 string - /// once a call to [msg](LogMsg::msg) is made. - pub unsafe fn new(msg: &'a mut LogMsg) -> Self { - Self(msg) - } - - /// Extracts the underlying [LogMsg] from this wrapper. - /// - /// Safety - /// - /// The content of the underlying [LogMsg] MUST be a valid UTF-8 string. - pub unsafe fn into_inner(self) -> &'a mut LogMsg { - self.0 - } -} - -impl<'a> std::io::Write for IoWrapper<'a> { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - unsafe { Ok(self.0.write(buf)) } - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -/// Write time information into the given [LogMsg]. +/// Write time information into the given [Write](Write). /// /// # Arguments /// -/// * `msg`: the [LogMsg] to write time information to. +/// * `msg`: the [Write](Write) to write time information to. /// * `time`: the time to write. /// /// returns: () -pub fn write_time(msg: &mut LogMsg, time: OffsetDateTime) { - unsafe { msg.write(b"(") }; +pub fn write_time(msg: &mut impl Write, time: OffsetDateTime) { + let _ = msg.write_str("("); let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); - let mut wrapper = unsafe { IoWrapper::new(msg) }; + let mut wrapper = IoToFmt::new(msg); let _ = time.format_into(&mut wrapper, format); - unsafe { msg.write(b") ") }; + let msg = wrapper.into_inner(); + let _ = msg.write_str(")"); } -/// Adds the current time to the given [LogMsg]. -/// -/// # Arguments -/// -/// * `msg`: the [LogMsg] to write time information to. -/// -/// returns: () -pub fn add_time(msg: &mut LogMsg) { - write_time(msg, OffsetDateTime::now_local().unwrap_or_else(OffsetDateTime::now_utc)) +#[cfg(test)] +mod tests { + use bp3d_os::time::LocalOffsetDateTime; + use time::OffsetDateTime; + use crate::{Level, Location, LogMsg}; + use crate::util::write_time; + + #[test] + fn fhsdiub() { + let time = OffsetDateTime::now_local().unwrap_or_else(OffsetDateTime::now_utc); + let mut msg = LogMsg::new(Location::new("test", "test.c", 1), Level::Info); + write_time(&mut msg, time); + assert!(msg.msg().len() > 0); + } } From 8e7855f1344cec99cbab4117c8a1261b1ce9d183 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 17 Jun 2024 06:35:55 +0200 Subject: [PATCH 27/64] Replaced log buffer system by a dedicated LogQueueHandler --- src/builder.rs | 16 +++--- src/handler/log_queue.rs | 104 +++++++++++++++++++++++++++++++++++++++ src/handler/mod.rs | 2 + src/internal.rs | 60 ++-------------------- 4 files changed, 118 insertions(+), 64 deletions(-) create mode 100644 src/handler/log_queue.rs diff --git a/src/builder.rs b/src/builder.rs index 83a9ddf..f2e99eb 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -66,25 +66,25 @@ impl Default for Colors { /// /// The following example shows initialization of this logger and use of the log buffer. /// ``` -/// use bp3d_logger::{Builder, Level, LogMsg, Location}; +/// use bp3d_logger::{Builder, Level, LogMsg, Location, handler::{LogQueue, LogQueueHandler}}; /// /// fn main() { -/// let logger = Builder::new().add_stdout().start(); -/// logger.enable_log_buffer(true); // Enable log redirect pump into application channel. +/// let queue = LogQueue::default(); +/// let logger = Builder::new().add_stdout().add_handler(LogQueueHandler::new(queue.clone())).start(); /// /// //... application code with log redirect pump./// /// logger.log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message")); /// logger.enable(false); +/// logger.log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Dropped message")); /// logger.raw_log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message 1")); /// logger.enable(true); /// /// logger.flush(); -/// let l = logger.read_log().unwrap(); // Capture the last log message. +/// let l = queue.pop().unwrap(); // Capture the last log message. /// // We can't test for equality because log messages contains a timestamp... -/// assert!(l.msg().ends_with("Example message")); -/// let l = logger.read_log().unwrap(); -/// assert!(l.msg().ends_with("Example message 1")); -/// logger.enable_log_buffer(false); +/// assert_eq!(l.msg(), "Example message"); +/// let l = queue.pop().unwrap(); +/// assert_eq!(l.msg(), "Example message 1"); /// //... application code without log redirect pump. /// } /// ``` diff --git a/src/handler/log_queue.rs b/src/handler/log_queue.rs new file mode 100644 index 0000000..07d83a1 --- /dev/null +++ b/src/handler/log_queue.rs @@ -0,0 +1,104 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::sync::Arc; +use crossbeam_queue::ArrayQueue; +use crate::handler::{Flag, Handler}; +use crate::LogMsg; + +const DEFAULT_BUF_SIZE: usize = 32; + +/// A log queue. +/// +/// The default size of the log queue is 32 log messages, that is 32 * 1024 = 32768 bytes. +#[derive(Clone)] +pub struct LogQueue(Arc>); + +impl Default for LogQueue { + fn default() -> Self { + Self::new(DEFAULT_BUF_SIZE) + } +} + +impl LogQueue { + /// Creates a new [LogQueue](LogQueue). + /// + /// The queue acts as a ring-buffer, when it is full, new logs are inserted replacing older + /// logs. + /// + /// # Arguments + /// + /// * `buffer_size`: the size of the buffer. + /// + /// returns: LogBuffer + pub fn new(buffer_size: usize) -> Self { + Self(Arc::new(ArrayQueue::new(buffer_size))) + } + + /// Pops an element from the queue if any. + pub fn pop(&self) -> Option { + self.0.pop() + } + + /// Clears the log queue. + pub fn clear(&self) { + while self.pop().is_some() {} + } +} + +/// A basic handler which redirects log messages to a queue. +pub struct LogQueueHandler { + queue: LogQueue +} + +impl LogQueueHandler { + /// Creates a new [LogQueueHandler](LogQueueHandler) + /// + /// # Arguments + /// + /// * `queue`: the queue to record log messages into. + /// + /// returns: LogQueueHandler + pub fn new(queue: LogQueue) -> Self { + Self { + queue + } + } +} + +impl Handler for LogQueueHandler { + fn install(&mut self, _: &Flag) { + } + + fn write(&mut self, msg: &LogMsg) { + self.queue.0.force_push(msg.clone()); + } + + fn flush(&mut self) { + } +} diff --git a/src/handler/mod.rs b/src/handler/mod.rs index 93d7f25..d9be295 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -30,6 +30,7 @@ mod stdout; mod file; +mod log_queue; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -86,3 +87,4 @@ pub trait Handler: Send { pub use file::FileHandler; pub use stdout::StdHandler; +pub use log_queue::{LogQueueHandler, LogQueue}; diff --git a/src/internal.rs b/src/internal.rs index 854bf17..e8cd240 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -28,10 +28,8 @@ use crate::{LogMsg, Builder}; use crossbeam_channel::{bounded, Receiver, Sender}; -use crossbeam_queue::ArrayQueue; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; use crate::handler::{Flag, Handler}; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. @@ -43,32 +41,25 @@ const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. enum Command { Flush, Log(LogMsg), - Terminate, - EnableLogBuffer, - DisableLogBuffer, + Terminate } struct Thread { handlers: Vec>, recv_ch: Receiver, - enable_stdout: Flag, - enable_log_buffer: bool, - log_buffer: Arc>, + enable_stdout: Flag } impl Thread { pub fn new( builder: Builder, recv_ch: Receiver, - log_buffer: Arc>, enable_stdout: Flag ) -> Thread { Thread { handlers: builder.handlers, recv_ch, - enable_stdout, - enable_log_buffer: false, - log_buffer, + enable_stdout } } @@ -87,14 +78,6 @@ impl Thread { } false } - Command::EnableLogBuffer => { - self.enable_log_buffer = true; - false - } - Command::DisableLogBuffer => { - self.enable_log_buffer = false; - false - } } } @@ -117,7 +100,6 @@ pub struct Logger { send_ch: Sender, enabled: AtomicBool, enable_stdout: Flag, - log_buffer: Arc>, thread: ManuallyDrop>, } @@ -128,19 +110,16 @@ impl Logger { pub(crate) fn new(builder: Builder) -> Logger { let buf_size = builder.buf_size.unwrap_or(BUF_SIZE); let (send_ch, recv_ch) = bounded(buf_size); - let log_buffer = Arc::new(ArrayQueue::new(buf_size)); let recv_ch1 = recv_ch.clone(); - let log_buffer1 = log_buffer.clone(); let enable_stdout = Flag::new(true); let enable_stdout1 = enable_stdout.clone(); let thread = std::thread::spawn(move || { - let thread = Thread::new(builder, recv_ch1, log_buffer1, enable_stdout1); + let thread = Thread::new(builder, recv_ch1, enable_stdout1); thread.run(); }); Logger { thread: ManuallyDrop::new(thread), send_ch, - log_buffer, enabled: AtomicBool::new(true), enable_stdout } @@ -160,34 +139,6 @@ impl Logger { self.enabled.store(flag, Ordering::Release); } - /// Enables the log redirect pump. - pub fn enable_log_buffer(&self, flag: bool) { - unsafe { - if flag { - self.send_ch - .send(Command::EnableLogBuffer) - .unwrap_unchecked(); - } else { - self.send_ch - .send(Command::DisableLogBuffer) - .unwrap_unchecked(); - self.clear_log_buffer(); - } - } - } - - /// Clears the log buffer. - #[inline] - pub fn clear_log_buffer(&self) { - while self.log_buffer.pop().is_some() {} //Clear the entire log buffer. - } - - /// Attempts to extract one log message from the buffer. - #[inline] - pub fn read_log(&self) -> Option { - self.log_buffer.pop() - } - /// Low-level log function. This injects log messages directly into the logging thread channel. /// /// This function applies basic formatting depending on the backend: @@ -245,9 +196,6 @@ impl Drop for Logger { // Disable this Logger. self.enable(false); - // Disable the log buffer (this automatically clears it). - self.enable_log_buffer(false); - // Send termination command and join with logging thread. // This cannot panic as send_ch is owned by LoggerImpl which is intended // to be statically allocated. From e33c1dcbc4d606ea1e59f2d63280ebb888903863 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 17 Jun 2024 07:12:59 +0200 Subject: [PATCH 28/64] Added support for LevelFilter --- src/builder.rs | 25 +++++++++---- src/easy_termcolor.rs | 4 -- src/handler/file.rs | 3 +- src/handler/mod.rs | 3 +- src/internal.rs | 41 +++++++++++--------- src/level.rs | 87 +++++++++++++++++++++++++++++++++++++------ src/lib.rs | 2 +- src/util.rs | 2 - 8 files changed, 120 insertions(+), 47 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index f2e99eb..a196d21 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -29,6 +29,7 @@ use crate::GetLogs; use crate::handler::{FileHandler, Handler, StdHandler}; use crate::internal::Logger; +use crate::level::LevelFilter; /// Enum of the different color settings when printing to stdout/stderr. #[derive(Debug, Copy, Clone)] @@ -66,7 +67,7 @@ impl Default for Colors { /// /// The following example shows initialization of this logger and use of the log buffer. /// ``` -/// use bp3d_logger::{Builder, Level, LogMsg, Location, handler::{LogQueue, LogQueueHandler}}; +/// use bp3d_logger::{Builder, Level, LogMsg, Location, LevelFilter, handler::{LogQueue, LogQueueHandler}}; /// /// fn main() { /// let queue = LogQueue::default(); @@ -74,10 +75,10 @@ impl Default for Colors { /// /// //... application code with log redirect pump./// /// logger.log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message")); -/// logger.enable(false); +/// logger.set_filter(LevelFilter::None); /// logger.log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Dropped message")); /// logger.raw_log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message 1")); -/// logger.enable(true); +/// logger.set_filter(LevelFilter::Info); /// /// logger.flush(); /// let l = queue.pop().unwrap(); // Capture the last log message. @@ -92,7 +93,8 @@ pub struct Builder { pub(crate) colors: Colors, pub(crate) smart_stderr: bool, pub(crate) buf_size: Option, - pub(crate) handlers: Vec> + pub(crate) handlers: Vec>, + pub(crate) filter: LevelFilter } impl Default for Builder { @@ -101,7 +103,8 @@ impl Default for Builder { colors: Colors::default(), smart_stderr: true, buf_size: None, - handlers: Vec::new() + handlers: Vec::new(), + filter: LevelFilter::Info } } } @@ -120,6 +123,14 @@ impl Builder { self } + /// Sets the default level filter when initializing the logger. + /// + /// The default is [Info](LevelFilter::Info). + pub fn filter(mut self, filter: LevelFilter) -> Self { + self.filter = filter; + self + } + /// Enables or disables automatic redirection of error logs to stderr. /// /// The default for this flag is true. @@ -153,7 +164,7 @@ impl Builder { } /// Enables stdout logging. - pub fn add_stdout(mut self) -> Self { + pub fn add_stdout(self) -> Self { let motherfuckingrust = self.smart_stderr; let motherfuckingrust1 = self.colors; self.add_handler(StdHandler::new(motherfuckingrust, motherfuckingrust1)) @@ -165,7 +176,7 @@ impl Builder { /// a log directory from various sources. /// /// If the log directory could not be found the function prints an error to stderr. - pub fn add_file(mut self, app: T) -> Self { + pub fn add_file(self, app: T) -> Self { if let Some(logs) = app.get_logs() { self.add_handler(FileHandler::new(logs)) } else { diff --git a/src/easy_termcolor.rs b/src/easy_termcolor.rs index 053f090..2d70791 100644 --- a/src/easy_termcolor.rs +++ b/src/easy_termcolor.rs @@ -56,10 +56,6 @@ impl EasyTermColor { pub fn color(level: Level) -> ColorSpec { match level { - Level::None => ColorSpec::new() - .set_fg(Some(Color::White)) - .set_bold(true) - .clone(), Level::Error => ColorSpec::new() .set_fg(Some(Color::Red)) .set_bold(true) diff --git a/src/handler/file.rs b/src/handler/file.rs index cefb7c4..e631c8b 100644 --- a/src/handler/file.rs +++ b/src/handler/file.rs @@ -32,9 +32,8 @@ use std::io::{BufWriter, Write}; use std::path::PathBuf; use bp3d_util::format::{FixedBufStr, IoToFmt}; use time::format_description::well_known::Iso8601; -use time::OffsetDateTime; use crate::handler::{Flag, Handler}; -use crate::{Level, Location, LogMsg}; +use crate::LogMsg; /// A file handler which writes log messages into different files each named by the target name. pub struct FileHandler { diff --git a/src/handler/mod.rs b/src/handler/mod.rs index d9be295..1c9a6b3 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -34,8 +34,7 @@ mod log_queue; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; -use time::OffsetDateTime; -use crate::{Level, Location, LogMsg}; +use crate::LogMsg; /// A dynamic atomic flag. #[derive(Clone)] diff --git a/src/internal.rs b/src/internal.rs index e8cd240..c272279 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -29,8 +29,9 @@ use crate::{LogMsg, Builder}; use crossbeam_channel::{bounded, Receiver, Sender}; use std::mem::ManuallyDrop; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicU8, Ordering}; use crate::handler::{Flag, Handler}; +use crate::level::LevelFilter; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. @@ -52,12 +53,12 @@ struct Thread { impl Thread { pub fn new( - builder: Builder, + handlers: Vec>, recv_ch: Receiver, enable_stdout: Flag ) -> Thread { Thread { - handlers: builder.handlers, + handlers, recv_ch, enable_stdout } @@ -98,14 +99,11 @@ impl Thread { /// The main Logger type allows to control the entire logger state and submit messages for logging. pub struct Logger { send_ch: Sender, - enabled: AtomicBool, + level: AtomicU8, enable_stdout: Flag, thread: ManuallyDrop>, } -//TODO: Implement log Level checking for Logger2.checked_log and support setting/getting current log level. -//TODO: Implement support for multiple custom log backends. - impl Logger { pub(crate) fn new(builder: Builder) -> Logger { let buf_size = builder.buf_size.unwrap_or(BUF_SIZE); @@ -114,13 +112,13 @@ impl Logger { let enable_stdout = Flag::new(true); let enable_stdout1 = enable_stdout.clone(); let thread = std::thread::spawn(move || { - let thread = Thread::new(builder, recv_ch1, enable_stdout1); + let thread = Thread::new(builder.handlers, recv_ch1, enable_stdout1); thread.run(); }); Logger { thread: ManuallyDrop::new(thread), send_ch, - enabled: AtomicBool::new(true), + level: AtomicU8::new(builder.filter as u8), enable_stdout } } @@ -134,11 +132,6 @@ impl Logger { self.enable_stdout.set(flag); } - /// Enables this logger. - pub fn enable(&self, flag: bool) { - self.enabled.store(flag, Ordering::Release); - } - /// Low-level log function. This injects log messages directly into the logging thread channel. /// /// This function applies basic formatting depending on the backend: @@ -166,15 +159,29 @@ impl Logger { /// This function calls the [raw_log](Self::raw_log) function only when this logger is enabled. #[inline] pub fn log(&self, msg: &LogMsg) { - if self.is_enabled() { + if self.filter() >= msg.level().as_level_filter() { self.raw_log(msg); } } + /// Returns the filter level of this logger instance. + pub fn filter(&self) -> LevelFilter { + unsafe { LevelFilter::from_u8(self.level.load(Ordering::Acquire)).unwrap_unchecked() } + } + + /// Sets the new level filter for this logger. + /// + /// # Arguments + /// + /// * `filter`: the new [LevelFilter](LevelFilter). + pub fn set_filter(&self, filter: LevelFilter) { + self.level.store(filter as u8, Ordering::Release); + } + /// Returns true if the logger is currently enabled and is capturing log messages. #[inline] pub fn is_enabled(&self) -> bool { - self.enabled.load(Ordering::Acquire) + self.filter() > LevelFilter::None } /// Flushes all pending messages. @@ -194,7 +201,7 @@ impl Logger { impl Drop for Logger { fn drop(&mut self) { // Disable this Logger. - self.enable(false); + self.set_filter(LevelFilter::None); // Send termination command and join with logging thread. // This cannot panic as send_ch is owned by LoggerImpl which is intended diff --git a/src/level.rs b/src/level.rs index 2060460..11968bb 100644 --- a/src/level.rs +++ b/src/level.rs @@ -31,42 +31,96 @@ use std::fmt::{Display, Formatter}; /// An enum representing the available verbosity levels of the logger. #[repr(u8)] #[derive(Clone, PartialEq, Copy, Ord, PartialOrd, Eq, Debug, Hash)] -pub enum Level { +pub enum LevelFilter { /// The "none" level. /// /// This level is used to disable logging. None = 0, + /// The "trace" level. + /// + /// Designates very low priority, often extremely verbose, information. + Trace = 1, + + /// The "debug" level. + /// + /// Designates lower priority information. + Debug = 2, + + /// The "info" level. + /// + /// Designates useful information. + Info = 3, + + /// The "warn" level. + /// + /// Designates hazardous situations. + Warn = 4, + /// The "error" level. /// /// Designates very serious errors. // This way these line up with the discriminants for LevelFilter below // This works because Rust treats field-less enums the same way as C does: // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-field-less-enumerations - Error = 1, + Error = 5 +} - /// The "warn" level. +impl LevelFilter { + /// Creates a new [LevelFilter](LevelFilter) from an existing u8 value. /// - /// Designates hazardous situations. - Warn = 2, + /// This returns None if the value could not be found. + /// + /// # Arguments + /// + /// * `value`: the value to convert from. + /// + /// returns: Option + pub fn from_u8(value: u8) -> Option { + if value > 5 { + None + } else { + // SAFETY: This is safe because LevelFilter is a u8 and we have checked that the variant + // index is not out of bounds. + unsafe { std::mem::transmute(value) } + } + } +} + +/// An enum representing the available verbosity levels for a message. +#[repr(u8)] +#[derive(Clone, PartialEq, Copy, Ord, PartialOrd, Eq, Debug, Hash)] +pub enum Level { + /// The "trace" level. + /// + /// Designates very low priority, often extremely verbose, information. + Trace = 1, + + /// The "debug" level. + /// + /// Designates lower priority information. + Debug = 2, /// The "info" level. /// /// Designates useful information. Info = 3, - /// The "debug" level. + /// The "warn" level. /// - /// Designates lower priority information. - Debug = 4, + /// Designates hazardous situations. + Warn = 4, - /// The "trace" level. + /// The "error" level. /// - /// Designates very low priority, often extremely verbose, information. - Trace = 5 + /// Designates very serious errors. + // This way these line up with the discriminants for LevelFilter below + // This works because Rust treats field-less enums the same way as C does: + // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-field-less-enumerations + Error = 5 } -static LOG_LEVEL_NAMES: [&str; 6] = ["OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"]; +static LOG_LEVEL_NAMES: [&str; 6] = ["OFF", "TRACE", "DEBUG", "INFO", "WARNING", "ERROR"]; impl Level { /// Returns the string representation of the `Level`. @@ -75,6 +129,15 @@ impl Level { pub fn as_str(&self) -> &'static str { LOG_LEVEL_NAMES[*self as usize] } + + /// Returns the [LevelFilter](LevelFilter) representation of this log [Level](Level). + #[allow(clippy::missing_transmute_annotations)] + pub fn as_level_filter(&self) -> LevelFilter { + // SAFETY: This is safe because both Level and LevelFilter are u8 values and Level shares + // the same variants as LevelFilter (with the exception that LevelFilter has one more + // variant at 0). + unsafe { std::mem::transmute(*self) } + } } impl Display for Level { diff --git a/src/lib.rs b/src/lib.rs index da0d105..90db761 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ use std::path::PathBuf; pub use log_msg::{LogMsg, Location}; pub use builder::*; pub use internal::Logger; -pub use level::Level; +pub use level::{Level, LevelFilter}; /// The log buffer type. pub type LogBuffer = Receiver; diff --git a/src/util.rs b/src/util.rs index d870914..c66dd6b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -29,11 +29,9 @@ //! Logging utilities. use std::fmt::Write; -use bp3d_os::time::LocalOffsetDateTime; use bp3d_util::format::IoToFmt; use time::macros::format_description; use time::OffsetDateTime; -use crate::LogMsg; /// Extracts the target name and the module path (without the target name) from a full module path string. /// From 68c6331d6ddb3c1c4b7f5c93131b9ad9b30cb97c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 05:15:20 +0000 Subject: [PATCH 29/64] Format Rust code using rustfmt --- src/builder.rs | 6 +++--- src/easy_termcolor.rs | 2 +- src/handler/file.rs | 18 ++++++++++------ src/handler/log_queue.rs | 16 ++++++-------- src/handler/mod.rs | 9 ++++---- src/handler/stdout.rs | 45 ++++++++++++++++++++++++++++------------ src/internal.rs | 16 +++++++------- src/level.rs | 4 ++-- src/lib.rs | 11 +++++----- src/log_msg.rs | 12 +++++------ src/util.rs | 8 +++---- 11 files changed, 82 insertions(+), 65 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index a196d21..8512580 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -26,10 +26,10 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::GetLogs; use crate::handler::{FileHandler, Handler, StdHandler}; use crate::internal::Logger; use crate::level::LevelFilter; +use crate::GetLogs; /// Enum of the different color settings when printing to stdout/stderr. #[derive(Debug, Copy, Clone)] @@ -94,7 +94,7 @@ pub struct Builder { pub(crate) smart_stderr: bool, pub(crate) buf_size: Option, pub(crate) handlers: Vec>, - pub(crate) filter: LevelFilter + pub(crate) filter: LevelFilter, } impl Default for Builder { @@ -104,7 +104,7 @@ impl Default for Builder { smart_stderr: true, buf_size: None, handlers: Vec::new(), - filter: LevelFilter::Info + filter: LevelFilter::Info, } } } diff --git a/src/easy_termcolor.rs b/src/easy_termcolor.rs index 2d70791..2e2a16c 100644 --- a/src/easy_termcolor.rs +++ b/src/easy_termcolor.rs @@ -26,9 +26,9 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use crate::Level; use std::fmt::Display; use termcolor::{Color, ColorSpec}; -use crate::Level; pub struct EasyTermColor(pub T); diff --git a/src/handler/file.rs b/src/handler/file.rs index e631c8b..06b26c6 100644 --- a/src/handler/file.rs +++ b/src/handler/file.rs @@ -26,14 +26,14 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use crate::handler::{Flag, Handler}; +use crate::LogMsg; +use bp3d_util::format::{FixedBufStr, IoToFmt}; use std::collections::HashMap; use std::fs::{File, OpenOptions}; use std::io::{BufWriter, Write}; use std::path::PathBuf; -use bp3d_util::format::{FixedBufStr, IoToFmt}; use time::format_description::well_known::Iso8601; -use crate::handler::{Flag, Handler}; -use crate::LogMsg; /// A file handler which writes log messages into different files each named by the target name. pub struct FileHandler { @@ -75,8 +75,7 @@ impl FileHandler { } impl Handler for FileHandler { - fn install(&mut self, _: &Flag) { - } + fn install(&mut self, _: &Flag) {} fn write(&mut self, msg: &LogMsg) { let (target, module) = msg.location().get_target_module(); @@ -84,7 +83,14 @@ impl Handler for FileHandler { let _ = msg.time().format_into(&mut wrapper, &Iso8601::DEFAULT); let time_str = wrapper.into_inner(); if let Ok(file) = self.get_create_open_file(target) { - let _ = writeln!(file, "[{}] ({}) {}: {}", msg.level(), time_str.str(), module, msg.msg()); + let _ = writeln!( + file, + "[{}] ({}) {}: {}", + msg.level(), + time_str.str(), + module, + msg.msg() + ); } } diff --git a/src/handler/log_queue.rs b/src/handler/log_queue.rs index 07d83a1..beac7ae 100644 --- a/src/handler/log_queue.rs +++ b/src/handler/log_queue.rs @@ -26,10 +26,10 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::sync::Arc; -use crossbeam_queue::ArrayQueue; use crate::handler::{Flag, Handler}; use crate::LogMsg; +use crossbeam_queue::ArrayQueue; +use std::sync::Arc; const DEFAULT_BUF_SIZE: usize = 32; @@ -73,7 +73,7 @@ impl LogQueue { /// A basic handler which redirects log messages to a queue. pub struct LogQueueHandler { - queue: LogQueue + queue: LogQueue, } impl LogQueueHandler { @@ -85,20 +85,16 @@ impl LogQueueHandler { /// /// returns: LogQueueHandler pub fn new(queue: LogQueue) -> Self { - Self { - queue - } + Self { queue } } } impl Handler for LogQueueHandler { - fn install(&mut self, _: &Flag) { - } + fn install(&mut self, _: &Flag) {} fn write(&mut self, msg: &LogMsg) { self.queue.0.force_push(msg.clone()); } - fn flush(&mut self) { - } + fn flush(&mut self) {} } diff --git a/src/handler/mod.rs b/src/handler/mod.rs index 1c9a6b3..30dafe7 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -28,20 +28,19 @@ //! The log handler system, with default provided handlers. -mod stdout; mod file; mod log_queue; +mod stdout; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; use crate::LogMsg; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; /// A dynamic atomic flag. #[derive(Clone)] pub struct Flag(Arc); impl Flag { - /// Creates a new flag. /// /// # Arguments @@ -85,5 +84,5 @@ pub trait Handler: Send { } pub use file::FileHandler; +pub use log_queue::{LogQueue, LogQueueHandler}; pub use stdout::StdHandler; -pub use log_queue::{LogQueueHandler, LogQueue}; diff --git a/src/handler/stdout.rs b/src/handler/stdout.rs index 38c9420..163fd8f 100644 --- a/src/handler/stdout.rs +++ b/src/handler/stdout.rs @@ -26,23 +26,23 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::io::IsTerminal; -use std::mem::MaybeUninit; +use crate::easy_termcolor::{color, EasyTermColor}; +use crate::handler::{Flag, Handler}; +use crate::util::write_time; +use crate::{Colors, Level, Location, LogMsg}; use bp3d_os::time::LocalUtcOffset; use bp3d_util::format::FixedBufStr; +use std::io::IsTerminal; +use std::mem::MaybeUninit; use termcolor::{ColorChoice, ColorSpec, StandardStream}; use time::{OffsetDateTime, UtcOffset}; -use crate::{Colors, Level, Location, LogMsg}; -use crate::easy_termcolor::{color, EasyTermColor}; -use crate::handler::{Flag, Handler}; -use crate::util::write_time; /// A simple stdout/stderr handler which redirects error messages to stderr and other messages to /// stdout. pub struct StdHandler { smart_stderr: bool, colors: Colors, - enable: MaybeUninit + enable: MaybeUninit, } fn format_time_str(time: &OffsetDateTime) -> FixedBufStr<128> { @@ -53,7 +53,13 @@ fn format_time_str(time: &OffsetDateTime) -> FixedBufStr<128> { time_str } -fn write_msg(stream: StandardStream, location: &Location, time: &OffsetDateTime, msg: &str, level: Level) { +fn write_msg( + stream: StandardStream, + location: &Location, + time: &OffsetDateTime, + msg: &str, + level: Level, +) { let (target, module) = location.get_target_module(); let t = ColorSpec::new().set_bold(true).clone(); let time_str = format_time_str(time); @@ -103,7 +109,7 @@ impl StdHandler { StdHandler { smart_stderr, colors, - enable: MaybeUninit::uninit() + enable: MaybeUninit::uninit(), } } @@ -146,13 +152,26 @@ impl Handler for StdHandler { let (target, module) = msg.location().get_target_module(); let time_str = format_time_str(msg.time()); match stream { - Stream::Stderr => eprintln!("<{}> [{}] {} {}: {}", target, msg.level(), time_str.str(), module, msg.msg()), - _ => println!("<{}> [{}] {} {}: {}", target, msg.level(), time_str.str(), module, msg.msg()) + Stream::Stderr => eprintln!( + "<{}> [{}] {} {}: {}", + target, + msg.level(), + time_str.str(), + module, + msg.msg() + ), + _ => println!( + "<{}> [{}] {} {}: {}", + target, + msg.level(), + time_str.str(), + module, + msg.msg() + ), }; } }; } - fn flush(&mut self) { - } + fn flush(&mut self) {} } diff --git a/src/internal.rs b/src/internal.rs index c272279..309c75e 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -26,12 +26,12 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{LogMsg, Builder}; +use crate::handler::{Flag, Handler}; +use crate::level::LevelFilter; +use crate::{Builder, LogMsg}; use crossbeam_channel::{bounded, Receiver, Sender}; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicU8, Ordering}; -use crate::handler::{Flag, Handler}; -use crate::level::LevelFilter; const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. @@ -42,25 +42,25 @@ const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. enum Command { Flush, Log(LogMsg), - Terminate + Terminate, } struct Thread { handlers: Vec>, recv_ch: Receiver, - enable_stdout: Flag + enable_stdout: Flag, } impl Thread { pub fn new( handlers: Vec>, recv_ch: Receiver, - enable_stdout: Flag + enable_stdout: Flag, ) -> Thread { Thread { handlers, recv_ch, - enable_stdout + enable_stdout, } } @@ -119,7 +119,7 @@ impl Logger { thread: ManuallyDrop::new(thread), send_ch, level: AtomicU8::new(builder.filter as u8), - enable_stdout + enable_stdout, } } diff --git a/src/level.rs b/src/level.rs index 11968bb..ad2d8ba 100644 --- a/src/level.rs +++ b/src/level.rs @@ -63,7 +63,7 @@ pub enum LevelFilter { // This way these line up with the discriminants for LevelFilter below // This works because Rust treats field-less enums the same way as C does: // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-field-less-enumerations - Error = 5 + Error = 5, } impl LevelFilter { @@ -117,7 +117,7 @@ pub enum Level { // This way these line up with the discriminants for LevelFilter below // This works because Rust treats field-less enums the same way as C does: // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-field-less-enumerations - Error = 5 + Error = 5, } static LOG_LEVEL_NAMES: [&str; 6] = ["OFF", "TRACE", "DEBUG", "INFO", "WARNING", "ERROR"]; diff --git a/src/lib.rs b/src/lib.rs index 90db761..58ec51f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,22 +33,22 @@ //! An async flexible logger framework designed for BP3D software. +mod builder; mod easy_termcolor; +pub mod handler; mod internal; +mod level; mod log_msg; pub mod util; -mod builder; -mod level; -pub mod handler; use bp3d_os::dirs::App; use crossbeam_channel::Receiver; use std::path::PathBuf; -pub use log_msg::{LogMsg, Location}; pub use builder::*; pub use internal::Logger; pub use level::{Level, LevelFilter}; +pub use log_msg::{Location, LogMsg}; /// The log buffer type. pub type LogBuffer = Receiver; @@ -86,8 +86,7 @@ impl<'a> GetLogs for &'a str { pub struct Guard; impl Drop for Guard { - fn drop(&mut self) { - } + fn drop(&mut self) {} } /// Runs a closure in scope of a logger configuration, then free the given logger configuration diff --git a/src/log_msg.rs b/src/log_msg.rs index 847693d..48aa305 100644 --- a/src/log_msg.rs +++ b/src/log_msg.rs @@ -26,11 +26,11 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use crate::util::extract_target_module; +use crate::Level; use std::fmt::{Error, Write}; use std::mem::MaybeUninit; use time::OffsetDateTime; -use crate::Level; -use crate::util::extract_target_module; // Size of the control fields of the log message structure: // 40 bytes of Location structure (&'static str is 16 bytes) + 16 bytes of OffsetDateTime + 4 bytes of msg len + 1 byte of Level + 3 bytes of padding @@ -44,7 +44,7 @@ const LOG_BUFFER_SIZE: usize = 1024; pub struct Location { module_path: &'static str, file: &'static str, - line: u32 + line: u32, } impl Location { @@ -63,7 +63,7 @@ impl Location { Self { module_path, file, - line + line, } } @@ -266,9 +266,7 @@ impl LogMsg { pub fn msg(&self) -> &str { // SAFETY: This is always safe because LogMsg is always UTF-8. unsafe { - std::str::from_utf8_unchecked(std::mem::transmute( - &self.buffer[..self.msg_len as _], - )) + std::str::from_utf8_unchecked(std::mem::transmute(&self.buffer[..self.msg_len as _])) } } diff --git a/src/util.rs b/src/util.rs index c66dd6b..b133cd9 100644 --- a/src/util.rs +++ b/src/util.rs @@ -28,8 +28,8 @@ //! Logging utilities. -use std::fmt::Write; use bp3d_util::format::IoToFmt; +use std::fmt::Write; use time::macros::format_description; use time::OffsetDateTime; @@ -67,11 +67,11 @@ pub fn write_time(msg: &mut impl Write, time: OffsetDateTime) { } #[cfg(test)] -mod tests { +mod tests { + use crate::util::write_time; + use crate::{Level, Location, LogMsg}; use bp3d_os::time::LocalOffsetDateTime; use time::OffsetDateTime; - use crate::{Level, Location, LogMsg}; - use crate::util::write_time; #[test] fn fhsdiub() { From b7826d1398c892426eaa2b4df962c1bd88b909e7 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 17 Jun 2024 07:18:11 +0200 Subject: [PATCH 30/64] Updated GitHub workflows --- .github/workflows/development.yml | 14 +++++++------- .github/workflows/release.yml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 64e8000..f5410d6 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -16,7 +16,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Build @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -54,7 +54,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install Audit Tool @@ -73,7 +73,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -94,7 +94,7 @@ jobs: ispre: ${{ steps.version.outputs.ispre }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get Version id: version uses: bp3d-actions/cargo-version@main @@ -109,7 +109,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Setup cargo @@ -132,7 +132,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create Pull Request uses: repo-sync/pull-request@master with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf2e432..8c47f5c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: ispre: ${{ steps.version.outputs.ispre }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get Version id: version uses: bp3d-actions/cargo-version@main @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Setup cargo From 5053fce1869cd472510c0f5af2625763b6dfe93d Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 17 Jun 2024 08:08:26 +0200 Subject: [PATCH 31/64] Updated GitHub workflow to macos-12 --- .github/workflows/development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index f5410d6..295b310 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -11,7 +11,7 @@ jobs: matrix: os: - ubuntu-20.04 - - macos-11 + - macos-12 - windows-2019 runs-on: ${{ matrix.os }} steps: From 0de379a973658a7e00f2789669c65b5db1d30142 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 17 Jun 2024 18:36:30 +0200 Subject: [PATCH 32/64] Fixed clippy warnings --- src/handler/file.rs | 2 +- src/level.rs | 1 + src/log_msg.rs | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/handler/file.rs b/src/handler/file.rs index 06b26c6..b2c1aeb 100644 --- a/src/handler/file.rs +++ b/src/handler/file.rs @@ -60,7 +60,7 @@ impl FileHandler { &mut self, target: &str, ) -> Result<&mut BufWriter, std::io::Error> { - if self.targets.get(target).is_none() { + if self.targets.contains_key(target) { let f = OpenOptions::new() .append(true) .create(true) diff --git a/src/level.rs b/src/level.rs index ad2d8ba..24d9bc4 100644 --- a/src/level.rs +++ b/src/level.rs @@ -76,6 +76,7 @@ impl LevelFilter { /// * `value`: the value to convert from. /// /// returns: Option + #[allow(clippy::missing_transmute_annotations)] pub fn from_u8(value: u8) -> Option { if value > 5 { None diff --git a/src/log_msg.rs b/src/log_msg.rs index 48aa305..cabf918 100644 --- a/src/log_msg.rs +++ b/src/log_msg.rs @@ -236,6 +236,7 @@ impl LogMsg { /// bytes. /// * If buf contains invalid UTF-8 bytes, further operations on the log message buffer may /// result in UB. + #[allow(clippy::missing_transmute_annotations)] pub unsafe fn write(&mut self, buf: &[u8]) -> usize { let len = std::cmp::min(buf.len(), LOG_MSG_SIZE - self.msg_len as usize); if len > 0 { @@ -263,6 +264,7 @@ impl LogMsg { /// Returns the log message as a string. #[inline] + #[allow(clippy::missing_transmute_annotations)] pub fn msg(&self) -> &str { // SAFETY: This is always safe because LogMsg is always UTF-8. unsafe { From 39e9c5c3127a5e55837496f98398515e5bea3f71 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 17 Jun 2024 18:38:12 +0200 Subject: [PATCH 33/64] Fixed bug in file log handler --- src/handler/file.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handler/file.rs b/src/handler/file.rs index b2c1aeb..f140f81 100644 --- a/src/handler/file.rs +++ b/src/handler/file.rs @@ -60,7 +60,7 @@ impl FileHandler { &mut self, target: &str, ) -> Result<&mut BufWriter, std::io::Error> { - if self.targets.contains_key(target) { + if !self.targets.contains_key(target) { let f = OpenOptions::new() .append(true) .create(true) From 0f7e73f208595f2f21351747bcd8299effea1dac Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 17 Jun 2024 18:45:33 +0200 Subject: [PATCH 34/64] Updated crate version in preparation of next pre-release for use with bp3d-tracing rewrite --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9383c09..1809c11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-logger" -version = "2.0.0-rc.1.0.0" +version = "2.0.0-rc.2.0.0" authors = ["Yuri Edward "] edition = "2021" description = "A flexible Log implementation intended to be used with BP3D software." From e3c447465f4874a7e21cc3b6dafe37b6e0a192f1 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 22 Jun 2024 11:42:34 +0200 Subject: [PATCH 35/64] Bumped version of bp3d-os --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1809c11..a101164 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-logger" -version = "2.0.0-rc.2.0.0" +version = "2.0.0-rc.2.0.1" authors = ["Yuri Edward "] edition = "2021" description = "A flexible Log implementation intended to be used with BP3D software." @@ -13,7 +13,7 @@ categories = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bp3d-os = { version = "1.0.0-rc.3.0.2", features=["dirs", "time"] } +bp3d-os = { version = "1.0.0-rc.3.0.3", features=["dirs", "time"] } bp3d-util = { version = "1.0.0", features = ["format"] } crossbeam-channel = "0.5.2" crossbeam-queue = "0.3.8" From 9cebe5f9c1e5ebc864ffacf28bc225a2116a1232 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Thu, 18 Jul 2024 21:25:13 +0200 Subject: [PATCH 36/64] Added new location macro to util --- Cargo.toml | 2 +- src/util.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a101164..d3cf7d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-logger" -version = "2.0.0-rc.2.0.1" +version = "2.0.0-rc.2.1.0" authors = ["Yuri Edward "] edition = "2021" description = "A flexible Log implementation intended to be used with BP3D software." diff --git a/src/util.rs b/src/util.rs index b133cd9..5401926 100644 --- a/src/util.rs +++ b/src/util.rs @@ -66,6 +66,12 @@ pub fn write_time(msg: &mut impl Write, time: OffsetDateTime) { let _ = msg.write_str(")"); } +/// Generate a [Location](crate::Location) structure. +#[macro_export] +macro_rules! location { + () => {$crate::Location::new(module_path!(), file!(), line!())}; +} + #[cfg(test)] mod tests { use crate::util::write_time; From 892f9b5656cd5ee90533204d46c36d7cb5f425eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 19:27:18 +0000 Subject: [PATCH 37/64] Format Rust code using rustfmt --- src/util.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/util.rs b/src/util.rs index 5401926..9a85f75 100644 --- a/src/util.rs +++ b/src/util.rs @@ -69,7 +69,9 @@ pub fn write_time(msg: &mut impl Write, time: OffsetDateTime) { /// Generate a [Location](crate::Location) structure. #[macro_export] macro_rules! location { - () => {$crate::Location::new(module_path!(), file!(), line!())}; + () => { + $crate::Location::new(module_path!(), file!(), line!()) + }; } #[cfg(test)] From e27f85860f66f474898165ec93c6e241d60de99f Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 20 Jul 2024 15:10:56 +0200 Subject: [PATCH 38/64] Updated to the new reusable workflows --- .github/workflows/development.yml | 146 +++--------------------------- .github/workflows/release.yml | 44 +-------- 2 files changed, 16 insertions(+), 174 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 295b310..185113a 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -5,137 +5,15 @@ on: - workflow_dispatch jobs: - test-build: - name: Build & Test - strategy: - matrix: - os: - - ubuntu-20.04 - - macos-12 - - windows-2019 - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - name: Build - run: cargo build --all-features - - name: Test - uses: bp3d-actions/cargo@main - with: - check-name: cargo test (${{ matrix.os }}) - command: test - args: --all-features --no-fail-fast - token: ${{ secrets.GITHUB_TOKEN }} - - clippy: - name: Check | Clippy - if: ${{ always() }} - needs: test-build - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - name: Run check - uses: bp3d-actions/clippy-check@main - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features - - audit: - name: Check | Audit - if: ${{ always() }} - needs: test-build - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - name: Install Audit Tool - run: cargo install cargo-audit - - name: Run check - uses: bp3d-actions/audit-check@main - with: - token: ${{ secrets.GITHUB_TOKEN }} - - fmt: - name: Format Code - if: ${{ always() && github.ref != 'refs/heads/master' }} - needs: - - clippy - - audit - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - name: Run code formatter - uses: bp3d-actions/rustfmt-check@main - with: - token: ${{ secrets.GITHUB_TOKEN }} - - version: - name: Get Version - needs: test-build - runs-on: ubuntu-latest - outputs: - name: ${{ steps.version.outputs.name }} - version: ${{ steps.version.outputs.version }} - isnew: ${{ steps.version.outputs.isnew }} - ispre: ${{ steps.version.outputs.ispre }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Get Version - id: version - uses: bp3d-actions/cargo-version@main - with: - mode: get - token: ${{ secrets.GITHUB_TOKEN }} - - create-pre-release: - name: Create Pre Release - needs: version - if: github.ref == 'refs/heads/develop' && needs.version.outputs.isnew == 'true' && needs.version.outputs.ispre == 'true' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - name: Setup cargo - run: cargo login ${{ secrets.RELEASE_TOKEN }} - - name: Publish - run: cargo publish - - name: Create - uses: ncipollo/release-action@main - with: - tag: ${{ needs.version.outputs.version }} - commit: ${{ github.ref }} - prerelease: true - name: ${{ needs.version.outputs.name }} release ${{ needs.version.outputs.version }} - body: "[Link to crates.io](https://crates.io/crates/${{ needs.version.outputs.name }})" - - create-release-pr: - name: Create Release Pull Request - needs: version - if: needs.version.outputs.isnew == 'true' && needs.version.outputs.ispre == 'false' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Create Pull Request - uses: repo-sync/pull-request@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - destination_branch: master - pr_title: Release ${{ needs.version.outputs.version }} + Test: + uses: BlockProject3D/workflows/.github/workflows/Build_Test.yml@main + + Analyze: + uses: BlockProject3D/workflows/.github/workflows/Analyze.yml@main + needs: Test + + Release: + uses: BlockProject3D/workflows/.github/workflows/Release.yml@main + needs: Test + secrets: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c47f5c..b4df58a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,43 +6,7 @@ on: - master jobs: - version: - name: Get Version - runs-on: ubuntu-latest - outputs: - name: ${{ steps.version.outputs.name }} - version: ${{ steps.version.outputs.version }} - isnew: ${{ steps.version.outputs.isnew }} - ispre: ${{ steps.version.outputs.ispre }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Get Version - id: version - uses: bp3d-actions/cargo-version@main - with: - mode: get - token: ${{ secrets.GITHUB_TOKEN }} - - create-release: - name: Release - needs: version - if: needs.version.outputs.isnew == 'true' && needs.version.outputs.ispre == 'false' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - name: Setup cargo - run: cargo login ${{ secrets.RELEASE_TOKEN }} - - name: Publish - run: cargo publish - - name: Create - uses: ncipollo/release-action@main - with: - tag: ${{ needs.version.outputs.version }} - commit: ${{ github.ref }} - prerelease: false - name: ${{ needs.version.outputs.name }} release ${{ needs.version.outputs.version }} - body: "[Link to crates.io](https://crates.io/crates/${{ needs.version.outputs.name }})" + Publish: + uses: BlockProject3D/workflows/.github/workflows/Publish.yml@main + secrets: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} From 7dcec8cf7d4421e01fa46d130aa73c989086f743 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 20 Jul 2024 15:32:59 +0200 Subject: [PATCH 39/64] Moved the entire bp3d-debug repository to this repository --- Cargo.toml | 25 +- LICENSE.txt | 27 -- README.MD | 24 +- src/builder.rs | 200 ------------ src/{handler => engine}/mod.rs | 83 +++-- src/engine/void.rs | 83 +++++ src/field.rs | 138 +++++++++ src/handler/file.rs | 102 ------- src/handler/log_queue.rs | 100 ------ src/handler/stdout.rs | 177 ----------- src/internal.rs | 231 -------------- src/level.rs | 148 --------- src/lib.rs | 74 +---- src/log_msg.rs | 289 ------------------ .../interface.rs} | 68 ++--- src/logger/macros.rs | 79 +++++ src/logger/mod.rs | 32 ++ src/profiler/interface.rs | 36 +++ src/profiler/macros.rs | 41 +++ src/profiler/mod.rs | 33 ++ src/profiler/section.rs | 158 ++++++++++ src/trace/future.rs | 66 ++++ src/trace/interface.rs | 44 +++ src/trace/macros.rs | 35 +++ src/trace/mod.rs | 34 +++ src/trace/span.rs | 118 +++++++ src/util.rs | 91 ------ 27 files changed, 981 insertions(+), 1555 deletions(-) delete mode 100644 LICENSE.txt delete mode 100644 src/builder.rs rename src/{handler => engine}/mod.rs (54%) create mode 100644 src/engine/void.rs create mode 100644 src/field.rs delete mode 100644 src/handler/file.rs delete mode 100644 src/handler/log_queue.rs delete mode 100644 src/handler/stdout.rs delete mode 100644 src/internal.rs delete mode 100644 src/level.rs delete mode 100644 src/log_msg.rs rename src/{easy_termcolor.rs => logger/interface.rs} (55%) create mode 100644 src/logger/macros.rs create mode 100644 src/logger/mod.rs create mode 100644 src/profiler/interface.rs create mode 100644 src/profiler/macros.rs create mode 100644 src/profiler/mod.rs create mode 100644 src/profiler/section.rs create mode 100644 src/trace/future.rs create mode 100644 src/trace/interface.rs create mode 100644 src/trace/macros.rs create mode 100644 src/trace/mod.rs create mode 100644 src/trace/span.rs delete mode 100644 src/util.rs diff --git a/Cargo.toml b/Cargo.toml index d3cf7d0..a155616 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,19 @@ [package] -name = "bp3d-logger" -version = "2.0.0-rc.2.1.0" +name = "bp3d-debug" +version = "1.0.0-rc.1.0.0" authors = ["Yuri Edward "] edition = "2021" -description = "A flexible Log implementation intended to be used with BP3D software." +description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." license = "BSD-3-Clause" -repository = "https://github.com/BlockProject3D/debug.logger" -readme = "./README.MD" -keywords = [] -categories = [] +repository = "https://gitlab.com/bp3d/tracing/tracing" +readme = "../README.MD" +keywords = ["bp3d", "tracing"] +categories = ["development-tools", "development-tools::debugging", "development-tools::profiling"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bp3d-os = { version = "1.0.0-rc.3.0.3", features=["dirs", "time"] } -bp3d-util = { version = "1.0.0", features = ["format"] } -crossbeam-channel = "0.5.2" -crossbeam-queue = "0.3.8" -once_cell = "1.10.0" -time = { version = "0.3.0", features = ["formatting", "macros"] } -termcolor = "1.1.3" +bp3d-logger = "2.0.0-rc.2.1.0" -[features] +[build-dependencies] +semver = "1.0.7" diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index f180790..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2021, BlockProject 3D - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of BlockProject 3D nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.MD b/README.MD index 8695fc9..8a47d7f 100644 --- a/README.MD +++ b/README.MD @@ -1,23 +1,11 @@ ![logo](https://assets.gitlab-static.net/uploads/-/system/group/avatar/10553166/logo_white.ico?width=64) -# BP3D logger +# Debug core -A flexible [Log](https://docs.rs/log/latest/log/trait.Log.html) implementation intended to be used with BP3D software. +The debug core contains Rust APIs used to debug BP3D software. This contains a set of tools optimized for use by BP3D based projects with soft real-time constraints. -## Main features -- Support for file logging. -- Support for stdout/stderr logging. -- Support for in memory logger. -- Easily switch on/off logging backends. -- Logging backends should not panic/abort/crash the software in any way. - -### stdout/stderr -- Error messages are written to stderr instead of stdout (configurable). -- If write fails, message is ignored; no panic produced. - -### file logging -- If write fails, an error is sent to other logging backends, if other backends fails, message is ignored; no panic produced. - -### in memory -- Log is limited to a fixed number of messages (configurable). +## Features +- A logger system with trace disabled in release builds for improved performance. +- A simple profiler system which can efficiently measure the time spent in Rust code scope. +- A trace system designed to trace asynchronous and long-running operations. diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index 8512580..0000000 --- a/src/builder.rs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) 2024, BlockProject 3D -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of BlockProject 3D nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use crate::handler::{FileHandler, Handler, StdHandler}; -use crate::internal::Logger; -use crate::level::LevelFilter; -use crate::GetLogs; - -/// Enum of the different color settings when printing to stdout/stderr. -#[derive(Debug, Copy, Clone)] -pub enum Colors { - /// Color printing is always enabled. - Enabled, - - /// Color printing is always disabled. - Disabled, - - /// Color printing is automatic (if current terminal is a tty, print with colors, otherwise - /// print without colors). - Auto, -} - -impl Default for Colors { - fn default() -> Self { - Self::Disabled - } -} - -/// The base logger builder/initializer. -/// -/// # Examples -/// -/// The following example shows basic initialization of this logger. -/// ``` -/// use bp3d_logger::{Builder, Level, LogMsg, Location}; -/// -/// fn main() { -/// let logger = Builder::new().add_stdout().add_file("my-app").start(); -/// logger.log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message")); -/// } -/// ``` -/// -/// The following example shows initialization of this logger and use of the log buffer. -/// ``` -/// use bp3d_logger::{Builder, Level, LogMsg, Location, LevelFilter, handler::{LogQueue, LogQueueHandler}}; -/// -/// fn main() { -/// let queue = LogQueue::default(); -/// let logger = Builder::new().add_stdout().add_handler(LogQueueHandler::new(queue.clone())).start(); -/// -/// //... application code with log redirect pump./// -/// logger.log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message")); -/// logger.set_filter(LevelFilter::None); -/// logger.log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Dropped message")); -/// logger.raw_log(&LogMsg::from_msg(Location::new("bp3d-logger", "test.c", 1), Level::Info, "Example message 1")); -/// logger.set_filter(LevelFilter::Info); -/// -/// logger.flush(); -/// let l = queue.pop().unwrap(); // Capture the last log message. -/// // We can't test for equality because log messages contains a timestamp... -/// assert_eq!(l.msg(), "Example message"); -/// let l = queue.pop().unwrap(); -/// assert_eq!(l.msg(), "Example message 1"); -/// //... application code without log redirect pump. -/// } -/// ``` -pub struct Builder { - pub(crate) colors: Colors, - pub(crate) smart_stderr: bool, - pub(crate) buf_size: Option, - pub(crate) handlers: Vec>, - pub(crate) filter: LevelFilter, -} - -impl Default for Builder { - fn default() -> Self { - Self { - colors: Colors::default(), - smart_stderr: true, - buf_size: None, - handlers: Vec::new(), - filter: LevelFilter::Info, - } - } -} - -impl Builder { - /// Creates a new instance of a logger builder. - pub fn new() -> Builder { - Builder::default() - } - - /// Sets the colors state when logging to stdout/stderr. - /// - /// The default behavior is to disable colors. - pub fn colors(mut self, state: Colors) -> Self { - self.colors = state; - self - } - - /// Sets the default level filter when initializing the logger. - /// - /// The default is [Info](LevelFilter::Info). - pub fn filter(mut self, filter: LevelFilter) -> Self { - self.filter = filter; - self - } - - /// Enables or disables automatic redirection of error logs to stderr. - /// - /// The default for this flag is true. - pub fn smart_stderr(mut self, flag: bool) -> Self { - self.smart_stderr = flag; - self - } - - /// Sets the buffer size. - /// - /// # Arguments - /// - /// * `buf_size`: the buffer size. - /// - /// returns: Builder - pub fn buffer_size(mut self, buf_size: usize) -> Self { - self.buf_size = Some(buf_size); - self - } - - /// Adds a new log [Handler](Handler). - /// - /// # Arguments - /// - /// * `handler`: the new handler implementation to add. - /// - /// returns: Builder - pub fn add_handler(mut self, handler: T) -> Self { - self.handlers.push(Box::new(handler)); - self - } - - /// Enables stdout logging. - pub fn add_stdout(self) -> Self { - let motherfuckingrust = self.smart_stderr; - let motherfuckingrust1 = self.colors; - self.add_handler(StdHandler::new(motherfuckingrust, motherfuckingrust1)) - } - - /// Enables file logging to the given application. - /// - /// The application is given as a reference to [GetLogs](GetLogs) to allow obtaining - /// a log directory from various sources. - /// - /// If the log directory could not be found the function prints an error to stderr. - pub fn add_file(self, app: T) -> Self { - if let Some(logs) = app.get_logs() { - self.add_handler(FileHandler::new(logs)) - } else { - eprintln!("Failed to obtain application log directory"); - self - } - } - - /// Initializes the log implementation with this current configuration. - /// - /// NOTE: This returns an instance of [Logger](Logger) which is the main entry point for all - /// logging based operations. This instance also acts as a guard to flush all log buffers - /// before returning. It is necessary to flush log buffers because this implementation - /// uses threads to avoid blocking the main thread when issuing logs. - /// - /// NOTE 2: There are no safety concerns with running twice this function in the same - /// application, only that calling this function may be slow due to thread management. - pub fn start(self) -> Logger { - Logger::new(self) - } -} diff --git a/src/handler/mod.rs b/src/engine/mod.rs similarity index 54% rename from src/handler/mod.rs rename to src/engine/mod.rs index 30dafe7..3bd0e2c 100644 --- a/src/handler/mod.rs +++ b/src/engine/mod.rs @@ -26,63 +26,50 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//! The log handler system, with default provided handlers. +use std::sync::atomic::{AtomicBool, Ordering}; -mod file; -mod log_queue; -mod stdout; +mod void; -use crate::LogMsg; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; +pub trait Engine: + crate::logger::Logger + crate::profiler::Profiler + crate::trace::Tracer + Sync +{ +} +impl Engine + for T +{ +} -/// A dynamic atomic flag. -#[derive(Clone)] -pub struct Flag(Arc); +static ENGINE_INIT_FLAG: AtomicBool = AtomicBool::new(false); -impl Flag { - /// Creates a new flag. - /// - /// # Arguments - /// - /// * `initial`: the initial value of this flag. - /// - /// returns: Flag - pub fn new(initial: bool) -> Self { - Self(Arc::new(AtomicBool::new(initial))) - } +static mut ENGINE: &dyn Engine = &void::VoidDebugger {}; - /// Returns true if this flag is ON, false otherwise. - pub fn is_enabled(&self) -> bool { - self.0.load(Ordering::Acquire) - } +pub fn get() -> &'static dyn Engine { + unsafe { ENGINE } +} - /// Sets this flag. - pub fn set(&self, flag: bool) { - self.0.store(flag, Ordering::Release); +pub fn set(engine: &'static dyn Engine) -> bool { + let flag = ENGINE_INIT_FLAG.load(Ordering::Relaxed); + if flag { + return false; } + unsafe { ENGINE = engine }; + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + true } -/// The main handler trait. -pub trait Handler: Send { - /// Called when the handler is installed in the async logging thread. - /// - /// # Arguments - /// - /// * `enable_stdout`: boolean flag to know if printing to stdout is allowed. - fn install(&mut self, enable_stdout: &Flag); +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; - /// Called when a message is being written. - /// - /// # Arguments - /// - /// * `msg`: the log message which was emitted as a [LogMsg](LogMsg). - fn write(&mut self, msg: &LogMsg); + #[test] + fn basic() { + crate::engine::set(&crate::engine::void::VoidDebugger {}); + assert!(!crate::engine::set(&crate::engine::void::VoidDebugger {})); + } - /// Called when the flush command is received in the async logging thread. - fn flush(&mut self); + #[test] + fn after_use() { + crate::engine::get().span_exit(unsafe { NonZeroU32::new_unchecked(1) }); + assert!(!crate::engine::set(&crate::engine::void::VoidDebugger {})); + } } - -pub use file::FileHandler; -pub use log_queue::{LogQueue, LogQueueHandler}; -pub use stdout::StdHandler; diff --git a/src/engine/void.rs b/src/engine/void.rs new file mode 100644 index 0000000..32cbf4d --- /dev/null +++ b/src/engine/void.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::engine::ENGINE_INIT_FLAG; +use crate::field::Field; +use crate::trace::span::Callsite; +use std::fmt::Arguments; +use std::num::NonZeroU32; +use std::sync::atomic::Ordering; + +pub struct VoidDebugger {} + +impl crate::profiler::Profiler for VoidDebugger { + fn section_register(&self, _: &'static crate::profiler::section::Section) -> NonZeroU32 { + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + unsafe { NonZeroU32::new_unchecked(1) } + } + + fn section_record(&self, _: NonZeroU32, _: u64, _: u64, _: &[Field]) { + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + } +} + +impl crate::trace::Tracer for VoidDebugger { + fn register_callsite(&self, _: &'static Callsite) -> NonZeroU32 { + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + unsafe { NonZeroU32::new_unchecked(1) } + } + + fn span_create(&self, _: NonZeroU32, _: &[Field]) -> NonZeroU32 { + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + unsafe { NonZeroU32::new_unchecked(1) } + } + + fn span_enter(&self, _: NonZeroU32) { + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + } + + fn span_record(&self, _: NonZeroU32, _: &[Field]) { + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + } + + fn span_exit(&self, _: NonZeroU32) { + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + } +} + +impl crate::logger::Logger for VoidDebugger { + fn log(&self, callsite: &'static crate::logger::Callsite, args: Arguments, _: &[Field]) { + println!( + "[{}] {}: {}", + callsite.level(), + callsite.location().module_path(), + args + ); + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + } +} diff --git a/src/field.rs b/src/field.rs new file mode 100644 index 0000000..b45c9aa --- /dev/null +++ b/src/field.rs @@ -0,0 +1,138 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::fmt::Debug; + +pub enum FieldValue<'a> { + Int(i64), + UInt(u64), + Float(f32), + Double(f64), + String(&'a str), + Debug(&'a dyn Debug), +} + +pub struct Field<'a> { + name: &'a str, + value: FieldValue<'a>, +} + +impl<'a> Field<'a> { + pub fn new(name: &'a str, value: impl Into>) -> Self { + Self { + name, + value: value.into(), + } + } + + pub fn new_debug(name: &'a str, value: &'a dyn Debug) -> Self { + Self { + name, + value: FieldValue::Debug(value), + } + } + + pub fn name(&self) -> &str { + self.name + } + + pub fn value(&self) -> &FieldValue<'a> { + &self.value + } +} + +macro_rules! impl_into_field_value { + // Would've preferred expr, but turns out expr is useless in macros, so let's not use it. + ($($t: ty => $func: ident),*) => { + $( + impl<'a> From<$t> for FieldValue<'a> { + fn from(value: $t) -> Self { + FieldValue::$func(value as _) + } + } + )* + }; +} + +impl_into_field_value! { + u8 => UInt, + u16 => UInt, + u32 => UInt, + u64 => UInt, + i8 => Int, + i16 => Int, + i32 => Int, + i64 => Int, + f32 => Float, + f64 => Double +} + +impl<'a> From<&'a str> for FieldValue<'a> { + fn from(value: &'a str) -> Self { + Self::String(value) + } +} + +pub struct FieldSet<'a, const N: usize>([Field<'a>; N]); + +impl<'a, const N: usize> FieldSet<'a, N> { + pub fn new(fields: [Field<'a>; N]) -> Self { + Self(fields) + } +} + +impl<'a, const N: usize> AsRef<[Field<'a>]> for FieldSet<'a, N> { + fn as_ref(&self) -> &[Field<'a>] { + &self.0 + } +} + +#[macro_export] +macro_rules! field { + ($name: ident) => { + $crate::field::Field::new(stringify!($name), $name) + }; + (?$name: ident) => { + $crate::field::Field::new_debug(stringify!($name), &$name) + }; + ($name: ident = $value: expr) => { + $crate::field::Field::new(stringify!($name), $value) + }; + ($name: ident = ?$value: expr) => { + $crate::field::Field::new_debug(stringify!($name), &$value) + }; +} + +#[macro_export] +macro_rules! fields { + ($({$($field: tt)*})*) => { + [$( + $crate::field!($($field)*), + )*] + }; +} diff --git a/src/handler/file.rs b/src/handler/file.rs deleted file mode 100644 index f140f81..0000000 --- a/src/handler/file.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2024, BlockProject 3D -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of BlockProject 3D nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use crate::handler::{Flag, Handler}; -use crate::LogMsg; -use bp3d_util::format::{FixedBufStr, IoToFmt}; -use std::collections::HashMap; -use std::fs::{File, OpenOptions}; -use std::io::{BufWriter, Write}; -use std::path::PathBuf; -use time::format_description::well_known::Iso8601; - -/// A file handler which writes log messages into different files each named by the target name. -pub struct FileHandler { - targets: HashMap>, - path: PathBuf, -} - -impl FileHandler { - /// Creates a new [FileHandler](FileHandler). - /// - /// # Arguments - /// - /// * `path`: the path to the base folder which should contain logs. - /// - /// returns: FileHandler - pub fn new(path: PathBuf) -> FileHandler { - FileHandler { - targets: HashMap::new(), - path, - } - } - - fn get_create_open_file( - &mut self, - target: &str, - ) -> Result<&mut BufWriter, std::io::Error> { - if !self.targets.contains_key(target) { - let f = OpenOptions::new() - .append(true) - .create(true) - .open(self.path.join(format!("{}.log", target)))?; - self.targets.insert(target.into(), BufWriter::new(f)); - } - unsafe { - // This cannot never fail because None is captured and initialized by the if block. - Ok(self.targets.get_mut(target).unwrap_unchecked()) - } - } -} - -impl Handler for FileHandler { - fn install(&mut self, _: &Flag) {} - - fn write(&mut self, msg: &LogMsg) { - let (target, module) = msg.location().get_target_module(); - let mut wrapper = IoToFmt::new(FixedBufStr::<128>::new()); - let _ = msg.time().format_into(&mut wrapper, &Iso8601::DEFAULT); - let time_str = wrapper.into_inner(); - if let Ok(file) = self.get_create_open_file(target) { - let _ = writeln!( - file, - "[{}] ({}) {}: {}", - msg.level(), - time_str.str(), - module, - msg.msg() - ); - } - } - - fn flush(&mut self) { - for v in self.targets.values_mut() { - let _ = v.flush(); - } - } -} diff --git a/src/handler/log_queue.rs b/src/handler/log_queue.rs deleted file mode 100644 index beac7ae..0000000 --- a/src/handler/log_queue.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2024, BlockProject 3D -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of BlockProject 3D nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use crate::handler::{Flag, Handler}; -use crate::LogMsg; -use crossbeam_queue::ArrayQueue; -use std::sync::Arc; - -const DEFAULT_BUF_SIZE: usize = 32; - -/// A log queue. -/// -/// The default size of the log queue is 32 log messages, that is 32 * 1024 = 32768 bytes. -#[derive(Clone)] -pub struct LogQueue(Arc>); - -impl Default for LogQueue { - fn default() -> Self { - Self::new(DEFAULT_BUF_SIZE) - } -} - -impl LogQueue { - /// Creates a new [LogQueue](LogQueue). - /// - /// The queue acts as a ring-buffer, when it is full, new logs are inserted replacing older - /// logs. - /// - /// # Arguments - /// - /// * `buffer_size`: the size of the buffer. - /// - /// returns: LogBuffer - pub fn new(buffer_size: usize) -> Self { - Self(Arc::new(ArrayQueue::new(buffer_size))) - } - - /// Pops an element from the queue if any. - pub fn pop(&self) -> Option { - self.0.pop() - } - - /// Clears the log queue. - pub fn clear(&self) { - while self.pop().is_some() {} - } -} - -/// A basic handler which redirects log messages to a queue. -pub struct LogQueueHandler { - queue: LogQueue, -} - -impl LogQueueHandler { - /// Creates a new [LogQueueHandler](LogQueueHandler) - /// - /// # Arguments - /// - /// * `queue`: the queue to record log messages into. - /// - /// returns: LogQueueHandler - pub fn new(queue: LogQueue) -> Self { - Self { queue } - } -} - -impl Handler for LogQueueHandler { - fn install(&mut self, _: &Flag) {} - - fn write(&mut self, msg: &LogMsg) { - self.queue.0.force_push(msg.clone()); - } - - fn flush(&mut self) {} -} diff --git a/src/handler/stdout.rs b/src/handler/stdout.rs deleted file mode 100644 index 163fd8f..0000000 --- a/src/handler/stdout.rs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) 2024, BlockProject 3D -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of BlockProject 3D nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use crate::easy_termcolor::{color, EasyTermColor}; -use crate::handler::{Flag, Handler}; -use crate::util::write_time; -use crate::{Colors, Level, Location, LogMsg}; -use bp3d_os::time::LocalUtcOffset; -use bp3d_util::format::FixedBufStr; -use std::io::IsTerminal; -use std::mem::MaybeUninit; -use termcolor::{ColorChoice, ColorSpec, StandardStream}; -use time::{OffsetDateTime, UtcOffset}; - -/// A simple stdout/stderr handler which redirects error messages to stderr and other messages to -/// stdout. -pub struct StdHandler { - smart_stderr: bool, - colors: Colors, - enable: MaybeUninit, -} - -fn format_time_str(time: &OffsetDateTime) -> FixedBufStr<128> { - let offset = UtcOffset::local_offset_at(*time); - let time = offset.map(|v| time.to_offset(v)).unwrap_or(*time); - let mut time_str = FixedBufStr::<128>::new(); - write_time(&mut time_str, time); - time_str -} - -fn write_msg( - stream: StandardStream, - location: &Location, - time: &OffsetDateTime, - msg: &str, - level: Level, -) { - let (target, module) = location.get_target_module(); - let t = ColorSpec::new().set_bold(true).clone(); - let time_str = format_time_str(time); - EasyTermColor(stream) - .write('<') - .color(t) - .write(target) - .reset() - .write("> ") - .write('[') - .color(color(level)) - .write(level) - .reset() - .write("] ") - .write(time_str.str()) - .write(" ") - .write(module) - .write(": ") - .write(msg) - .lf(); -} - -enum Stream { - Stdout, - Stderr, -} - -impl Stream { - pub fn isatty(&self) -> bool { - match self { - Stream::Stdout => std::io::stdout().is_terminal(), - Stream::Stderr => std::io::stderr().is_terminal(), - } - } -} - -impl StdHandler { - /// Creates a new [StdHandler](StdHandler). - /// - /// # Arguments - /// - /// * `smart_stderr`: true to enable redirecting error logs to stderr, false otherwise. - /// * `colors`: the printing color policy. - /// - /// returns: StdHandler - pub fn new(smart_stderr: bool, colors: Colors) -> StdHandler { - StdHandler { - smart_stderr, - colors, - enable: MaybeUninit::uninit(), - } - } - - fn get_stream(&self, level: Level) -> Stream { - match self.smart_stderr { - false => Stream::Stdout, - true => match level { - Level::Error => Stream::Stderr, - _ => Stream::Stdout, - }, - } - } -} - -impl Handler for StdHandler { - fn install(&mut self, enable_stdout: &Flag) { - self.enable.write(enable_stdout.clone()); - } - - fn write(&mut self, msg: &LogMsg) { - if !unsafe { self.enable.assume_init_ref().is_enabled() } { - // Skip logging if temporarily disabled. - return; - } - let stream = self.get_stream(msg.level()); - let use_termcolor = match self.colors { - Colors::Disabled => false, - Colors::Enabled => true, - Colors::Auto => stream.isatty(), - }; - match use_termcolor { - true => { - let val = match stream { - Stream::Stderr => StandardStream::stderr(ColorChoice::Always), - _ => StandardStream::stdout(ColorChoice::Always), - }; - write_msg(val, msg.location(), msg.time(), msg.msg(), msg.level()); - } - false => { - let (target, module) = msg.location().get_target_module(); - let time_str = format_time_str(msg.time()); - match stream { - Stream::Stderr => eprintln!( - "<{}> [{}] {} {}: {}", - target, - msg.level(), - time_str.str(), - module, - msg.msg() - ), - _ => println!( - "<{}> [{}] {} {}: {}", - target, - msg.level(), - time_str.str(), - module, - msg.msg() - ), - }; - } - }; - } - - fn flush(&mut self) {} -} diff --git a/src/internal.rs b/src/internal.rs deleted file mode 100644 index 309c75e..0000000 --- a/src/internal.rs +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) 2024, BlockProject 3D -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of BlockProject 3D nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use crate::handler::{Flag, Handler}; -use crate::level::LevelFilter; -use crate::{Builder, LogMsg}; -use crossbeam_channel::{bounded, Receiver, Sender}; -use std::mem::ManuallyDrop; -use std::sync::atomic::{AtomicU8, Ordering}; - -const BUF_SIZE: usize = 16; // The maximum count of log messages in the channel. - -//Disable large_enum_variant as using a Box will inevitably cause a small allocation on a critical path, -//allocating in a critical code path will most likely result in degraded performance. -//And yes, logging is a critical path when using bp3d-tracing. -#[allow(clippy::large_enum_variant)] -enum Command { - Flush, - Log(LogMsg), - Terminate, -} - -struct Thread { - handlers: Vec>, - recv_ch: Receiver, - enable_stdout: Flag, -} - -impl Thread { - pub fn new( - handlers: Vec>, - recv_ch: Receiver, - enable_stdout: Flag, - ) -> Thread { - Thread { - handlers, - recv_ch, - enable_stdout, - } - } - - fn exec_commad(&mut self, cmd: Command) -> bool { - match cmd { - Command::Terminate => true, - Command::Flush => { - for v in &mut self.handlers { - v.flush(); - } - false - } - Command::Log(buffer) => { - for v in &mut self.handlers { - v.write(&buffer); - } - false - } - } - } - - pub fn run(mut self) { - for v in &mut self.handlers { - v.install(&self.enable_stdout); - } - while let Ok(v) = self.recv_ch.recv() { - let flag = self.exec_commad(v); - if flag { - // The thread has requested to exit itself; drop out of the main loop. - break; - } - } - } -} - -/// The main Logger type allows to control the entire logger state and submit messages for logging. -pub struct Logger { - send_ch: Sender, - level: AtomicU8, - enable_stdout: Flag, - thread: ManuallyDrop>, -} - -impl Logger { - pub(crate) fn new(builder: Builder) -> Logger { - let buf_size = builder.buf_size.unwrap_or(BUF_SIZE); - let (send_ch, recv_ch) = bounded(buf_size); - let recv_ch1 = recv_ch.clone(); - let enable_stdout = Flag::new(true); - let enable_stdout1 = enable_stdout.clone(); - let thread = std::thread::spawn(move || { - let thread = Thread::new(builder.handlers, recv_ch1, enable_stdout1); - thread.run(); - }); - Logger { - thread: ManuallyDrop::new(thread), - send_ch, - level: AtomicU8::new(builder.filter as u8), - enable_stdout, - } - } - - /// Enables the stdout/stderr logger. - /// - /// # Arguments - /// - /// * `flag`: true to enable stdout, false to disable stdout. - pub fn enable_stdout(&self, flag: bool) { - self.enable_stdout.set(flag); - } - - /// Low-level log function. This injects log messages directly into the logging thread channel. - /// - /// This function applies basic formatting depending on the backend: - /// - For stdout/stderr backend the format is \ \[level\] msg. - /// - For file backend the format is \[level\] msg and the message is recorded in the file - /// corresponding to the log target. - /// - /// WARNING: For optimization reasons, this function does not check and thus does neither honor - /// the enabled flag nor the current log level. For a checked log function, - /// use [checked_log](Self::log). - #[inline] - pub fn raw_log(&self, msg: &LogMsg) { - unsafe { - // This cannot panic as send_ch is owned by LoggerImpl which is intended - // to be statically allocated. - self.send_ch - .send(Command::Log(msg.clone())) - .unwrap_unchecked(); - } - } - - /// Main log function. This injects log messages into the logging thread channel only if - /// this logger is enabled. - /// - /// This function calls the [raw_log](Self::raw_log) function only when this logger is enabled. - #[inline] - pub fn log(&self, msg: &LogMsg) { - if self.filter() >= msg.level().as_level_filter() { - self.raw_log(msg); - } - } - - /// Returns the filter level of this logger instance. - pub fn filter(&self) -> LevelFilter { - unsafe { LevelFilter::from_u8(self.level.load(Ordering::Acquire)).unwrap_unchecked() } - } - - /// Sets the new level filter for this logger. - /// - /// # Arguments - /// - /// * `filter`: the new [LevelFilter](LevelFilter). - pub fn set_filter(&self, filter: LevelFilter) { - self.level.store(filter as u8, Ordering::Release); - } - - /// Returns true if the logger is currently enabled and is capturing log messages. - #[inline] - pub fn is_enabled(&self) -> bool { - self.filter() > LevelFilter::None - } - - /// Flushes all pending messages. - pub fn flush(&self) { - if !self.is_enabled() { - return; - } - unsafe { - // This cannot panic as send_ch is owned by LoggerImpl which is intended - // to be statically allocated. - self.send_ch.send(Command::Flush).unwrap_unchecked(); - while !self.send_ch.is_empty() {} - } - } -} - -impl Drop for Logger { - fn drop(&mut self) { - // Disable this Logger. - self.set_filter(LevelFilter::None); - - // Send termination command and join with logging thread. - // This cannot panic as send_ch is owned by LoggerImpl which is intended - // to be statically allocated. - unsafe { - self.send_ch.send(Command::Flush).unwrap_unchecked(); - self.send_ch.send(Command::Terminate).unwrap_unchecked(); - } - - // Join the logging thread; this will lock until the thread is completely terminated. - let thread = unsafe { ManuallyDrop::into_inner(std::ptr::read(&self.thread)) }; - thread.join().unwrap(); - } -} - -#[cfg(test)] -mod tests { - use crate::Builder; - - fn ensure_send_sync(_: T) {} - - #[test] - fn basic_test() { - let logger = Builder::new().start(); - ensure_send_sync(logger); - } -} diff --git a/src/level.rs b/src/level.rs deleted file mode 100644 index 24d9bc4..0000000 --- a/src/level.rs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) 2024, BlockProject 3D -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of BlockProject 3D nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use std::fmt::{Display, Formatter}; - -/// An enum representing the available verbosity levels of the logger. -#[repr(u8)] -#[derive(Clone, PartialEq, Copy, Ord, PartialOrd, Eq, Debug, Hash)] -pub enum LevelFilter { - /// The "none" level. - /// - /// This level is used to disable logging. - None = 0, - - /// The "trace" level. - /// - /// Designates very low priority, often extremely verbose, information. - Trace = 1, - - /// The "debug" level. - /// - /// Designates lower priority information. - Debug = 2, - - /// The "info" level. - /// - /// Designates useful information. - Info = 3, - - /// The "warn" level. - /// - /// Designates hazardous situations. - Warn = 4, - - /// The "error" level. - /// - /// Designates very serious errors. - // This way these line up with the discriminants for LevelFilter below - // This works because Rust treats field-less enums the same way as C does: - // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-field-less-enumerations - Error = 5, -} - -impl LevelFilter { - /// Creates a new [LevelFilter](LevelFilter) from an existing u8 value. - /// - /// This returns None if the value could not be found. - /// - /// # Arguments - /// - /// * `value`: the value to convert from. - /// - /// returns: Option - #[allow(clippy::missing_transmute_annotations)] - pub fn from_u8(value: u8) -> Option { - if value > 5 { - None - } else { - // SAFETY: This is safe because LevelFilter is a u8 and we have checked that the variant - // index is not out of bounds. - unsafe { std::mem::transmute(value) } - } - } -} - -/// An enum representing the available verbosity levels for a message. -#[repr(u8)] -#[derive(Clone, PartialEq, Copy, Ord, PartialOrd, Eq, Debug, Hash)] -pub enum Level { - /// The "trace" level. - /// - /// Designates very low priority, often extremely verbose, information. - Trace = 1, - - /// The "debug" level. - /// - /// Designates lower priority information. - Debug = 2, - - /// The "info" level. - /// - /// Designates useful information. - Info = 3, - - /// The "warn" level. - /// - /// Designates hazardous situations. - Warn = 4, - - /// The "error" level. - /// - /// Designates very serious errors. - // This way these line up with the discriminants for LevelFilter below - // This works because Rust treats field-less enums the same way as C does: - // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-field-less-enumerations - Error = 5, -} - -static LOG_LEVEL_NAMES: [&str; 6] = ["OFF", "TRACE", "DEBUG", "INFO", "WARNING", "ERROR"]; - -impl Level { - /// Returns the string representation of the `Level`. - /// - /// This returns the same string as the `fmt::Display` implementation. - pub fn as_str(&self) -> &'static str { - LOG_LEVEL_NAMES[*self as usize] - } - - /// Returns the [LevelFilter](LevelFilter) representation of this log [Level](Level). - #[allow(clippy::missing_transmute_annotations)] - pub fn as_level_filter(&self) -> LevelFilter { - // SAFETY: This is safe because both Level and LevelFilter are u8 values and Level shares - // the same variants as LevelFilter (with the exception that LevelFilter has one more - // variant at 0). - unsafe { std::mem::transmute(*self) } - } -} - -impl Display for Level { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} diff --git a/src/lib.rs b/src/lib.rs index 58ec51f..49d6447 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,72 +26,8 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// The reason why this is needed is because the 3 examples of usage of the Logger struct requires -// some context to not make it confusing. -#![allow(clippy::needless_doctest_main)] -#![warn(missing_docs)] - -//! An async flexible logger framework designed for BP3D software. - -mod builder; -mod easy_termcolor; -pub mod handler; -mod internal; -mod level; -mod log_msg; -pub mod util; - -use bp3d_os::dirs::App; -use crossbeam_channel::Receiver; -use std::path::PathBuf; - -pub use builder::*; -pub use internal::Logger; -pub use level::{Level, LevelFilter}; -pub use log_msg::{Location, LogMsg}; - -/// The log buffer type. -pub type LogBuffer = Receiver; - -/// Trait to allow getting a log directory from either a bp3d_os::dirs::App or a String. -pub trait GetLogs { - /// Gets the log directory as a PathBuf. - /// - /// Returns None if no directory could be computed. - fn get_logs(self) -> Option; -} - -impl<'a> GetLogs for &'a String { - fn get_logs(self) -> Option { - self.as_str().get_logs() - } -} - -impl<'a, 'b> GetLogs for &'a App<'b> { - fn get_logs(self) -> Option { - self.get_logs().map(|v| v.create())?.ok().map(|v| v.into()) - } -} - -impl<'a> GetLogs for &'a str { - fn get_logs(self) -> Option { - let app = App::new(self); - app.get_logs().map(|v| v.create())?.ok().map(|v| v.into()) - } -} - -/// Represents a logger guard. -/// -/// WARNING: Once this guard is dropped messages are no longer captured. -pub struct Guard; - -impl Drop for Guard { - fn drop(&mut self) {} -} - -/// Runs a closure in scope of a logger configuration, then free the given logger configuration -/// and return closure result. -pub fn with_logger R>(logger: Builder, f: F) -> R { - let _guard = logger.start(); - f() -} +pub mod engine; +pub mod field; +pub mod logger; +pub mod profiler; +pub mod trace; diff --git a/src/log_msg.rs b/src/log_msg.rs deleted file mode 100644 index cabf918..0000000 --- a/src/log_msg.rs +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright (c) 2024, BlockProject 3D -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of BlockProject 3D nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use crate::util::extract_target_module; -use crate::Level; -use std::fmt::{Error, Write}; -use std::mem::MaybeUninit; -use time::OffsetDateTime; - -// Size of the control fields of the log message structure: -// 40 bytes of Location structure (&'static str is 16 bytes) + 16 bytes of OffsetDateTime + 4 bytes of msg len + 1 byte of Level + 3 bytes of padding -const LOG_CONTROL_SIZE: usize = 40 + 16 + 4 + 1 + 3; -// Limit the size of the log message string so that the size of the log structure is LOG_BUFFER_SIZE -const LOG_MSG_SIZE: usize = LOG_BUFFER_SIZE - LOG_CONTROL_SIZE; -const LOG_BUFFER_SIZE: usize = 1024; - -/// The context of a log message. -#[derive(Clone, Copy)] -pub struct Location { - module_path: &'static str, - file: &'static str, - line: u32, -} - -impl Location { - /// Creates a new instance of a log message location. - /// - /// This function is const to let the caller store location structures in statics. - /// - /// # Arguments - /// - /// * `module_path`: the module path obtained from the [module_path](module_path) macro. - /// * `file`: the source file obtained from the [file](file) macro. - /// * `line`: the line number in the source file obtained from the [line](line) macro. - /// - /// returns: Metadata - pub const fn new(module_path: &'static str, file: &'static str, line: u32) -> Self { - Self { - module_path, - file, - line, - } - } - - /// The module path which issued this log message. - pub fn module_path(&self) -> &'static str { - self.module_path - } - - /// The source file which issued this log message. - pub fn file(&self) -> &'static str { - self.file - } - - /// The line in the source file which issued this log message. - pub fn line(&self) -> u32 { - self.line - } - - /// Extracts the target name and the module name from the module path. - pub fn get_target_module(&self) -> (&'static str, &'static str) { - extract_target_module(self.module_path) - } -} - -/// A log message. -/// -/// This structure uses a large 1K buffer which stores the entire log message to improve -/// performance. -/// -/// The repr(C) is used to force the control fields (msg_len, level and target_len) to be before -/// the message buffer and avoid large movs when setting control fields. -/// -/// # Examples -/// -/// ``` -/// use bp3d_logger::{Level, Location, LogMsg}; -/// use std::fmt::Write; -/// let mut msg = LogMsg::new(Location::new("test", "file.c", 1), Level::Info); -/// let _ = write!(msg, "This is a formatted message {}", 42); -/// assert_eq!(msg.msg(), "This is a formatted message 42"); -/// ``` -#[derive(Clone)] -#[repr(C)] -pub struct LogMsg { - location: Location, - time: OffsetDateTime, - msg_len: u32, - level: Level, - buffer: [MaybeUninit; LOG_MSG_SIZE], -} - -impl LogMsg { - /// Creates a new instance of log message buffer. - /// - /// # Arguments - /// - /// * `location`: the location this message comes from. - /// * `level`: the [Level](Level) of the log message. - /// - /// returns: LogMsg - /// - /// # Examples - /// - /// ``` - /// use bp3d_logger::{Level, Location, LogMsg}; - /// let msg = LogMsg::new(Location::new("test", "file.c", 1), Level::Info); - /// assert_eq!(msg.location().module_path(), "test"); - /// assert_eq!(msg.level(), Level::Info); - /// ``` - pub fn new(location: Location, level: Level) -> LogMsg { - LogMsg::with_time(location, OffsetDateTime::now_utc(), level) - } - - /// Creates a new instance of log message buffer. - /// - /// # Arguments - /// - /// * `location`: the location this message comes from. - /// * `level`: the [Level](Level) of the log message. - /// - /// returns: LogMsg - /// - /// # Examples - /// - /// ``` - /// use time::macros::datetime; - /// use bp3d_logger::{Level, Location, LogMsg}; - /// let msg = LogMsg::with_time(Location::new("test", "file.c", 1), datetime!(1999-1-1 0:0 UTC), Level::Info); - /// assert_eq!(msg.location().module_path(), "test"); - /// assert_eq!(msg.level(), Level::Info); - /// ``` - pub fn with_time(location: Location, time: OffsetDateTime, level: Level) -> LogMsg { - LogMsg { - location, - time, - buffer: unsafe { MaybeUninit::uninit().assume_init() }, - msg_len: 0, - level, - } - } - - /// Clears the log message but keep the target and the level. - /// - /// # Examples - /// - /// ``` - /// use bp3d_logger::{Level, Location, LogMsg}; - /// let mut msg = LogMsg::from_msg(Location::new("test", "file.c", 1), Level::Info, "this is a test"); - /// msg.clear(); - /// assert_eq!(msg.msg(), ""); - /// assert_eq!(msg.location().module_path(), "test"); - /// assert_eq!(msg.level(), Level::Info); - /// ``` - #[inline] - pub fn clear(&mut self) { - self.msg_len = 0; - } - - /// Replaces the time contained in this log message. - /// - /// # Arguments - /// - /// * `time`: the new [OffsetDateTime](OffsetDateTime). - /// - /// returns: () - pub fn set_time(&mut self, time: OffsetDateTime) { - self.time = time; - } - - /// Auto-creates a new log message with a pre-defined string message. - /// - /// This function is the same as calling [write](LogMsg::write) after [new](LogMsg::new). - /// - /// # Arguments - /// - /// * `target`: the target name this log comes from. - /// * `level`: the [Level](Level) of the log message. - /// * `msg`: the message string. - /// - /// returns: LogMsg - /// - /// # Examples - /// - /// ``` - /// use bp3d_logger::{LogMsg, Level, Location}; - /// let mut msg = LogMsg::from_msg(Location::new("test", "file.c", 1), Level::Info, "this is a test"); - /// assert_eq!(msg.location().module_path(), "test"); - /// assert_eq!(msg.level(), Level::Info); - /// assert_eq!(msg.msg(), "this is a test"); - /// ``` - pub fn from_msg(location: Location, level: Level, msg: &str) -> LogMsg { - let mut ads = Self::new(location, level); - unsafe { ads.write(msg.as_bytes()) }; - ads - } - - /// Appends a raw byte buffer at the end of the message buffer. - /// - /// Returns the number of bytes written. - /// - /// # Arguments - /// - /// * `buf`: the raw byte buffer to append. - /// - /// returns: usize - /// - /// # Safety - /// - /// * [LogMsg](LogMsg) contains only valid UTF-8 strings so buf must contain only valid UTF-8 - /// bytes. - /// * If buf contains invalid UTF-8 bytes, further operations on the log message buffer may - /// result in UB. - #[allow(clippy::missing_transmute_annotations)] - pub unsafe fn write(&mut self, buf: &[u8]) -> usize { - let len = std::cmp::min(buf.len(), LOG_MSG_SIZE - self.msg_len as usize); - if len > 0 { - std::ptr::copy_nonoverlapping( - buf.as_ptr(), - std::mem::transmute(self.buffer.as_mut_ptr().offset(self.msg_len as _)), - len, - ); - self.msg_len += len as u32; //The length is always less than 2^32. - } - len - } - - /// Returns the location the log message comes from. - #[inline] - pub fn location(&self) -> &Location { - &self.location - } - - /// Returns the time of this log message. - #[inline] - pub fn time(&self) -> &OffsetDateTime { - &self.time - } - - /// Returns the log message as a string. - #[inline] - #[allow(clippy::missing_transmute_annotations)] - pub fn msg(&self) -> &str { - // SAFETY: This is always safe because LogMsg is always UTF-8. - unsafe { - std::str::from_utf8_unchecked(std::mem::transmute(&self.buffer[..self.msg_len as _])) - } - } - - /// Returns the level of this log message. - #[inline] - pub fn level(&self) -> Level { - self.level - } -} - -impl Write for LogMsg { - fn write_str(&mut self, s: &str) -> Result<(), Error> { - unsafe { - self.write(s.as_bytes()); - } - Ok(()) - } -} diff --git a/src/easy_termcolor.rs b/src/logger/interface.rs similarity index 55% rename from src/easy_termcolor.rs rename to src/logger/interface.rs index 2e2a16c..fc0c917 100644 --- a/src/easy_termcolor.rs +++ b/src/logger/interface.rs @@ -26,55 +26,43 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::Level; -use std::fmt::Display; -use termcolor::{Color, ColorSpec}; +use crate::field::Field; +use bp3d_logger::{Level, Location}; +use std::fmt::Arguments; -pub struct EasyTermColor(pub T); +pub struct Callsite { + location: Location, + level: Level, +} -impl EasyTermColor { - pub fn write(mut self, elem: impl Display) -> Self { - let _ = write!(&mut self.0, "{}", elem); - self +impl Callsite { + pub const fn new(location: Location, level: Level) -> Self { + Self { location, level } } - pub fn color(mut self, elem: ColorSpec) -> Self { - let _ = self.0.set_color(&elem); - self + pub fn location(&self) -> &Location { + &self.location } - pub fn reset(mut self) -> Self { - let _ = self.0.reset(); - self + pub fn level(&self) -> Level { + self.level } +} - pub fn lf(mut self) -> Self { - let _ = writeln!(&mut self.0); - self - } +pub trait Logger { + fn log(&self, callsite: &'static Callsite, msg: Arguments, fields: &[Field]); } -pub fn color(level: Level) -> ColorSpec { - match level { - Level::Error => ColorSpec::new() - .set_fg(Some(Color::Red)) - .set_bold(true) - .clone(), - Level::Warn => ColorSpec::new() - .set_fg(Some(Color::Yellow)) - .set_bold(true) - .clone(), - Level::Info => ColorSpec::new() - .set_fg(Some(Color::Green)) - .set_bold(true) - .clone(), - Level::Debug => ColorSpec::new() - .set_fg(Some(Color::Blue)) - .set_bold(true) - .clone(), - Level::Trace => ColorSpec::new() - .set_fg(Some(Color::Cyan)) - .set_bold(true) - .clone(), +#[cfg(test)] +mod tests { + use crate::{log, trace}; + use bp3d_logger::Level; + + #[test] + fn api_test() { + let i = 42; + log!(Level::Info, { i }, "test: {i}: {}", i); + log!(Level::Error, "test: {}", i); + trace!({i} {?i} {id=i}, "test: {}", i); } } diff --git a/src/logger/macros.rs b/src/logger/macros.rs new file mode 100644 index 0000000..c0980dc --- /dev/null +++ b/src/logger/macros.rs @@ -0,0 +1,79 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[macro_export] +macro_rules! log { + ($level: expr, $({$($field: tt)*})*, $msg: literal $(,$($args: tt)*)?) => { + { + static _CALLSITE: $crate::logger::Callsite = $crate::logger::Callsite::new(bp3d_logger::location!(), $level); + $crate::engine::get().log(&_CALLSITE, format_args!($msg $(, $($args),*)?), &[$($crate::field!($($field)*),)*]); + } + }; + ($level: expr, $msg: literal $(,$($args: tt)*)?) => { + { + static _CALLSITE: $crate::logger::Callsite = $crate::logger::Callsite::new(bp3d_logger::location!(), $level); + $crate::engine::get().log(&_CALLSITE, format_args!($msg $(, $($args),*)?), &[]); + } + }; +} + +#[macro_export] +macro_rules! trace { + ($($args: tt)*) => { + #[cfg(debug_assertions)] + $crate::log!(bp3d_logger::Level::Trace, $($args)*); + }; +} + +#[macro_export] +macro_rules! debug { + ($($args: tt)*) => { + $crate::log!(bp3d_logger::Level::Debug, $($args)*); + }; +} + +#[macro_export] +macro_rules! info { + ($($args: tt)*) => { + $crate::log!(bp3d_logger::Level::Info, $($args)*); + }; +} + +#[macro_export] +macro_rules! warning { + ($($args: tt)*) => { + $crate::log!(bp3d_logger::Level::Warn, $($args)*); + }; +} + +#[macro_export] +macro_rules! error { + ($($args: tt)*) => { + $crate::log!(bp3d_logger::Level::Error, $($args)*); + }; +} diff --git a/src/logger/mod.rs b/src/logger/mod.rs new file mode 100644 index 0000000..b6ce8b2 --- /dev/null +++ b/src/logger/mod.rs @@ -0,0 +1,32 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod interface; +pub mod macros; + +pub use interface::*; diff --git a/src/profiler/interface.rs b/src/profiler/interface.rs new file mode 100644 index 0000000..27364c7 --- /dev/null +++ b/src/profiler/interface.rs @@ -0,0 +1,36 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::field::Field; +use crate::profiler::section::Section; +use std::num::NonZeroU32; + +pub trait Profiler { + fn section_register(&self, section: &'static Section) -> NonZeroU32; + fn section_record(&self, id: NonZeroU32, start: u64, end: u64, fields: &[Field]); +} diff --git a/src/profiler/macros.rs b/src/profiler/macros.rs new file mode 100644 index 0000000..0bb1882 --- /dev/null +++ b/src/profiler/macros.rs @@ -0,0 +1,41 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[macro_export] +macro_rules! profiler_section_start { + ($name: ident $(: $parent: ident)?, $level: expr, $({$($field: tt)*})*) => { + static $name: $crate::profiler::section::Section = $crate::profiler::section::Section::new(stringify!($name), bp3d_logger::location!(), $level) + $(.set_parent(&$parent))?; + let _section = $name.enter($crate::field::FieldSet::new([$($crate::field!($($field)*),)*])); + }; + ($name: ident $(: $parent: ident)?, $level: expr) => { + static $name: $crate::profiler::section::Section = $crate::profiler::section::Section::new(stringify!($name), bp3d_logger::location!(), $level) + $(.set_parent(&$parent))?; + let _section = $name.enter($crate::field::FieldSet::new([])); + }; +} diff --git a/src/profiler/mod.rs b/src/profiler/mod.rs new file mode 100644 index 0000000..9261bc1 --- /dev/null +++ b/src/profiler/mod.rs @@ -0,0 +1,33 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod interface; +mod macros; +pub mod section; + +pub use interface::*; diff --git a/src/profiler/section.rs b/src/profiler/section.rs new file mode 100644 index 0000000..8e61dd2 --- /dev/null +++ b/src/profiler/section.rs @@ -0,0 +1,158 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::field::FieldSet; +use bp3d_logger::Location; +use std::num::NonZeroU32; +use std::sync::OnceLock; +use std::time::Instant; + +#[repr(u8)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Level { + /// A section located in a critically hot path. + Critical = 0, + + /// A periodic section. + Periodic = 1, + + // An event based section. + Event = 2, +} + +thread_local! { + static CUR_TIME: Instant = Instant::now(); +} + +pub struct Entered<'a, const N: usize> { + id: NonZeroU32, + start: u64, + fields: FieldSet<'a, N>, +} + +impl<'a, const N: usize> Drop for Entered<'a, N> { + fn drop(&mut self) { + let end = CUR_TIME.with(|v| v.elapsed().as_nanos() as _); + crate::engine::get().section_record(self.id, self.start, end, self.fields.as_ref()); + } +} + +pub struct Section { + name: &'static str, + location: Location, + level: Level, + parent: Option<&'static Section>, + id: OnceLock, +} + +impl Section { + pub const fn new(name: &'static str, location: Location, level: Level) -> Self { + Self { + name, + location, + level, + parent: None, + id: OnceLock::new(), + } + } + + pub const fn set_parent(mut self, parent: &'static Section) -> Self { + self.parent = Some(parent); + self + } + + pub fn name(&self) -> &'static str { + self.name + } + + pub fn location(&self) -> &Location { + &self.location + } + + pub fn level(&self) -> Level { + self.level + } + + pub fn parent(&self) -> Option<&'static Section> { + self.parent + } + + pub fn get_id(&'static self) -> &NonZeroU32 { + self.id + .get_or_init(|| crate::engine::get().section_register(self)) + } + + pub fn enter<'a, const N: usize>(&'static self, fields: FieldSet<'a, N>) -> Entered<'a, N> { + let id = self.get_id(); + Entered { + id: *id, + start: CUR_TIME.with(|v| v.elapsed().as_nanos() as _), + fields, + } + } +} + +#[cfg(test)] +mod tests { + use crate::field::FieldSet; + use crate::profiler::section::{Level, Section}; + use crate::{fields, profiler_section_start}; + use bp3d_logger::location; + + #[test] + fn basic() { + static _SECTION: Section = Section::new("api_test", location!(), Level::Event); + } + + #[test] + fn api_test() { + static SECTION: Section = Section::new("api_test", location!(), Level::Event); + static _SECTION2: Section = + Section::new("api_test2", location!(), Level::Event).set_parent(&SECTION); + SECTION.enter(FieldSet::new(fields!())); + SECTION.enter(FieldSet::new(fields!({ test = 42 }))); + SECTION.enter(FieldSet::new(fields!({ test = "test 123" }))); + SECTION.enter(FieldSet::new(fields!({ test = 42.42 }))); + SECTION.enter(FieldSet::new(fields!({test=?Level::Event}))); + SECTION.enter(FieldSet::new(fields!({test=?Level::Event} {test2=42}))); + let value = 32; + let str = "this is a test"; + let lvl = Level::Event; + SECTION.enter(FieldSet::new(fields!({value} {str} {?lvl} {test = value}))); + } + + #[test] + fn api_test2() { + let value = 32; + let str = "this is a test"; + let lvl = Level::Event; + profiler_section_start!(API_TEST, Level::Event); + profiler_section_start!(API2_TEST: API_TEST, Level::Event); + profiler_section_start!(API3_TEST_WITH_PARAMS: API2_TEST, Level::Event, {value} {str} {?lvl} {test=value}); + } +} diff --git a/src/trace/future.rs b/src/trace/future.rs new file mode 100644 index 0000000..17b9b79 --- /dev/null +++ b/src/trace/future.rs @@ -0,0 +1,66 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::trace::span::{Entered, Span}; +use crate::trace::Trace; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +pub struct TracedFuture { + future: F, + span: Option, +} + +impl Future for TracedFuture { + type Output = F::Output; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + unsafe { + let pin = Pin::new_unchecked(&mut self.future); + let value = pin.poll(cx); + if value.is_ready() { + drop(self.span.take()); + } + value + } + } +} + +impl Unpin for TracedFuture {} + +impl Trace for F { + type Output = TracedFuture; + + fn trace(self, span: Span) -> Self::Output { + TracedFuture { + future: self, + span: Some(span.enter()), + } + } +} diff --git a/src/trace/interface.rs b/src/trace/interface.rs new file mode 100644 index 0000000..5891816 --- /dev/null +++ b/src/trace/interface.rs @@ -0,0 +1,44 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::field::Field; +use crate::trace::span::{Callsite, Span}; +use std::num::NonZeroU32; + +pub trait Tracer { + fn register_callsite(&self, callsite: &'static Callsite) -> NonZeroU32; + fn span_create(&self, callsite: NonZeroU32, fields: &[Field]) -> NonZeroU32; + fn span_enter(&self, id: NonZeroU32); + fn span_record(&self, id: NonZeroU32, fields: &[Field]); + fn span_exit(&self, id: NonZeroU32); +} + +pub trait Trace { + type Output; + fn trace(self, span: Span) -> Self::Output; +} diff --git a/src/trace/macros.rs b/src/trace/macros.rs new file mode 100644 index 0000000..8ca2c61 --- /dev/null +++ b/src/trace/macros.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[macro_export] +macro_rules! span { + ($name: ident) => { + static $name: $crate::trace::span::Callsite = + $crate::trace::span::Callsite::new(stringify!($name), bp3d_logger::location!()); + }; +} diff --git a/src/trace/mod.rs b/src/trace/mod.rs new file mode 100644 index 0000000..0649c51 --- /dev/null +++ b/src/trace/mod.rs @@ -0,0 +1,34 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod future; +mod interface; +mod macros; +pub mod span; + +pub use interface::*; diff --git a/src/trace/span.rs b/src/trace/span.rs new file mode 100644 index 0000000..f5dd189 --- /dev/null +++ b/src/trace/span.rs @@ -0,0 +1,118 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::field::Field; +use bp3d_logger::Location; +use std::num::NonZeroU32; +use std::sync::OnceLock; + +pub struct Callsite { + name: &'static str, + location: Location, + id: OnceLock, +} + +impl Callsite { + pub const fn new(name: &'static str, location: Location) -> Self { + Self { + name, + location, + id: OnceLock::new(), + } + } + + pub fn location(&self) -> &Location { + &self.location + } + + pub fn name(&self) -> &'static str { + self.name + } + + pub fn get_id(&'static self) -> &NonZeroU32 { + self.id + .get_or_init(|| crate::engine::get().register_callsite(self)) + } +} + +pub struct Entered { + id: NonZeroU32, +} + +impl Drop for Entered { + fn drop(&mut self) { + crate::engine::get().span_exit(self.id); + } +} + +pub struct Span { + id: NonZeroU32, +} + +impl Span { + pub fn with_fields(callsite: &'static Callsite, fields: &[Field]) -> Self { + let id = crate::engine::get().span_create(*callsite.get_id(), fields); + Self { id } + } + + pub fn new(callsite: &'static Callsite) -> Self { + let id = crate::engine::get().span_create(*callsite.get_id(), &[]); + Self { id } + } + + pub fn record(&self, fields: &[Field]) { + crate::engine::get().span_record(self.id, fields); + } + + pub fn enter(self) -> Entered { + Entered { id: self.id } + } +} + +#[cfg(test)] +mod tests { + use crate::profiler::section::Level; + use crate::trace::span::Span; + use crate::{fields, span}; + + #[test] + fn api_test() { + let value = 32; + let str = "this is a test"; + let lvl = Level::Event; + span!(API_TEST); + span!(API_TEST2); + let _span = Span::new(&API_TEST); + let span = Span::with_fields( + &API_TEST2, + fields!({value} {str} {?lvl} {test=value}).as_ref(), + ); + span.record(fields!({ test2 = str }).as_ref()); + let _entered = span.enter(); + } +} diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 9a85f75..0000000 --- a/src/util.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2024, BlockProject 3D -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of BlockProject 3D nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -//! Logging utilities. - -use bp3d_util::format::IoToFmt; -use std::fmt::Write; -use time::macros::format_description; -use time::OffsetDateTime; - -/// Extracts the target name and the module path (without the target name) from a full module path string. -/// -/// # Arguments -/// -/// * `base_string`: a full module path string (ex: bp3d_logger::util::extract_target_module). -/// -/// returns: (&str, &str) -pub fn extract_target_module(base_string: &str) -> (&str, &str) { - let target = base_string - .find("::") - .map(|v| &base_string[..v]) - .unwrap_or(base_string); - let module = base_string.find("::").map(|v| &base_string[(v + 2)..]); - (target, module.unwrap_or("main")) -} - -/// Write time information into the given [Write](Write). -/// -/// # Arguments -/// -/// * `msg`: the [Write](Write) to write time information to. -/// * `time`: the time to write. -/// -/// returns: () -pub fn write_time(msg: &mut impl Write, time: OffsetDateTime) { - let _ = msg.write_str("("); - let format = format_description!("[weekday repr:short] [month repr:short] [day] [hour repr:12]:[minute]:[second] [period case:upper]"); - let mut wrapper = IoToFmt::new(msg); - let _ = time.format_into(&mut wrapper, format); - let msg = wrapper.into_inner(); - let _ = msg.write_str(")"); -} - -/// Generate a [Location](crate::Location) structure. -#[macro_export] -macro_rules! location { - () => { - $crate::Location::new(module_path!(), file!(), line!()) - }; -} - -#[cfg(test)] -mod tests { - use crate::util::write_time; - use crate::{Level, Location, LogMsg}; - use bp3d_os::time::LocalOffsetDateTime; - use time::OffsetDateTime; - - #[test] - fn fhsdiub() { - let time = OffsetDateTime::now_local().unwrap_or_else(OffsetDateTime::now_utc); - let mut msg = LogMsg::new(Location::new("test", "test.c", 1), Level::Info); - write_time(&mut msg, time); - assert!(msg.msg().len() > 0); - } -} From b85d06eaa9b4f30c314ed032a5cb4331a2d6d6d4 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 20 Jul 2024 15:33:56 +0200 Subject: [PATCH 40/64] Fixed broken path to repository --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a155616..f4eaf2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." license = "BSD-3-Clause" -repository = "https://gitlab.com/bp3d/tracing/tracing" -readme = "../README.MD" +repository = "https://github.com/BlockProject3D/debug.core" +readme = "README.MD" keywords = ["bp3d", "tracing"] categories = ["development-tools", "development-tools::debugging", "development-tools::profiling"] From 8c688bb65e2609df0f3917f1eb61cde90fe38ec2 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 20 Jul 2024 16:01:37 +0200 Subject: [PATCH 41/64] Removed dependency over bp3d-logger --- Cargo.toml | 6 --- src/lib.rs | 1 + src/logger/interface.rs | 5 +- src/logger/level.rs | 79 +++++++++++++++++++++++++++++++ src/logger/macros.rs | 14 +++--- src/logger/mod.rs | 2 + src/profiler/macros.rs | 4 +- src/profiler/section.rs | 5 +- src/trace/macros.rs | 2 +- src/trace/span.rs | 2 +- src/util.rs | 100 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 198 insertions(+), 22 deletions(-) create mode 100644 src/logger/level.rs create mode 100644 src/util.rs diff --git a/Cargo.toml b/Cargo.toml index f4eaf2d..11d1225 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,3 @@ keywords = ["bp3d", "tracing"] categories = ["development-tools", "development-tools::debugging", "development-tools::profiling"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bp3d-logger = "2.0.0-rc.2.1.0" - -[build-dependencies] -semver = "1.0.7" diff --git a/src/lib.rs b/src/lib.rs index 49d6447..37ccd5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,3 +31,4 @@ pub mod field; pub mod logger; pub mod profiler; pub mod trace; +pub mod util; diff --git a/src/logger/interface.rs b/src/logger/interface.rs index fc0c917..e4426ac 100644 --- a/src/logger/interface.rs +++ b/src/logger/interface.rs @@ -27,7 +27,8 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::field::Field; -use bp3d_logger::{Level, Location}; +use crate::logger::Level; +use crate::util::Location; use std::fmt::Arguments; pub struct Callsite { @@ -56,7 +57,7 @@ pub trait Logger { #[cfg(test)] mod tests { use crate::{log, trace}; - use bp3d_logger::Level; + use crate::logger::Level; #[test] fn api_test() { diff --git a/src/logger/level.rs b/src/logger/level.rs new file mode 100644 index 0000000..dcd1105 --- /dev/null +++ b/src/logger/level.rs @@ -0,0 +1,79 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::fmt::{Display, Formatter}; + +/// An enum representing the available verbosity levels for a message. +#[repr(u8)] +#[derive(Clone, PartialEq, Copy, Ord, PartialOrd, Eq, Debug, Hash)] +pub enum Level { + /// The "trace" level. + /// + /// Designates very low priority, often extremely verbose, information. + Trace = 1, + + /// The "debug" level. + /// + /// Designates lower priority information. + Debug = 2, + + /// The "info" level. + /// + /// Designates useful information. + Info = 3, + + /// The "warn" level. + /// + /// Designates hazardous situations. + Warn = 4, + + /// The "error" level. + /// + /// Designates very serious errors. + // This way these line up with the discriminants for LevelFilter below + // This works because Rust treats field-less enums the same way as C does: + // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-field-less-enumerations + Error = 5, +} + +static LOG_LEVEL_NAMES: [&str; 6] = ["OFF", "TRACE", "DEBUG", "INFO", "WARNING", "ERROR"]; + +impl Level { + /// Returns the string representation of the `Level`. + /// + /// This returns the same string as the `fmt::Display` implementation. + pub fn as_str(&self) -> &'static str { + LOG_LEVEL_NAMES[*self as usize] + } +} + +impl Display for Level { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} diff --git a/src/logger/macros.rs b/src/logger/macros.rs index c0980dc..11c9543 100644 --- a/src/logger/macros.rs +++ b/src/logger/macros.rs @@ -30,13 +30,13 @@ macro_rules! log { ($level: expr, $({$($field: tt)*})*, $msg: literal $(,$($args: tt)*)?) => { { - static _CALLSITE: $crate::logger::Callsite = $crate::logger::Callsite::new(bp3d_logger::location!(), $level); + static _CALLSITE: $crate::logger::Callsite = $crate::logger::Callsite::new($crate::location!(), $level); $crate::engine::get().log(&_CALLSITE, format_args!($msg $(, $($args),*)?), &[$($crate::field!($($field)*),)*]); } }; ($level: expr, $msg: literal $(,$($args: tt)*)?) => { { - static _CALLSITE: $crate::logger::Callsite = $crate::logger::Callsite::new(bp3d_logger::location!(), $level); + static _CALLSITE: $crate::logger::Callsite = $crate::logger::Callsite::new($crate::location!(), $level); $crate::engine::get().log(&_CALLSITE, format_args!($msg $(, $($args),*)?), &[]); } }; @@ -46,34 +46,34 @@ macro_rules! log { macro_rules! trace { ($($args: tt)*) => { #[cfg(debug_assertions)] - $crate::log!(bp3d_logger::Level::Trace, $($args)*); + $crate::log!($crate::logger::Level::Trace, $($args)*); }; } #[macro_export] macro_rules! debug { ($($args: tt)*) => { - $crate::log!(bp3d_logger::Level::Debug, $($args)*); + $crate::log!($crate::logger::Level::Debug, $($args)*); }; } #[macro_export] macro_rules! info { ($($args: tt)*) => { - $crate::log!(bp3d_logger::Level::Info, $($args)*); + $crate::log!($crate::logger::Level::Info, $($args)*); }; } #[macro_export] macro_rules! warning { ($($args: tt)*) => { - $crate::log!(bp3d_logger::Level::Warn, $($args)*); + $crate::log!($crate::logger::Level::Warn, $($args)*); }; } #[macro_export] macro_rules! error { ($($args: tt)*) => { - $crate::log!(bp3d_logger::Level::Error, $($args)*); + $crate::log!($crate::logger::Level::Error, $($args)*); }; } diff --git a/src/logger/mod.rs b/src/logger/mod.rs index b6ce8b2..7b3c642 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -27,6 +27,8 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mod interface; +mod level; pub mod macros; pub use interface::*; +pub use level::Level; diff --git a/src/profiler/macros.rs b/src/profiler/macros.rs index 0bb1882..e6fbb35 100644 --- a/src/profiler/macros.rs +++ b/src/profiler/macros.rs @@ -29,12 +29,12 @@ #[macro_export] macro_rules! profiler_section_start { ($name: ident $(: $parent: ident)?, $level: expr, $({$($field: tt)*})*) => { - static $name: $crate::profiler::section::Section = $crate::profiler::section::Section::new(stringify!($name), bp3d_logger::location!(), $level) + static $name: $crate::profiler::section::Section = $crate::profiler::section::Section::new(stringify!($name), $crate::location!(), $level) $(.set_parent(&$parent))?; let _section = $name.enter($crate::field::FieldSet::new([$($crate::field!($($field)*),)*])); }; ($name: ident $(: $parent: ident)?, $level: expr) => { - static $name: $crate::profiler::section::Section = $crate::profiler::section::Section::new(stringify!($name), bp3d_logger::location!(), $level) + static $name: $crate::profiler::section::Section = $crate::profiler::section::Section::new(stringify!($name), $crate::location!(), $level) $(.set_parent(&$parent))?; let _section = $name.enter($crate::field::FieldSet::new([])); }; diff --git a/src/profiler/section.rs b/src/profiler/section.rs index 8e61dd2..adb1241 100644 --- a/src/profiler/section.rs +++ b/src/profiler/section.rs @@ -27,7 +27,7 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::field::FieldSet; -use bp3d_logger::Location; +use crate::util::Location; use std::num::NonZeroU32; use std::sync::OnceLock; use std::time::Instant; @@ -121,8 +121,7 @@ impl Section { mod tests { use crate::field::FieldSet; use crate::profiler::section::{Level, Section}; - use crate::{fields, profiler_section_start}; - use bp3d_logger::location; + use crate::{fields, location, profiler_section_start}; #[test] fn basic() { diff --git a/src/trace/macros.rs b/src/trace/macros.rs index 8ca2c61..1c80c55 100644 --- a/src/trace/macros.rs +++ b/src/trace/macros.rs @@ -30,6 +30,6 @@ macro_rules! span { ($name: ident) => { static $name: $crate::trace::span::Callsite = - $crate::trace::span::Callsite::new(stringify!($name), bp3d_logger::location!()); + $crate::trace::span::Callsite::new(stringify!($name), $crate::location!()); }; } diff --git a/src/trace/span.rs b/src/trace/span.rs index f5dd189..661bd26 100644 --- a/src/trace/span.rs +++ b/src/trace/span.rs @@ -27,7 +27,7 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::field::Field; -use bp3d_logger::Location; +use crate::util::Location; use std::num::NonZeroU32; use std::sync::OnceLock; diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..7319bbe --- /dev/null +++ b/src/util.rs @@ -0,0 +1,100 @@ +// Copyright (c) 2024, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// Extracts the target name and the module path (without the target name) from a full module path string. +/// +/// # Arguments +/// +/// * `base_string`: a full module path string (ex: bp3d_logger::util::extract_target_module). +/// +/// returns: (&str, &str) +pub fn extract_target_module(base_string: &str) -> (&str, &str) { + let target = base_string + .find("::") + .map(|v| &base_string[..v]) + .unwrap_or(base_string); + let module = base_string.find("::").map(|v| &base_string[(v + 2)..]); + (target, module.unwrap_or("main")) +} + +/// The context of a log message. +#[derive(Clone, Copy)] +pub struct Location { + module_path: &'static str, + file: &'static str, + line: u32, +} + +impl Location { + /// Creates a new instance of a log message location. + /// + /// This function is const to let the caller store location structures in statics. + /// + /// # Arguments + /// + /// * `module_path`: the module path obtained from the [module_path](module_path) macro. + /// * `file`: the source file obtained from the [file](file) macro. + /// * `line`: the line number in the source file obtained from the [line](line) macro. + /// + /// returns: Metadata + pub const fn new(module_path: &'static str, file: &'static str, line: u32) -> Self { + Self { + module_path, + file, + line, + } + } + + /// The module path which issued this log message. + pub fn module_path(&self) -> &'static str { + self.module_path + } + + /// The source file which issued this log message. + pub fn file(&self) -> &'static str { + self.file + } + + /// The line in the source file which issued this log message. + pub fn line(&self) -> u32 { + self.line + } + + /// Extracts the target name and the module name from the module path. + pub fn get_target_module(&self) -> (&'static str, &'static str) { + extract_target_module(self.module_path) + } +} + +/// Generate a [Location](crate::Location) structure. +#[macro_export] +macro_rules! location { + () => { + $crate::util::Location::new(module_path!(), file!(), line!()) + }; +} From 8227c3fbfa911692c5ced85a33a7cb0b389b5c21 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 20 Jul 2024 16:02:49 +0200 Subject: [PATCH 42/64] Bumped crate version to trigger a new release. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 11d1225..61a0a10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.1.0.0" +version = "1.0.0-rc.2.0.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." From 202c34acb0726248ecf9b399035c482a5d627d48 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 14:05:12 +0000 Subject: [PATCH 43/64] Format Rust code using rustfmt --- src/logger/interface.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logger/interface.rs b/src/logger/interface.rs index e4426ac..7186661 100644 --- a/src/logger/interface.rs +++ b/src/logger/interface.rs @@ -56,8 +56,8 @@ pub trait Logger { #[cfg(test)] mod tests { - use crate::{log, trace}; use crate::logger::Level; + use crate::{log, trace}; #[test] fn api_test() { From fca585cec1608e0b91f70fc52409844ffb0f0090 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sat, 20 Jul 2024 17:06:25 +0200 Subject: [PATCH 44/64] Added missing licence file --- LICENSE.txt | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..83dbf64 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2024, BlockProject 3D + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of BlockProject 3D nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 161a095f2e3b97c370c974c341a574050a0bd96a Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sun, 21 Jul 2024 15:54:00 +0200 Subject: [PATCH 45/64] Added implementations of Display and Debug to FieldValue --- Cargo.toml | 2 +- src/field.rs | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 61a0a10..ca7b63a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.2.0.0" +version = "1.0.0-rc.2.1.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." diff --git a/src/field.rs b/src/field.rs index b45c9aa..054163b 100644 --- a/src/field.rs +++ b/src/field.rs @@ -26,8 +26,9 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::fmt::Debug; +use std::fmt::{Debug, Display, Formatter}; +#[derive(Debug)] pub enum FieldValue<'a> { Int(i64), UInt(u64), @@ -37,6 +38,19 @@ pub enum FieldValue<'a> { Debug(&'a dyn Debug), } +impl<'a> Display for FieldValue<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FieldValue::Int(v) => write!(f, "{}", v), + FieldValue::UInt(v) => write!(f, "{}", v), + FieldValue::Float(v) => write!(f, "{}", v), + FieldValue::Double(v) => write!(f, "{}", v), + FieldValue::String(v) => f.write_str(v), + FieldValue::Debug(v) => write!(f, "{:?}", v) + } + } +} + pub struct Field<'a> { name: &'a str, value: FieldValue<'a>, From fd7183f3665bc6ec8d0bffc1deb5c069f59cf3a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 21 Jul 2024 13:56:54 +0000 Subject: [PATCH 46/64] Format Rust code using rustfmt --- src/field.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field.rs b/src/field.rs index 054163b..ab0a329 100644 --- a/src/field.rs +++ b/src/field.rs @@ -46,7 +46,7 @@ impl<'a> Display for FieldValue<'a> { FieldValue::Float(v) => write!(f, "{}", v), FieldValue::Double(v) => write!(f, "{}", v), FieldValue::String(v) => f.write_str(v), - FieldValue::Debug(v) => write!(f, "{:?}", v) + FieldValue::Debug(v) => write!(f, "{:?}", v), } } } From 52adbd3459c2635a12b56ffb2b3b7ca8534fa0b6 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Thu, 22 Aug 2024 18:29:27 +0200 Subject: [PATCH 47/64] Updated tracer interface to pass the id of the span as a combination of the callsite and the instance number --- Cargo.toml | 2 +- src/engine/mod.rs | 3 ++- src/engine/void.rs | 8 +++---- src/trace/interface.rs | 8 +++---- src/trace/macros.rs | 14 ++++++++++-- src/trace/mod.rs | 2 ++ src/trace/span.rs | 52 ++++++++++++++++++++++++++++++------------ 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ca7b63a..f191887 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.2.1.0" +version = "1.0.0-rc.3.0.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 3bd0e2c..c238639 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -60,6 +60,7 @@ pub fn set(engine: &'static dyn Engine) -> bool { #[cfg(test)] mod tests { use std::num::NonZeroU32; + use crate::trace::span::Id; #[test] fn basic() { @@ -69,7 +70,7 @@ mod tests { #[test] fn after_use() { - crate::engine::get().span_exit(unsafe { NonZeroU32::new_unchecked(1) }); + crate::engine::get().span_exit(Id::new(unsafe { NonZeroU32::new_unchecked(1) }, unsafe { NonZeroU32::new_unchecked(1) })); assert!(!crate::engine::set(&crate::engine::void::VoidDebugger {})); } } diff --git a/src/engine/void.rs b/src/engine/void.rs index 32cbf4d..7db07d6 100644 --- a/src/engine/void.rs +++ b/src/engine/void.rs @@ -28,7 +28,7 @@ use crate::engine::ENGINE_INIT_FLAG; use crate::field::Field; -use crate::trace::span::Callsite; +use crate::trace::span::{Callsite, Id}; use std::fmt::Arguments; use std::num::NonZeroU32; use std::sync::atomic::Ordering; @@ -57,15 +57,15 @@ impl crate::trace::Tracer for VoidDebugger { unsafe { NonZeroU32::new_unchecked(1) } } - fn span_enter(&self, _: NonZeroU32) { + fn span_enter(&self, _: Id) { ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); } - fn span_record(&self, _: NonZeroU32, _: &[Field]) { + fn span_record(&self, _: Id, _: &[Field]) { ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); } - fn span_exit(&self, _: NonZeroU32) { + fn span_exit(&self, _: Id) { ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); } } diff --git a/src/trace/interface.rs b/src/trace/interface.rs index 5891816..d968334 100644 --- a/src/trace/interface.rs +++ b/src/trace/interface.rs @@ -27,15 +27,15 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::field::Field; -use crate::trace::span::{Callsite, Span}; +use crate::trace::span::{Callsite, Id, Span}; use std::num::NonZeroU32; pub trait Tracer { fn register_callsite(&self, callsite: &'static Callsite) -> NonZeroU32; fn span_create(&self, callsite: NonZeroU32, fields: &[Field]) -> NonZeroU32; - fn span_enter(&self, id: NonZeroU32); - fn span_record(&self, id: NonZeroU32, fields: &[Field]); - fn span_exit(&self, id: NonZeroU32); + fn span_enter(&self, id: Id); + fn span_record(&self, id: Id, fields: &[Field]); + fn span_exit(&self, id: Id); } pub trait Trace { diff --git a/src/trace/macros.rs b/src/trace/macros.rs index 1c80c55..fea966a 100644 --- a/src/trace/macros.rs +++ b/src/trace/macros.rs @@ -28,8 +28,18 @@ #[macro_export] macro_rules! span { + ($name: ident, $({$($field: tt)*})*) => { + { + static $name: $crate::trace::span::Callsite = + $crate::trace::span::Callsite::new(stringify!($name), $crate::location!()); + $crate::trace::span::Span::with_fields(&$name, &[$($crate::field!($($field)*),)*]) + } + }; ($name: ident) => { - static $name: $crate::trace::span::Callsite = - $crate::trace::span::Callsite::new(stringify!($name), $crate::location!()); + { + static $name: $crate::trace::span::Callsite = + $crate::trace::span::Callsite::new(stringify!($name), $crate::location!()); + $crate::trace::span::Span::new(&$name) + } }; } diff --git a/src/trace/mod.rs b/src/trace/mod.rs index 0649c51..de2f4fa 100644 --- a/src/trace/mod.rs +++ b/src/trace/mod.rs @@ -26,6 +26,8 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//TODO: implement support for storing both the callsite and instance numbers and return them when calling the underlying tracer interface. + mod future; mod interface; mod macros; diff --git a/src/trace/span.rs b/src/trace/span.rs index 661bd26..36d8db6 100644 --- a/src/trace/span.rs +++ b/src/trace/span.rs @@ -28,9 +28,35 @@ use crate::field::Field; use crate::util::Location; -use std::num::NonZeroU32; +use std::num::{NonZeroU32, NonZeroU64}; use std::sync::OnceLock; +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[repr(transparent)] +pub struct Id(NonZeroU64); + +impl Id { + pub fn new(callsite: NonZeroU32, instance: NonZeroU32) -> Self { + Self(unsafe { NonZeroU64::new_unchecked((callsite.get() as u64) << 32 | instance.get() as u64) }) + } + + pub fn from_raw(id: NonZeroU64) -> Self { + Self(id) + } + + pub fn into_raw(self) -> NonZeroU64 { + self.0 + } + + pub fn get_callsite(&self) -> NonZeroU32 { + unsafe { NonZeroU32::new_unchecked((self.0.get() >> 32) as u32) } + } + + pub fn get_instance(&self) -> NonZeroU32 { + unsafe { NonZeroU32::new_unchecked(self.0.get() as u32) } + } +} + pub struct Callsite { name: &'static str, location: Location, @@ -61,7 +87,7 @@ impl Callsite { } pub struct Entered { - id: NonZeroU32, + id: Id, } impl Drop for Entered { @@ -71,18 +97,20 @@ impl Drop for Entered { } pub struct Span { - id: NonZeroU32, + id: Id, } impl Span { pub fn with_fields(callsite: &'static Callsite, fields: &[Field]) -> Self { - let id = crate::engine::get().span_create(*callsite.get_id(), fields); - Self { id } + let callsite = *callsite.get_id(); + let instance = crate::engine::get().span_create(callsite, fields); + Self { id: Id::new(callsite, instance) } } pub fn new(callsite: &'static Callsite) -> Self { - let id = crate::engine::get().span_create(*callsite.get_id(), &[]); - Self { id } + let callsite = *callsite.get_id(); + let instance = crate::engine::get().span_create(callsite, &[]); + Self { id: Id::new(callsite, instance) } } pub fn record(&self, fields: &[Field]) { @@ -97,7 +125,6 @@ impl Span { #[cfg(test)] mod tests { use crate::profiler::section::Level; - use crate::trace::span::Span; use crate::{fields, span}; #[test] @@ -105,13 +132,8 @@ mod tests { let value = 32; let str = "this is a test"; let lvl = Level::Event; - span!(API_TEST); - span!(API_TEST2); - let _span = Span::new(&API_TEST); - let span = Span::with_fields( - &API_TEST2, - fields!({value} {str} {?lvl} {test=value}).as_ref(), - ); + let _span = span!(API_TEST); + let span = span!(API_TEST2, {value} {str} {?lvl} {test=value}); span.record(fields!({ test2 = str }).as_ref()); let _entered = span.enter(); } From 5a0ab1bb23e44a0c0efe6e16d82e7fea14b73a8e Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Thu, 22 Aug 2024 18:29:51 +0200 Subject: [PATCH 48/64] Removed TODO --- src/trace/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/trace/mod.rs b/src/trace/mod.rs index de2f4fa..0649c51 100644 --- a/src/trace/mod.rs +++ b/src/trace/mod.rs @@ -26,8 +26,6 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//TODO: implement support for storing both the callsite and instance numbers and return them when calling the underlying tracer interface. - mod future; mod interface; mod macros; From db9b7a51a332dbde93e508da16be57ef39b9a45c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:31:41 +0000 Subject: [PATCH 49/64] Format Rust code using rustfmt --- src/engine/mod.rs | 6 ++++-- src/trace/span.rs | 12 +++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/engine/mod.rs b/src/engine/mod.rs index c238639..bf8edd2 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -59,8 +59,8 @@ pub fn set(engine: &'static dyn Engine) -> bool { #[cfg(test)] mod tests { - use std::num::NonZeroU32; use crate::trace::span::Id; + use std::num::NonZeroU32; #[test] fn basic() { @@ -70,7 +70,9 @@ mod tests { #[test] fn after_use() { - crate::engine::get().span_exit(Id::new(unsafe { NonZeroU32::new_unchecked(1) }, unsafe { NonZeroU32::new_unchecked(1) })); + crate::engine::get().span_exit(Id::new(unsafe { NonZeroU32::new_unchecked(1) }, unsafe { + NonZeroU32::new_unchecked(1) + })); assert!(!crate::engine::set(&crate::engine::void::VoidDebugger {})); } } diff --git a/src/trace/span.rs b/src/trace/span.rs index 36d8db6..3992c75 100644 --- a/src/trace/span.rs +++ b/src/trace/span.rs @@ -37,7 +37,9 @@ pub struct Id(NonZeroU64); impl Id { pub fn new(callsite: NonZeroU32, instance: NonZeroU32) -> Self { - Self(unsafe { NonZeroU64::new_unchecked((callsite.get() as u64) << 32 | instance.get() as u64) }) + Self(unsafe { + NonZeroU64::new_unchecked((callsite.get() as u64) << 32 | instance.get() as u64) + }) } pub fn from_raw(id: NonZeroU64) -> Self { @@ -104,13 +106,17 @@ impl Span { pub fn with_fields(callsite: &'static Callsite, fields: &[Field]) -> Self { let callsite = *callsite.get_id(); let instance = crate::engine::get().span_create(callsite, fields); - Self { id: Id::new(callsite, instance) } + Self { + id: Id::new(callsite, instance), + } } pub fn new(callsite: &'static Callsite) -> Self { let callsite = *callsite.get_id(); let instance = crate::engine::get().span_create(callsite, &[]); - Self { id: Id::new(callsite, instance) } + Self { + id: Id::new(callsite, instance), + } } pub fn record(&self, fields: &[Field]) { From 6545fad65359bf3d8c27af58258286ed4dce3060 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Thu, 22 Aug 2024 18:47:25 +0200 Subject: [PATCH 50/64] Added Debug derive to span::Id --- Cargo.toml | 2 +- src/trace/span.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f191887..097bd1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.3.0.0" +version = "1.0.0-rc.3.1.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." diff --git a/src/trace/span.rs b/src/trace/span.rs index 3992c75..56819cc 100644 --- a/src/trace/span.rs +++ b/src/trace/span.rs @@ -31,7 +31,7 @@ use crate::util::Location; use std::num::{NonZeroU32, NonZeroU64}; use std::sync::OnceLock; -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[repr(transparent)] pub struct Id(NonZeroU64); From 03c1b86de3623039180d9624775c75b9893d1523 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Thu, 22 Aug 2024 20:47:16 +0200 Subject: [PATCH 51/64] Added Hash derive to span::Id --- Cargo.toml | 2 +- src/trace/span.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 097bd1c..7ae68a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.3.1.0" +version = "1.0.0-rc.3.2.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." diff --git a/src/trace/span.rs b/src/trace/span.rs index 56819cc..3b1feb5 100644 --- a/src/trace/span.rs +++ b/src/trace/span.rs @@ -31,7 +31,7 @@ use crate::util::Location; use std::num::{NonZeroU32, NonZeroU64}; use std::sync::OnceLock; -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] pub struct Id(NonZeroU64); From 27db84862940c02b6334f062b0e01102884a9d85 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 26 Aug 2024 21:56:56 +0200 Subject: [PATCH 52/64] Added Debug derive to Location --- Cargo.toml | 2 +- src/util.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7ae68a7..56ed0b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.3.2.0" +version = "1.0.0-rc.3.3.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." diff --git a/src/util.rs b/src/util.rs index 7319bbe..269ac62 100644 --- a/src/util.rs +++ b/src/util.rs @@ -43,7 +43,7 @@ pub fn extract_target_module(base_string: &str) -> (&str, &str) { } /// The context of a log message. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct Location { module_path: &'static str, file: &'static str, From ee5cd853226f23fc0422f85002741211cb17935c Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Wed, 28 Aug 2024 23:05:34 +0200 Subject: [PATCH 53/64] Fixed broken log macro --- Cargo.toml | 4 +++- src/logger/interface.rs | 3 +++ src/logger/macros.rs | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56ed0b3..f17ebc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.3.3.0" +version = "1.0.0-rc.4.0.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." @@ -11,3 +11,5 @@ keywords = ["bp3d", "tracing"] categories = ["development-tools", "development-tools::debugging", "development-tools::profiling"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +#TODO: Implement features to disable profiler and other different components \ No newline at end of file diff --git a/src/logger/interface.rs b/src/logger/interface.rs index 7186661..069a8af 100644 --- a/src/logger/interface.rs +++ b/src/logger/interface.rs @@ -61,9 +61,12 @@ mod tests { #[test] fn api_test() { + let tuple = (41, 42); let i = 42; log!(Level::Info, { i }, "test: {i}: {}", i); log!(Level::Error, "test: {}", i); trace!({i} {?i} {id=i}, "test: {}", i); + trace!("test: {}, {}", i, i); + trace!("test41_42: {}, {}", tuple.0, tuple.1); } } diff --git a/src/logger/macros.rs b/src/logger/macros.rs index 11c9543..3cc9df8 100644 --- a/src/logger/macros.rs +++ b/src/logger/macros.rs @@ -28,13 +28,13 @@ #[macro_export] macro_rules! log { - ($level: expr, $({$($field: tt)*})*, $msg: literal $(,$($args: tt)*)?) => { + ($level: expr, $({$($field: tt)*})*, $msg: literal $(,$($args: expr),*)?) => { { static _CALLSITE: $crate::logger::Callsite = $crate::logger::Callsite::new($crate::location!(), $level); $crate::engine::get().log(&_CALLSITE, format_args!($msg $(, $($args),*)?), &[$($crate::field!($($field)*),)*]); } }; - ($level: expr, $msg: literal $(,$($args: tt)*)?) => { + ($level: expr, $msg: literal $(,$($args: expr),*)?) => { { static _CALLSITE: $crate::logger::Callsite = $crate::logger::Callsite::new($crate::location!(), $level); $crate::engine::get().log(&_CALLSITE, format_args!($msg $(, $($args),*)?), &[]); From 3725ec06a92f83537268e59859bada160f792e4b Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Wed, 28 Aug 2024 23:11:02 +0200 Subject: [PATCH 54/64] Added support for logging variables --- Cargo.toml | 2 +- src/engine/{void.rs => default.rs} | 19 ++++++++++++------- src/engine/mod.rs | 10 +++++----- 3 files changed, 18 insertions(+), 13 deletions(-) rename src/engine/{void.rs => default.rs} (87%) diff --git a/Cargo.toml b/Cargo.toml index f17ebc4..38373ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.4.0.0" +version = "1.0.0-rc.5.0.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." diff --git a/src/engine/void.rs b/src/engine/default.rs similarity index 87% rename from src/engine/void.rs rename to src/engine/default.rs index 7db07d6..12aee28 100644 --- a/src/engine/void.rs +++ b/src/engine/default.rs @@ -33,9 +33,9 @@ use std::fmt::Arguments; use std::num::NonZeroU32; use std::sync::atomic::Ordering; -pub struct VoidDebugger {} +pub struct DefaultDebugger {} -impl crate::profiler::Profiler for VoidDebugger { +impl crate::profiler::Profiler for DefaultDebugger { fn section_register(&self, _: &'static crate::profiler::section::Section) -> NonZeroU32 { ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); unsafe { NonZeroU32::new_unchecked(1) } @@ -46,7 +46,7 @@ impl crate::profiler::Profiler for VoidDebugger { } } -impl crate::trace::Tracer for VoidDebugger { +impl crate::trace::Tracer for DefaultDebugger { fn register_callsite(&self, _: &'static Callsite) -> NonZeroU32 { ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); unsafe { NonZeroU32::new_unchecked(1) } @@ -70,13 +70,18 @@ impl crate::trace::Tracer for VoidDebugger { } } -impl crate::logger::Logger for VoidDebugger { - fn log(&self, callsite: &'static crate::logger::Callsite, args: Arguments, _: &[Field]) { +impl crate::logger::Logger for DefaultDebugger { + fn log(&self, callsite: &'static crate::logger::Callsite, args: Arguments, fields: &[Field]) { + let mut s = String::new(); + for field in fields { + s += &format!(", {}={}", field.name(), field.value()); + } println!( - "[{}] {}: {}", + "[{}] {}: {}{}", callsite.level(), callsite.location().module_path(), - args + args, + s ); ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); } diff --git a/src/engine/mod.rs b/src/engine/mod.rs index bf8edd2..39a2437 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -28,7 +28,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; -mod void; +mod default; pub trait Engine: crate::logger::Logger + crate::profiler::Profiler + crate::trace::Tracer + Sync @@ -41,7 +41,7 @@ impl &'static dyn Engine { unsafe { ENGINE } @@ -64,8 +64,8 @@ mod tests { #[test] fn basic() { - crate::engine::set(&crate::engine::void::VoidDebugger {}); - assert!(!crate::engine::set(&crate::engine::void::VoidDebugger {})); + crate::engine::set(&crate::engine::default::DefaultDebugger {}); + assert!(!crate::engine::set(&crate::engine::default::DefaultDebugger {})); } #[test] @@ -73,6 +73,6 @@ mod tests { crate::engine::get().span_exit(Id::new(unsafe { NonZeroU32::new_unchecked(1) }, unsafe { NonZeroU32::new_unchecked(1) })); - assert!(!crate::engine::set(&crate::engine::void::VoidDebugger {})); + assert!(!crate::engine::set(&crate::engine::default::DefaultDebugger {})); } } From 9a03f2ea4cb4dd4322e33fc030ebf5752f7e1d38 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 21:14:08 +0000 Subject: [PATCH 55/64] Format Rust code using rustfmt --- src/engine/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 39a2437..c629ff2 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -65,7 +65,9 @@ mod tests { #[test] fn basic() { crate::engine::set(&crate::engine::default::DefaultDebugger {}); - assert!(!crate::engine::set(&crate::engine::default::DefaultDebugger {})); + assert!(!crate::engine::set( + &crate::engine::default::DefaultDebugger {} + )); } #[test] @@ -73,6 +75,8 @@ mod tests { crate::engine::get().span_exit(Id::new(unsafe { NonZeroU32::new_unchecked(1) }, unsafe { NonZeroU32::new_unchecked(1) })); - assert!(!crate::engine::set(&crate::engine::default::DefaultDebugger {})); + assert!(!crate::engine::set( + &crate::engine::default::DefaultDebugger {} + )); } } From aa925b086b32e0dbf04f43abc873dae041347d8d Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Fri, 6 Sep 2024 23:00:08 +0200 Subject: [PATCH 56/64] Improved tracer API by allowing re-entering spans and separating span enter from destruction --- Cargo.toml | 2 +- src/trace/interface.rs | 1 + src/trace/span.rs | 8 +++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 38373ee..2db6087 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.5.0.0" +version = "1.0.0-rc.6.0.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." diff --git a/src/trace/interface.rs b/src/trace/interface.rs index d968334..51adc36 100644 --- a/src/trace/interface.rs +++ b/src/trace/interface.rs @@ -36,6 +36,7 @@ pub trait Tracer { fn span_enter(&self, id: Id); fn span_record(&self, id: Id, fields: &[Field]); fn span_exit(&self, id: Id); + fn span_destroy(&self, id: Id); } pub trait Trace { diff --git a/src/trace/span.rs b/src/trace/span.rs index 3b1feb5..d2dcf8b 100644 --- a/src/trace/span.rs +++ b/src/trace/span.rs @@ -123,11 +123,17 @@ impl Span { crate::engine::get().span_record(self.id, fields); } - pub fn enter(self) -> Entered { + pub fn enter(&self) -> Entered { Entered { id: self.id } } } +impl Drop for Span { + fn drop(&mut self) { + crate::engine::get().span_destroy(self.id); + } +} + #[cfg(test)] mod tests { use crate::profiler::section::Level; From c753c1704279ed637cf62c1b245ee41ac9cbd276 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Fri, 6 Sep 2024 23:22:38 +0200 Subject: [PATCH 57/64] Added missing span_destroy function implementation --- src/engine/default.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/default.rs b/src/engine/default.rs index 12aee28..9511325 100644 --- a/src/engine/default.rs +++ b/src/engine/default.rs @@ -68,6 +68,10 @@ impl crate::trace::Tracer for DefaultDebugger { fn span_exit(&self, _: Id) { ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); } + + fn span_destroy(&self, _: Id) { + ENGINE_INIT_FLAG.store(true, Ordering::Relaxed); + } } impl crate::logger::Logger for DefaultDebugger { From aa6fca4afced7e5263708ec18a36611604e442bd Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 4 Nov 2024 21:32:22 +0100 Subject: [PATCH 58/64] Added boolean support to FieldValue --- src/field.rs | 5 ++++- src/logger/interface.rs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/field.rs b/src/field.rs index ab0a329..1089b0e 100644 --- a/src/field.rs +++ b/src/field.rs @@ -36,6 +36,7 @@ pub enum FieldValue<'a> { Double(f64), String(&'a str), Debug(&'a dyn Debug), + Boolean(bool) } impl<'a> Display for FieldValue<'a> { @@ -47,6 +48,7 @@ impl<'a> Display for FieldValue<'a> { FieldValue::Double(v) => write!(f, "{}", v), FieldValue::String(v) => f.write_str(v), FieldValue::Debug(v) => write!(f, "{:?}", v), + FieldValue::Boolean(v) => write!(f, "{:?}", v) } } } @@ -103,7 +105,8 @@ impl_into_field_value! { i32 => Int, i64 => Int, f32 => Float, - f64 => Double + f64 => Double, + bool => Boolean } impl<'a> From<&'a str> for FieldValue<'a> { diff --git a/src/logger/interface.rs b/src/logger/interface.rs index 069a8af..4e84ae6 100644 --- a/src/logger/interface.rs +++ b/src/logger/interface.rs @@ -63,10 +63,12 @@ mod tests { fn api_test() { let tuple = (41, 42); let i = 42; + let b = true; log!(Level::Info, { i }, "test: {i}: {}", i); log!(Level::Error, "test: {}", i); trace!({i} {?i} {id=i}, "test: {}", i); trace!("test: {}, {}", i, i); trace!("test41_42: {}, {}", tuple.0, tuple.1); + trace!({b}, "a boolean"); } } From d9da38c7a991d2eab45ed7604a8970c0f3b5dfba Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Mon, 4 Nov 2024 21:34:28 +0100 Subject: [PATCH 59/64] Bumped cargo pre-release version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2db6087..7637c7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.6.0.0" +version = "1.0.0-rc.6.1.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." From 1ebae84c3516651d14ef5b67eda712582e777978 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:38:31 +0000 Subject: [PATCH 60/64] Format Rust code using rustfmt --- src/field.rs | 4 ++-- src/logger/interface.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/field.rs b/src/field.rs index 1089b0e..1a17dd3 100644 --- a/src/field.rs +++ b/src/field.rs @@ -36,7 +36,7 @@ pub enum FieldValue<'a> { Double(f64), String(&'a str), Debug(&'a dyn Debug), - Boolean(bool) + Boolean(bool), } impl<'a> Display for FieldValue<'a> { @@ -48,7 +48,7 @@ impl<'a> Display for FieldValue<'a> { FieldValue::Double(v) => write!(f, "{}", v), FieldValue::String(v) => f.write_str(v), FieldValue::Debug(v) => write!(f, "{:?}", v), - FieldValue::Boolean(v) => write!(f, "{:?}", v) + FieldValue::Boolean(v) => write!(f, "{:?}", v), } } } diff --git a/src/logger/interface.rs b/src/logger/interface.rs index 4e84ae6..1c57dd8 100644 --- a/src/logger/interface.rs +++ b/src/logger/interface.rs @@ -69,6 +69,6 @@ mod tests { trace!({i} {?i} {id=i}, "test: {}", i); trace!("test: {}, {}", i, i); trace!("test41_42: {}, {}", tuple.0, tuple.1); - trace!({b}, "a boolean"); + trace!({ b }, "a boolean"); } } From 5843027b16f6c4316e3378c59f30e38e2d37d3eb Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sun, 4 May 2025 11:05:20 +0200 Subject: [PATCH 61/64] Added support for usize/isize to FieldValue --- src/field.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/field.rs b/src/field.rs index 1089b0e..29117e7 100644 --- a/src/field.rs +++ b/src/field.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2024, BlockProject 3D +// Copyright (c) 2025, BlockProject 3D // // All rights reserved. // @@ -100,10 +100,12 @@ impl_into_field_value! { u16 => UInt, u32 => UInt, u64 => UInt, + usize => UInt, i8 => Int, i16 => Int, i32 => Int, i64 => Int, + isize => Int, f32 => Float, f64 => Double, bool => Boolean From bb447f4dfea86c8ef055ea7fa4ca8ce8f568033f Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Sun, 4 May 2025 11:05:43 +0200 Subject: [PATCH 62/64] Updated crate version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7637c7d..9ae2a07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.6.1.0" +version = "1.0.0-rc.6.2.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP." From 0fa3bc5fa5c7b3fd8610459b0e510b4270128b9a Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Tue, 9 Sep 2025 21:15:04 +0200 Subject: [PATCH 63/64] Fixed most clippy warnings --- Cargo.toml | 2 -- src/field.rs | 4 ++-- src/profiler/section.rs | 4 ++-- src/trace/span.rs | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ae2a07..83977c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,3 @@ keywords = ["bp3d", "tracing"] categories = ["development-tools", "development-tools::debugging", "development-tools::profiling"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -#TODO: Implement features to disable profiler and other different components \ No newline at end of file diff --git a/src/field.rs b/src/field.rs index 8c452d4..b99fdb8 100644 --- a/src/field.rs +++ b/src/field.rs @@ -39,8 +39,8 @@ pub enum FieldValue<'a> { Boolean(bool), } -impl<'a> Display for FieldValue<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +impl Display for FieldValue<'_> { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { match self { FieldValue::Int(v) => write!(f, "{}", v), FieldValue::UInt(v) => write!(f, "{}", v), diff --git a/src/profiler/section.rs b/src/profiler/section.rs index adb1241..a10baad 100644 --- a/src/profiler/section.rs +++ b/src/profiler/section.rs @@ -55,7 +55,7 @@ pub struct Entered<'a, const N: usize> { fields: FieldSet<'a, N>, } -impl<'a, const N: usize> Drop for Entered<'a, N> { +impl Drop for Entered<'_, N> { fn drop(&mut self) { let end = CUR_TIME.with(|v| v.elapsed().as_nanos() as _); crate::engine::get().section_record(self.id, self.start, end, self.fields.as_ref()); @@ -102,7 +102,7 @@ impl Section { self.parent } - pub fn get_id(&'static self) -> &NonZeroU32 { + pub fn get_id(&'static self) -> &'static NonZeroU32 { self.id .get_or_init(|| crate::engine::get().section_register(self)) } diff --git a/src/trace/span.rs b/src/trace/span.rs index d2dcf8b..fa02a0a 100644 --- a/src/trace/span.rs +++ b/src/trace/span.rs @@ -82,7 +82,7 @@ impl Callsite { self.name } - pub fn get_id(&'static self) -> &NonZeroU32 { + pub fn get_id(&'static self) -> &'static NonZeroU32 { self.id .get_or_init(|| crate::engine::get().register_callsite(self)) } From 5c6fa45cb31feebfd7f5b4d596ce21b1cc03c283 Mon Sep 17 00:00:00 2001 From: Yuri Edward Date: Tue, 9 Sep 2025 21:39:48 +0200 Subject: [PATCH 64/64] Updated version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 83977c4..4126c1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bp3d-debug" -version = "1.0.0-rc.6.2.0" +version = "1.0.0" authors = ["Yuri Edward "] edition = "2021" description = "Tracing subscriber implementations for use with BP3D software. Supports traditional logging through bp3d-logger and supports remote profiling through TCP."