tokio-rs / tracing

Application level tracing for Rust.
https://tracing.rs
MIT License
5.48k stars 720 forks source link

Non-const event level #2730

Open Victor-N-Suadicani opened 1 year ago

Victor-N-Suadicani commented 1 year ago

Feature Request

Crates

tracing

Motivation

Currently, this code is impossible it seems:

let level = tracing::Level::DEBUG; // Imagine this is decided at runtime.
tracing::event!(level, "foobar"); // ERROR: attempt to use non-constant value in a constant

This is surprising to me because the equivalent in the log crate just works (TM):

let level = log::Level::Debug;
log::log!(level, "foobar"); // Works fine :)

This is useful to decide at runtime what log level to use for an error based on its variant, just as an example. Some variants are perhaps expected, while others are rarer. The expected ones you would like to log at debug and the rare ones at error. The log crate supports this easily, tracing seems not to for some reason.

Proposal

Make it so this compiles:

let level = tracing::Level::DEBUG;
tracing::event!(level, "foobar");

Alternatives

I personally don't know of any other (just as easy) way to achieve this but I'd love to know.

davidbarsky commented 1 year ago

Thanks for opening this issue and sorry for taking a second to respond. tracing requires metadata fields like be 'static In order to enable fast filtering of events/spans. log doesn't have this requirement because it prioritizes (prioritized?) FFI with non-Rust libraries, whose log metadata might not be 'static.

I suppose this is a decision that can be revisited in future versions of tracing (in that making fields Cow<'static> is something that's already in the cards), but I don't recall what the implications are for doing something similar to metadata.

ten3roberts commented 10 months ago

Thanks for opening this issue and sorry for taking a second to respond. tracing requires metadata fields like be 'static In order to enable fast filtering of events/spans. log doesn't have this requirement because it prioritizes (prioritized?) FFI with non-Rust libraries, whose log metadata might not be 'static.

I suppose this is a decision that can be revisited in future versions of tracing (in that making fields Cow<'static> is something that's already in the cards), but I don't recall what the implications are for doing something similar to metadata.

Do you know if it is possible to work around this, possibly even to create events dynamically at runtime?

My current use case involves a plugin system which generates a log event (my own custom struct with a module and file path), and I would like to output these as first class tracing events with the correct log levels, rather than a new debug!("{}: {}", event.module, event.message) which has the callsite of the facade, and not the producer.

If that is not possible, would it be possible to workaround a non-const event level, other than a match statement for each log level which repeats the event macro?

mkatychev commented 8 months ago

@ten3roberts you could pass the macro token token fragments that you'd normally pass into event macros through a match arm along with a Level:

#[macro_export]
macro_rules! dyn_event {
    ($lvl:ident, $($arg:tt)+) => {
        match $lvl {
            ::tracing::Level::TRACE => ::tracing::trace!($($arg)+),
            ::tracing::Level::DEBUG => ::tracing::debug!($($arg)+),
            ::tracing::Level::INFO => ::tracing::info!($($arg)+),
            ::tracing::Level::WARN => ::tracing::warn!($($arg)+),
            ::tracing::Level::ERROR => ::tracing::error!($($arg)+),
        }
    };
}
RReverser commented 1 month ago
#[macro_export]
macro_rules! dyn_event {

It would be nice if this helper was added to tracing itself. I find myself reinventing it over and over in new projects.

mkatychev commented 1 month ago

@RReverser actually doesn't the event! macro already do this?

event!(Level::INFO, the_answer = data.0);

EDIT: no it doesn't, it could probaby be refactored to hold something like this assuming some sort of token is used :

let level = ::tracing::Level::TRACE;
event!(lvl: level, "trace");
event!(lvl: level, "trace");
RReverser commented 1 month ago

EDIT: no it doesn't

Yeah that's the crux of this GH issue.

mkatychev commented 1 month ago

I actually have a record! macro that uses Span::current() used internally I've been meaning to upstream (It's a draft right now):

let _my_span = info_span!("my_span", uuid = field::Empty).entered();
let uuid = uuid_str.parse()?;

record!(%uuid);
// adds INFO my_span{uuid=424644e0-4113-4caa-9467-75f87bf9e9e4}: hello!
info!("hello");

The lvl: token could probably be worked into that PR as well.

Part of the friction in adding either is that I'm still trying to figure out how to make valueset! macro's exit branch call an Event::new() and Span::record(k, v) respectively https://github.com/tokio-rs/tracing/blob/527b4f66a604e7a6baa6aa7536428e3a303ba3c8/tracing/src/macros.rs#L2789-L2792