From 26d9e6d0ef73b74c2733bd0976e0ffa606cd5ded Mon Sep 17 00:00:00 2001 From: Techcable Date: Wed, 27 Aug 2025 00:32:15 -0700 Subject: [PATCH] Attempt to track the caller Bizarre interaction between const-eval, macro span, #[track_caller], and Location::caller. Tried many different combinations of inline const, const-fn and const declaration. --- Cargo.toml | 4 + build.rs | 7 ++ src/lib.rs | 114 +++++++++++++++++++++------- tests/track_caller/mod.rs | 38 ++++++++++ tests/track_caller/separate_file.rs | 14 ++++ 5 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 tests/track_caller/mod.rs create mode 100644 tests/track_caller/separate_file.rs diff --git a/Cargo.toml b/Cargo.toml index 654521ba..7df1a498 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,6 +120,10 @@ serde_derive = "1" name = "singlethread" required-features = ["nothreads"] +[[test]] +name = "track_caller" +path = "tests/track_caller/mod.rs" + [package.metadata.docs.rs] features = ["std", "nested-values", "dynamic-keys"] diff --git a/build.rs b/build.rs index e7236610..9005999b 100644 --- a/build.rs +++ b/build.rs @@ -6,7 +6,14 @@ pub fn main() { // Renaming imports using the clippy-disallowed-types lint doesn't work println!("cargo:rustc-cfg=has_std_error") } + if rustversion::cfg!(since(1.79)) { + // use core::panic::Location::caller() rather than file!(), line!(), column!() macros. + // This requires inline const { ... } expressions and Location::caller() to be a const-fn + // Both are added in Rust 1.79 + println!("cargo:rustc-cfg=use_const_location"); + } if rustversion::cfg!(since(1.80)) { + println!("cargo:rustc-check-cfg=cfg(use_const_location)"); println!("cargo:rustc-check-cfg=cfg(has_std_error)") } } diff --git a/src/lib.rs b/src/lib.rs index 33f1476d..f199649f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -316,7 +316,6 @@ use core::{convert, fmt, result}; use core::error::Error as StdError; #[cfg(feature = "std")] use std::error::Error as StdError; - // }}} // {{{ Macros @@ -441,24 +440,86 @@ macro_rules! slog_kv( ($($args:tt)*) => ($crate::kv!(@ (); $($args)*)); ); +/// Use [`core::panic::Location::caller()`] to respect `#[track_caller]` +/// +/// **Beware**: Inline `const { ... }` expressions do not interact well with `#[track_caller]`, +/// yet are necessary for slog to create `&'static slog::Record` expressions. +/// See `tests/bizarre_nested_const_location.rs` for details. +/// To work around this, we go to great lengths to ensure that only the outermost `record!` macro +/// involves a `const { ... }` expression. +#[cfg(use_const_location)] +#[doc(hidden)] +pub mod location_support { + use std::panic::Location; + use crate::{RecordLocation, RecordStatic}; + + #[doc(hidden)] + #[macro_export] + macro_rules! __builtin_location { + (@record_location) => (const { + let loc = core::panic::Location::caller(); + &$crate::RecordLocation { + column: loc.column(), + line: loc.line(), + file: loc.file(), + function: "", + module: core::module_path!(), + } + }); + // implements the standard `record_static!` macro, + (@record_static $lvl:expr, $tag:expr) => ({ + let location = $crate::__builtin_location!(@record_location); + $crate::RecordStatic { level: $lvl, location, tag: $tag } + }); + // creates a static reference to the result of a record_static! macro + (@record_static_ref $lvl:expr, $tag:expr) => (const { + let loc = core::panic::Location::caller(); + &$crate::RecordStatic { + level: $lvl, + location: &$crate::RecordLocation { + column: loc.column(), + line: loc.line(), + file: loc.file(), + function: "", + module: core::module_path!(), + }, tag: $tag + } + }); + } +} + +#[cfg(not(use_const_location))] +#[doc(hidden)] +pub mod location_support { + #[doc(hidden)] + #[macro_export] + macro_rules! __builtin_location { + (@caller) => ((file!(), line!(), column!())); + (@record_static $lvl:expr, $tag:expr) => { + static LOC : $crate::RecordLocation = $crate::RecordLocation { + file: file!(), + line: line!(), + column: $crate::__builtin!(@column), + function: "", + module: $crate::__builtin!(@module_path), + }; + $crate::RecordStatic { + location : &LOC, + level: $lvl, + tag : $tag, + } + } + } +} + + + #[macro_export(local_inner_macros)] /// Create `RecordStatic` at the given code location +/// +/// On versions of Rust before 1.79, `#[track_caller]` is not respected. macro_rules! record_static( - ($lvl:expr, $tag:expr,) => { record_static!($lvl, $tag) }; - ($lvl:expr, $tag:expr) => {{ - static LOC : $crate::RecordLocation = $crate::RecordLocation { - file: $crate::__builtin!(@file), - line: $crate::__builtin!(@line), - column: $crate::__builtin!(@column), - function: "", - module: $crate::__builtin!(@module_path), - }; - $crate::RecordStatic { - location : &LOC, - level: $lvl, - tag : $tag, - } - }}; + ($lvl:expr, $tag:expr $(,)?) => ($crate::__builtin_location!(@record_static $lvl, $tag)); ); /// Create `RecordStatic` at the given code location (alias) @@ -477,14 +538,12 @@ macro_rules! slog_record_static( /// Note that this requires that `lvl` and `tag` are compile-time constants. If /// you need them to *not* be compile-time, such as when recreating a `Record` /// from a serialized version, use `Record::new` instead. +/// +/// On versions of Rust before 1.79, `#[track_caller]` is not respected. macro_rules! record( - ($lvl:expr, $tag:expr, $args:expr, $b:expr,) => { - record!($lvl, $tag, $args, $b) - }; - ($lvl:expr, $tag:expr, $args:expr, $b:expr) => {{ - #[allow(dead_code)] - static RS : $crate::RecordStatic<'static> = record_static!($lvl, $tag); - $crate::Record::new(&RS, $args, $b) + ($lvl:expr, $tag:expr, $args:expr, $b:expr $(,)?) => {{ + let rs: &'static $crate::RecordStatic<'static> = $crate::__builtin_location!(@record_static_ref $lvl, $tag); + $crate::Record::new(rs, $args, $b) }}; ); @@ -895,15 +954,12 @@ macro_rules! slog_trace( /// Helper macro for using the built-in macros inside of /// exposed macros with `local_inner_macros` attribute. -#[doc(hidden)] -#[macro_export] // TODO: Always use explicit paths +#[macro_export] macro_rules! __builtin { (@format_args $($t:tt)*) => ( format_args!($($t)*) ); (@stringify $($t:tt)*) => ( stringify!($($t)*) ); - (@file) => ( file!() ); - (@line) => ( line!() ); - (@column) => ( column!() ); (@module_path) => ( module_path!() ); + (@$loc_kind:ident) => ( $crate::__builtin_location!(@$loc_kind) ); (@wrap_error $v:expr) => ({ // this magical sequence of code is used to wrap either with // slog::ErrorValue or slog::ErrorRef as appropriate @@ -2353,6 +2409,8 @@ pub struct RecordStatic<'a> { /// /// Record is passed to a `Logger`, which delivers it to its own `Drain`, /// where actual logging processing is implemented. +/// +/// On versions of Rust before 1.79, `#[track_caller]` is not respected. #[must_use = "does nothing by itself"] pub struct Record<'a> { rstatic: &'a RecordStatic<'a>, diff --git a/tests/track_caller/mod.rs b/tests/track_caller/mod.rs new file mode 100644 index 00000000..4f717d6a --- /dev/null +++ b/tests/track_caller/mod.rs @@ -0,0 +1,38 @@ +//! Tests that slog respects `#[track_caller]` +//! +//! WARNING: Formatting changes to this file will disrupt the line and column numbers, +//! triggering test failures. +//! You have been warned. + +use std::panic::Location; + +// NOTE: Using a path attribute here breaks Location.file() +mod separate_file; + +#[test] +#[rustversion::attr(before(1.79), ignore = "no const Location::caller")] +fn track_caller() { + let record = separate_file::record_track(); + assert_eq!(record.file(), Location::caller().file()); + assert_eq!(record.line(), 12); + assert_eq!(record.column(), 5); +} + +#[test] +fn no_track_caller() { + let record = separate_file::record_no_track(); + assert_eq!(record.file(), "tests/track_caller/separate_file.rs"); + assert_eq!(record.line(), 7); + assert_eq!(record.column(), 18); +} + +#[test] +fn track_caller_loc() { + let loc = const { indirect() }; + assert_eq!(loc.line(), 31) +} + +#[track_caller] +const fn indirect() -> &'static core::panic::Location<'static> { + Location::caller() +} \ No newline at end of file diff --git a/tests/track_caller/separate_file.rs b/tests/track_caller/separate_file.rs new file mode 100644 index 00000000..c2241b6d --- /dev/null +++ b/tests/track_caller/separate_file.rs @@ -0,0 +1,14 @@ +//! WARNING: Formatting changes to this file will disrupt the line and column numbers, +//! triggering test failures. +//! You have been warned. + +pub fn record_no_track() -> slog::Record<'static> { + let args = Box::leak(Box::new(format_args!("problem!"))); + slog::record!(slog::Level::Warning, "", args, slog::b!()) +} + +#[track_caller] +pub fn record_track() -> slog::Record<'static> { + let args = Box::leak(Box::new(format_args!("problem!"))); + slog::record!(slog::Level::Warning, "", args, slog::b!()) +}