0

My goal

I'm trying to make my actix-web code more readable and came up with the idea that I want to extend Result with methods that transform errors into actix specific ones with status code etc. (so that I can hide some errors and give details to others depending on the used method). I already had something similar in a previous project using an own declarative macro but it wasn't convenient in bigger chains as calling it using .macroname(args) isn't possible.

I already found out how I can use the location (filename and line number) from the calling code inside the method to include it in the message, but...

The log crate's macros (info, warn, ...) show the wrong module

The log messages include the source module of the log call what I consider very useful but when the auxiliary methods are used the module in which they're defined is shown in the log message instead.

I already tried to look at logs source but didn't find something that could help me. I'll attach a slightly changed file that I'd like to use in my real actix project and a MWE with a Cargo.toml.

I completely understand why log is displaying the "wrong" module but I would still like to change it to make my extensions useful.

(I also don't think implementing From for actix errors is as useful, because I would like to give arguments to some methods.)

// Not my real error, I use [actix_web]
#[derive(Debug, thiserror::Error)]
pub enum FrameworkError {
    #[error("400: {0}")]
    Error400(Box<dyn std::error::Error>),
}

pub mod my_library {
    pub mod extensions {
        pub mod logging {
            use crate::FrameworkError;

            pub trait LogChangeErrForRespExt {
                type R;

                #[track_caller]
                fn log_and_explain_400(self) -> Self::R;
            }

            impl<T, E: std::error::Error + 'static> LogChangeErrForRespExt for Result<T, E> {
                type R = Result<T, FrameworkError>;

                /// Logs the client error using [log::info] and returns the [Display] string
                /// of the original error to the client
                #[track_caller]
                fn log_and_explain_400(self) -> Self::R {
                    // .map_err doesn't work as it is a function without #[track_caller]
                    match self {
                        Ok(ok) => Ok(ok),
                        Err(err) => {
                            let loc = std::panic::Location::caller();

                            // This log call *should* list [my_application_code::routes_example] as
                            // module

                            log::warn!(
                                "real location is {:?}:{}: Error: {err:?}",
                                loc.file(),
                                loc.line()
                            );
                            Err(FrameworkError::Error400(Box::new(err)))
                        }
                    }
                }
            }
        }
    }
}
use my_library::extensions::logging::LogChangeErrForRespExt;

pub mod my_application_code {
    pub mod routes_example {
        use crate::LogChangeErrForRespExt;

        fn failing_function() -> Result<u32, std::num::ParseIntError> {
            "abc".parse()
        }

        pub fn some_route() -> Result<u32, crate::FrameworkError> {
            log::warn!("This log call is from the correct module");
            let long_chain = failing_function().log_and_explain_400()? + 42; // line 61
            Ok(long_chain)
        }
    }
}

fn main() {
    let _ = dotenvy::dotenv();
    env_logger::builder()
        .filter_level(log::LevelFilter::Info)
        .init();

    let failure = my_application_code::routes_example::some_route();

    println!("{failure:?}");
}
[package]
name = "question-location-log"
version = "0.1.0"
edition = "2024"

[dependencies]
dotenvy = "0.15.7"
env_logger = "0.11.8"
log = "0.4.27"
thiserror = "2.0.14"
[2025-08-15T10:08:55Z WARN  question_location_log::my_application_code::routes_example] This log call is from the correct module
[2025-08-15T10:08:55Z WARN  question_location_log::my_library::extensions::logging] real location is "src/main.rs":61: Error: ParseIntError { kind: InvalidDigit }
Err(Error400(ParseIntError { kind: InvalidDigit }))
use std::fmt::{Debug, Display};

pub trait LogChangeErrForRespExt {
    type R;

    #[track_caller]
    fn log_hide_res(self) -> Self::R;
    #[track_caller]
    fn log_and_explain_400(self) -> Self::R;
}

impl<T, E: Debug + Display + 'static> LogChangeErrForRespExt for Result<T, E> {
    type R = Result<T, actix_web::error::Error>;

    /// Logs the error using [log::warn] and replaces the error with
    /// an [actix_web::error::ErrorInternalServerError] without details.
    ///
    /// This is for security reasons so no private error information could be leaked.
    #[track_caller]
    fn log_hide_res(self) -> Self::R {
        match self {
            Ok(ok) => Ok(ok),
            Err(err) => {
                log::warn!("real location is {:?}: Msg: {err:?}", std::panic::Location::caller());
                Err(actix_web::error::ErrorInternalServerError(
                    "Unexpected error occured",
                ))
            }
        }
    }

    /// Logs the client error using [log::info] and returns the [Display] string
    /// of the original error to the client
    #[track_caller]
    fn log_and_explain_400(self) -> Self::R {
        // .map_err doesn't work as it is a function without #[track_caller]
        match self {
            Ok(ok) => Ok(ok),
            Err(err) => {
                log::info!("real location is {:?}: Error: {err:?}", std::panic::Location::caller());
                Err(actix_web::error::ErrorBadRequest(err))
            }
        }
    }
}
0

1 Answer 1

0

So I found out that log offers an optional argument named target for specifying the string which only defaults to the module name of the caller.

I assume that this is done using the builtin module_path macro provided by the compiler. This works for log but I don't think it could work for getting the path for a Location as I would need.

Because now I don't think there is a better solution when not using a macro (what I don't want because of the calling syntax) I will use the file path with line number for this argument.

Feel free to let me know if there is a more elegant solution!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.