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.
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
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.
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.
Hi there.
After much research, and profanity , 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!?!
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)*)
);
}};
}