diff --git a/NEWS.md b/NEWS.md index 5edf5e7..5e03f86 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,7 @@ * `log_appender()`, `log_layout()` and `log_formatter()` now check that you are calling them with a function, and return the previously set value (#170, @hadley) * new function to return number of log indices (#194, @WurmPeter) * `appender_async` is now using `mirai` instead of a custom background process and queue system (#214, @hadley @shikokuchuo) +* File and line location of the log call is now available to the layouts (#110, @thomasp85) ## Fixes diff --git a/R/layouts.R b/R/layouts.R index 8ba9a74..1bf3f73 100644 --- a/R/layouts.R +++ b/R/layouts.R @@ -22,6 +22,8 @@ #' * `topenv`: the name of the top environment from which the parent call was called #' (eg R package name or `GlobalEnv`) #' * `call`: parent call (if any) calling the logging function +#' * `location`: A list with element `path` and `line` giving the location of the +#' log call #' * `fn`: function's (if any) name calling the logging function #' #' @param log_level log level as per [log_levels()] @@ -45,6 +47,7 @@ get_logger_meta_variables <- function(log_level = NULL, topenv = top_env_name(.topenv), fn = deparse_to_one_line(.topcall[[1]]), call = deparse_to_one_line(.topcall), + location = log_call_location(.logcall), time = timestamp, levelr = log_level, level = attr(log_level, "level"), diff --git a/R/logger-meta.R b/R/logger-meta.R index a56fee6..b34aacf 100644 --- a/R/logger-meta.R +++ b/R/logger-meta.R @@ -15,6 +15,7 @@ logger_meta_env <- function(log_level = NULL, delayedAssign("fn", deparse_to_one_line(.topcall[[1]]), assign.env = env) delayedAssign("call", deparse_to_one_line(.topcall), assign.env = env) delayedAssign("topenv", top_env_name(.topenv), assign.env = env) + delayedAssign("location", log_call_location(.logcall), assign.env = env) env$time <- timestamp env$levelr <- log_level diff --git a/R/utils.R b/R/utils.R index 0fe42b3..1e230ee 100644 --- a/R/utils.R +++ b/R/utils.R @@ -45,6 +45,29 @@ top_env_name <- function(.topenv = parent.frame()) { environmentName(topenv(.topenv)) } +#' Finds the location of the logger call (file and line) +#' @return list with path and line element +#' @noRd +#' @param .logcall The call that emitted the log +log_call_location <- function(.logcall) { + call_string <- deparse(.logcall) + loc <- list( + path = "", + line = "" + ) + for (trace in .traceback(0)) { + if (identical(call_string, as.vector(trace))) { + ref <- attr(trace, "srcref") + loc$line <- ref[1L] + file <- attr(ref, "srcfile") + if (!is.null(file)) { + loc$path <- file$filename + } + break + } + } + loc +} #' Deparse and join all lines into a single line #' diff --git a/man/get_logger_meta_variables.Rd b/man/get_logger_meta_variables.Rd index 7940c71..8819cfa 100644 --- a/man/get_logger_meta_variables.Rd +++ b/man/get_logger_meta_variables.Rd @@ -61,6 +61,8 @@ otherwise a fallback namespace (eg usually \code{global}) \item \code{topenv}: the name of the top environment from which the parent call was called (eg R package name or \code{GlobalEnv}) \item \code{call}: parent call (if any) calling the logging function +\item \code{location}: A list with element \code{path} and \code{line} giving the location of the +log call \item \code{fn}: function's (if any) name calling the logging function } } diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 6d1ca91..73692dc 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -1,3 +1,8 @@ +# Do not move this to another line as the location of this piece of code is tested for +test_info <- function() { + log_info("TEST") +} + local_test_logger <- function(threshold = INFO, formatter = formatter_glue, layout = layout_simple, diff --git a/tests/testthat/test-logger-meta.R b/tests/testthat/test-logger-meta.R index a86bf98..50a4d95 100644 --- a/tests/testthat/test-logger-meta.R +++ b/tests/testthat/test-logger-meta.R @@ -31,4 +31,7 @@ test_that("captures other environmental metadata", { expect_equal(env$os_release, sysinfo$release) expect_equal(env$os_version, sysinfo$version) expect_equal(env$user, sysinfo$user) + + local_test_logger(layout = layout_glue_generator("{location$path}#{location$line}: {msg}")) + expect_output(test_info(), "logger/tests/testthat/helper.R#3: TEST") })