Search code examples
rerror-handlingtry-catch

How do I save warnings and errors as output from a function?


I'm using lapply to run a complex function on a large number of items, and I'd like to save the output from each item (if any) together with any warnings/errors that were produced so that I can tell which item produced which warning/error.

I found a way to catch warnings using withCallingHandlers (described here). However, I need to catch errors as well. I can do it by wrapping it in a tryCatch (as in the code below), but is there a better way to do it?

catchToList <- function(expr) {
  val <- NULL
  myWarnings <- NULL
  wHandler <- function(w) {
    myWarnings <<- c(myWarnings, w$message)
    invokeRestart("muffleWarning")
  }
  myError <- NULL
  eHandler <- function(e) {
    myError <<- e$message
    NULL
  }
  val <- tryCatch(withCallingHandlers(expr, warning = wHandler), error = eHandler)
  list(value = val, warnings = myWarnings, error=myError)
} 

Sample output of this function is:

> catchToList({warning("warning 1");warning("warning 2");1})
$value
[1] 1

$warnings
[1] "warning 1" "warning 2"

$error
NULL

> catchToList({warning("my warning");stop("my error")})
$value
NULL

$warnings
[1] "my warning"

$error
[1] "my error"

There are several questions here on SO that discuss tryCatch and error handling, but none that I found that address this particular issue. See How can I check whether a function call results in a warning?, warnings() does not work within a function? How can one work around this?, and How to tell lapply to ignore an error and process the next thing in the list? for the most relevant ones.


Solution

  • Maybe this is the same as your solution, but I wrote a factory to convert plain old functions into functions that capture their values, errors, and warnings, so I can

    test <- function(i)
        switch(i, "1"=stop("oops"), "2"={ warning("hmm"); i }, i)
    res <- lapply(1:3, factory(test))
    

    with each element of the result containing the value, error, and / or warnings. This would work with user functions, system functions, or anonymous functions (factory(function(i) ...)). Here's the factory

    factory <- function(fun)
        function(...) {
            warn <- err <- NULL
            res <- withCallingHandlers(
                tryCatch(fun(...), error=function(e) {
                    err <<- conditionMessage(e)
                    NULL
                }), warning=function(w) {
                    warn <<- append(warn, conditionMessage(w))
                    invokeRestart("muffleWarning")
                })
            list(res, warn=warn, err=err)
        }
    

    and some helpers for dealing with the result list

    .has <- function(x, what)
        !sapply(lapply(x, "[[", what), is.null)
    hasWarning <- function(x) .has(x, "warn")
    hasError <- function(x) .has(x, "err")
    isClean <- function(x) !(hasError(x) | hasWarning(x))
    value <- function(x) sapply(x, "[[", 1)
    cleanv <- function(x) sapply(x[isClean(x)], "[[", 1)