```Econometrics and Free Software by Bruno Rodrigues.
Check out my package that adds logging to R functions, {chronicler}.
Or read my free ebook to learn some R, Modern R with the tidyverse,
and if you're interested in setting up reproducible analytical pipelines,
You can also watch my youtube channel.
Buy me a coffee, my kids don't let me sleep.
```

Capture errors, warnings and messages

R

In my last video I tried to add a feature to my {loud} package (more info here) and I succeeded. But in succeeding in realised that I would need to write a bit more code than what I expected. To make a long story short: it is possible to capture errors using `purrr::safely()`:

``````library(purrr)
safe_log <- safely(log)

a <- safe_log("10")

str(a)``````
``````## List of 2
##  \$ result: NULL
##  \$ error :List of 2
##   ..\$ message: chr "non-numeric argument to mathematical function"
##   ..\$ call   : language .Primitive("log")(x, base)
##   ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"``````

`a` is now a list with elements `\$result` and `\$error`. If everything goes right, `\$result` holds the result of the operation, and if everything goes wrong, `\$result` is `NULL` but `\$error` now contains the error message. This is especially useful in non-interactive contexts. There is another similar function in `{purrr}` called `quietly()`, which captures warnings and messages:

``````quiet_log <- quietly(log)

b <- quiet_log(-10)

str(b)``````
``````## List of 4
##  \$ result  : num NaN
##  \$ output  : chr ""
##  \$ warnings: chr "NaNs produced"
##  \$ messages: chr(0)``````

as you can see, providing a negative number to `log()` does not cause an error, but simply a warning. A result of `NaN` is returned (you can try with `log(-10)` in your console). `quietly()` captures the warning message and returns a list of 4 elements, `\$result`, `\$output`, `\$warnings` and `\$messages`. The problem here, is that:

``safe_log(-10)``
``## Warning in .Primitive("log")(x, base): NaNs produced``
``````## \$result
## [1] NaN
##
## \$error
## NULL``````

returns something useless: `\$result` is `NaN` (because that’s what `log()` returns for negative numbers) but `\$error` is `NULL` since no error was thrown, but only a warning! We have a similar problem with `quiet_log()`:

``quiet_log("10")``
``````Error in .Primitive("log")(x, base) :
non-numeric argument to mathematical function``````

here, the error message is thrown, but not captured, since `quietly()` does not capture error messages.

So, are we back to square one? Not necessarily, since you could compose both functions:

``````pure_log <- quietly(safely(log))

a2 <- pure_log(-10)

str(a2)``````
``````## List of 4
##  \$ result  :List of 2
##   ..\$ result: num NaN
##   ..\$ error : NULL
##  \$ output  : chr ""
##  \$ warnings: chr "NaNs produced"
##  \$ messages: chr(0)``````
``````b2 <- pure_log("10")

str(b2)``````
``````## List of 4
##  \$ result  :List of 2
##   ..\$ result: NULL
##   ..\$ error :List of 2
##   .. ..\$ message: chr "non-numeric argument to mathematical function"
##   .. ..\$ call   : language .Primitive("log")(x, base)
##   .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
##  \$ output  : chr ""
##  \$ warnings: chr(0)
##  \$ messages: chr(0)``````

As you can see, in the case of `a2`, the warning was captured, and in the case of `b2` the error was captured. The problem, is that the resulting object is quite complex. It’s a list where `\$result` is itself a list in case of a warning, or `\$error` is a list in case of an error.

I tried to write a function that would decorate a function (as do `safely()` and `quietly()`), which in turn would then return a simple list and capture, errors, warnings and messages. I came up with this code, after re-reading Advanced R, in particular this chapter:

``````purely <- function(.f){

function(..., .log = "Log start..."){

res <- rlang::try_fetch(
rlang::eval_bare(.f(...)),
error = function(err) err,
#rlang_error = function(rlerr) rlerr,
warning = function(warn) warn,
message = function(message) message,
)

final_result <- list(
result = NULL,
log = NULL
)

final_result\$result <- if(any(c("error", "rlang_error", "warning", "message") %in% class(res))){
NA
} else {
res
}

final_result\$log <- if(any(c("error", "rlang_error", "warning", "message") %in% class(res))){
#res\$message
purrr::pluck(res, "message", .default = "undefined error")
} else {
NA
}
final_result
}
}``````
``````f_m <- function(x){
message("this is a message")
str(x)
}

f_w <- function(x){
warning("this is a warning")
str(x)

}

f_e <- function(){
stop("This is an error")

}

pure_fm <- purely(f_m)
pure_fw <- purely(f_w)
pure_fe <- purely(f_e)``````

Messages get captured:

``````pure_fm(10) |>
str()``````
``````## List of 2
##  \$ result: logi NA
##  \$ log   : chr "this is a message\n"``````

as do warnings:

``````pure_fw(10) |>
str()``````
``````## List of 2
##  \$ result: logi NA
##  \$ log   : chr "this is a warning"``````

as do errors:

``````pure_fe() |>
str()``````
``````## List of 2
##  \$ result: logi NA
##  \$ log   : chr "This is an error"``````

The structure of the result is always `\$result` and `\$log`. In case everything goes well `\$result` holds the result:

``````pure_log <- purely(log)

pure_log(c(1,10))``````
``````## \$result
## [1] 0.000000 2.302585
##
## \$log
## [1] NA``````

And another example, with a more complex call:

``````pure_mean <- purely(mean)

pure_mean(c(1,10, NA), na.rm = TRUE)``````
``````## \$result
## [1] 5.5
##
## \$log
## [1] NA``````

But in case something goes wrong, the error message will get captured.

``suppressPackageStartupMessages(library(dplyr))``
``## {paint} masked print.tbl_df``
``pure_select <- purely(select)``

Let’s try here to select a column that does not exist:

``````clean_mtcars <- mtcars %>%
pure_select(hp, am, bm) #bm does not exist

str(clean_mtcars)``````
``````## List of 2
##  \$ result: logi NA
##  \$ log   : chr ""``````

Compare to what happens with `select()`:

``````clean_mtcars2 <- mtcars %>%
select(hp, am, bm) #bm does not exist``````
``````Error in `select()`:
! Can't subset columns that don't exist.
✖ Column `bm` doesn't exist.
Backtrace:
1. mtcars %>% select(hp, am, bm)
...
...``````

Update 2022-03-13

After writing this post I realised that the error message of select does not get captured. This is the only example I’ve found where the error message does not get caught. This seems to be related to the fact that tidyverse function have their own class of error messages that inherit from `error`. For some reason, there are no issues with other functions, for example:

``purely(group_by)(mtcars, bm)``
``````## \$result
## [1] NA
##
## \$log
##
## "Must group by variables found in `.data`."``````

I will need to solve this…

Post continued…

The code (and thus the pipeline) completely fails! I’ve added this function to my {loud} package, but the biggest benefit of all this is that the main function of the package, `loudly()` now uses `purely()` under the hood to provide more useful log messages in case of failure:

``````suppressPackageStartupMessages(library(loud))

loud_sqrt <- loudly(sqrt)
loud_mean <- loudly(mean)
loud_exp <- loudly(exp)

result_pipe <- -1:-10 |>
loud_mean() %>=% # This results in a negative number...
loud_sqrt() %>=% # which sqrt() does not know how to handle
loud_exp()``````

If we now inspect `result_pipe`, we find a complete log of what went wrong:

``result_pipe``
``````## \$result
## NULL
##
## \$log
## [1] "Log start..."
## [2] "✔ mean(-1:-10) started at 2022-03-13 14:17:30 and ended at 2022-03-13 14:17:30"
## [3] "✖ CAUTION - ERROR: sqrt(.l\$result) started at 2022-03-13 14:17:30 and failed at 2022-03-13 14:17:30 with following message: NaNs produced"
## [4] "✖ CAUTION - ERROR: exp(.l\$result) started at 2022-03-13 14:17:30 and failed at 2022-03-13 14:17:30 with following message: non-numeric argument to mathematical function"``````

If you want to know more about `{loud}`, I suggest you read my previous blog post and if you need a more realistic example, take a look at this.

If you try it, please let me know!

Hope you enjoyed! If you found this blog post useful, you might want to follow me on twitter for blog post updates and buy me an espresso or paypal.me, or buy my ebook on Leanpub. You can also watch my videos on youtube. So much content for you to consoom!