Runtime access to method calling stack

Hi there.
I am looking to gain read-only access to the runtime method calling stack.
I am primarily interested in finding out the name of the method my logger.log(...) method is being called from. This is to reduce the burden of the developer having to write it for every log message he/she needs to generate throughout their code.

Thank you.
Brad.

Backtrace::capture() is the API you are looking for. but the standard library has very limited feature, I'd recommend the backtrace crate instead. which lets you iterate through the frames.

if you are ok with just the caller location (file name and line number), but without the function name, you don't need a full stack trace, you can just use the #[track_caller] attribute and the Location::caller() API.

I am actually after the method name: "their_method".

mod nuts{
    fn their_method(){
        logger.log(Level::FINE, "Just entered"); // This needs to find: "their_method"

        ...
    }
}

So a possible output for the log:

<DateTime stuff> nuts::their_method [FINE] Just entered

your options I can think of include:

  • require the user to manually instrument their code, e.g. with a procedural macro, similar to #[tracing::instrument] or #[function_name::named];

    this method needs modification to user code, but is more flexible, for example, it allows the user to custom the identifier instead of just the function name if they want.

  • extract the information from the runtime stack trace, e.g. BacktraceSymbol::name().

    this method doesn't need user instrumentation, but it has potential huge performance overhead, and it relies on the debug symbols to resolve function addresses to their names, and it may be unreliable for other reasons including compiler optimization.

  • some "hacky" solution abusing the type names of function items as used by this crate;

    IMO this is better than the previous ones, but keep in mind that std::any::type_name() is not guaranteed to give stable result, that's why I say it's a somewhat "hacky" solution, but it should be mostly fine for diagnostic usages like a logger.

1 Like

So how come Rust does not have an equivalent of C's:

void func (void)
{
  printf("%s", __func__);
}

it had been proposed but never came to a conclusion.

Hi there.
After much research, and profanity :face_with_open_eyes_and_hand_over_mouth:, I have gone with the following solution (based on another's work):

//!
//! # Logger Macro Impl
//!

use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, parse_macro_input};

pub(crate) fn logger_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
    // println!("attr: (is_empty: {}) {attr}", attr.to_string().is_empty());

    // Parse the input as `ItemFn` which is a type provided
    // by `syn` to represent a function.
    let input = parse_macro_input!(item as ItemFn);

    let ItemFn {
        // The function signature
        sig,
        // The visibility specifier of this function
        vis,
        // The function block or body
        block,
        // Other attributes applied to this function
        attrs,
    } = input;

    // Extract statements in the body of the functions
    let statements = block.stmts;

    // Store the function identifier for logging
    let function_identifier = if attr.to_string().is_empty() {
        sig.ident.clone().to_string()
    }else{
        attr.to_string()
    };

    // Reconstruct the function as output using parsed input
    quote!(
        // Reapply all the other attributes on this function.
        // The compiler doesn't include the macro we are
        // currently working in this list.
        #(#attrs)*
        // Reconstruct the function declaration
        #vis #sig {
            // At the beginning of the function, borrow a reference to
            // module level static logger.
            let __binding = LOGGER;
            let mut __log = __binding.borrow_mut();
            __log.set_fn_name(#function_identifier); // <<== Here is the fn_name.

            #(#statements)*
        }
    )
    .into()
}

Now that this is working, I have been able to develop a fully functional logging crate.
I know there are a lot of them out there already, but one more just might be the one everyone
has been waiting for. Right!?! :innocent:

Hi I wanted to add my solution using the backtrace crate.
This gives output like trait/struct::function for example PolygonGraph::visibility_tree
For closures it adds closure at the end PolygonGraph::visibility_tree closure

#[cfg(feature = "debug_logs")]
#[macro_export]
macro_rules! log_debug {
    ($($arg:tt)*) => {{
        let mut function_name = String::from("unknown");

        use backtrace::Backtrace;
        let mut current_backtrace = Backtrace::new_unresolved();
        current_backtrace.resolve();
        if let Some(frame) = current_backtrace.frames().get(0) {
            if let Some(symbol) = frame.symbols().get(0) {
                if let Some(name) = symbol.name() {
                     let cleaned_name = format!("{}", name).split("::").map(|s| s.to_string()).collect::<Vec<String>>();
                     if cleaned_name.len() >= 2{


                    // Check if it's a closure and clean accordingly
                    if cleaned_name[cleaned_name.len() - 2].contains("{{closure}}") {
                        function_name = format!(
                            "{}::{} closure",
                            cleaned_name[cleaned_name.len() - 4],
                            cleaned_name[cleaned_name.len() - 3]
                        );
                    } else {
                        function_name = format!(
                            "{}::{}",
                            cleaned_name[cleaned_name.len() - 3],
                            cleaned_name[cleaned_name.len() - 2]
                        );
                    }
                }
                }
            }
        }
        log::debug!(
            "{}\n\t{}",
            function_name,
            format!($($arg)*)
        );
    }};
}