10

I am dealing with a function that is throwing both errors and warnings. (related: A Warning About Warning )

Often, a warning will proceed an error. In these situations, I would like to disregard the warning and process only the error.

On the other hand, if there is only a warning (with no error), then I would like to catch the warning.

I am attempting to work with the notoriously-easy-to-use tryCatch.

My immediate question is: Is there a way to force tryCatch to process errors before warnings (or to disregard warnings when there is an error)?

My understanding from the ?tryCatch documentation is that conditions are handled FIFO, in which case the answer to my immediate question is No - at least not directly. In which case, is it possible to process the warning and then have the function continue while still catching errors?

solutions NOT available to me:

  • suppressWarnings # I would like to still catch and handle the warnings
  • options(warn=2) # certain warnings are harmless
     relevant from `?tryCatch`

If a condition is signaled while evaluating expr then established handlers are checked, starting with the most recently established ones, for one matching the class of the condition. When several handlers are supplied in a single tryCatch then the first one is considered more recent than the second. If a handler is found then control is transferred to the tryCatch call that established the handler, the handler found and all more recent handlers are disestablished, the handler is called with the condition as its argument, and the result returned by the handler is returned as the value of the tryCatch call.

Below is a toy example:

F.errorAndWarning <- function() {
    warning("Warning before the error")
    cat("I have moved on.")
    stop("error")
    TRUE
}
F.error <- function() {stop("error"); TRUE}


test <- function(F)
  tryCatch(expr= {F}()
          , error=function(e)   cat("ERROR CAUGHT")
          , warning=function(w) cat("WARNING CAUGHT")
          )

test(F.error)
# ERROR CAUGHT
test(F.errorAndWarning)
# WARNING CAUGHT

Expected / ideal output:

test(F.errorAndWarning)
# ERROR CAUGHT
Ricardo Saporta
  • 54,400
  • 17
  • 144
  • 178
  • did you end up using any of the answers here? – Matthew Plourde Jul 24 '14 at 13:23
  • 2
    Matt, I used a hybrid of yours and Hansi's answer. I wasnt sure where to place the checkmark. I'm going to place it on Hansi's because I think his highly commented code will be helpful to others searching -- plus he could use a few extra points :) Thank you though, and for the reminder – Ricardo Saporta Jul 25 '14 at 05:30

4 Answers4

9

This relates to the "is it possible to process the warning and then have the function continue while still catching errors?" question.

I had a similar issue where I wanted to bind my logging functions for warnings and errors to the try catch and always continue after warnings and also be able to perform multiple attempts at try catch, e.g. for accessing a flimsy network drive. This is what I ended up using. This or a more simplified version could help with what your after.

MyLibrary.Sys.Try <- function(
    expr,                   # Expression that will be evaluated by the call to Try
    errorMessage="",        # Optional prepended string to add to error output
    warningMessage="",      # Optional prepended string to add to warning output
    failureMessage="",      # Optional prepended string to add to failing all attempts
    finally=NULL,           # Optional expression to be executed at the end of tryCatch
    attempts=1,             # Number of attempts to try evaluating the expression
    suppressError=TRUE,     # Should the call just log the error or raise it if all attempts fail
    quiet=FALSE             # Return expression normally or as invisible
) {
    tryNumber <- 0
    while(tryNumber<attempts) {
        tryNumber <- tryNumber + 1

        # If not suppressing the error and this is the last
        # attempt then just evaluate the expression straight out
        if(tryNumber == attempts && !suppressError){
            # NOTE: I think some warnings might be lost here when
            # running in non-interactive mode. But I think it should be okay
            # because even nested dispatch seems to pick them up:
            # MyLibrary.Sys.Try(MyLibrary.Sys.Try(function(),suppressError=F))
            return(expr)
        }

        tryCatch({
            # Set the warning handler to an empty function
            # so it won't be raised by tryCatch but will
            # be executed by withCallingHandlers
            options(warning.expression=quote(function(){}))
            withCallingHandlers({
                if(quiet) {
                    return(invisible(expr))
                } else {
                    return(expr)
                }
            },error=function(ex){
                MyLibrary.Sys.Log.Error(errorMessage,ex)
            }, warning=function(warn){
                # Had issues with identical warning messages being
                # issued here so to avoid that only log it the first
                # time it happens in a given minute. 
                warnStamp <- paste(Sys.time(),warn,collapse="_",sep="")
                if(!identical(warnStamp,getOption("MyLibrary.LAST.WARNING"))) {
                    if(!(interactive() && is.null(getOption("warning.expression")))){
                        MyLibrary.Sys.Log.Warning(warningMessage,warn)
                    }
                    options(MyLibrary.LAST.WARNING=warnStamp)
                }
            })
        },error=function(ex){
            # Suppressing the error since it's been logged
            # by the handler above. Needs to be suppressed
            # to not invoke the stop directly since the
            # handler above passes it through.
        },finally={
            # Set the warning handler to the default value
            # of NULL which will cause it to revert to it's
            # normal behaviour. If a custom handler is normally
            # attached it would make sense to store it above
            # and then restore it here. But don't need that now
            options(warning.expression=NULL)
            if(!is.null(finally)){
                if(quiet) {
                    return(invisible(finally))
                } else {
                    return(finally)
                }
            }
        })
    }

    msg <- paste(ifelse(nchar(failureMessage)>0," - ",""),"Failed to call expression after ",attempts," attempt(s)",sep="")
    MyLibrary.Sys.Log.Error(failureMessage,msg)
}
Hansi
  • 2,566
  • 1
  • 15
  • 19
5

I would write a function that executes an expression and prioritizes the errors.

prioritize.errors <- local({
    # this function executes an expression and stores the warnings 
    # until it finishes executing.
    warnings <- list()
    w.handler <- function(w) {
        warnings <<- c(warnings, list(w))
        invokeRestart('muffleWarning') # here's the trick
    }
    function(expr) {
        withCallingHandlers({expr}, warning=w.handler)
        for (w in warnings) warning(w)
        warnings <<- list()
    }
})

F.warning <- function() {
    warning("a warning")
    message('ok')
}


test <- function(expr) {
    tryCatch(expr, 
        error=function(e) cat("ERROR CAUGHT"), 
        warning=function(w) cat("WARNING CAUGHT")
    )
}

test(prioritize.errors(F.error()))
# ERROR CAUGHT 

test(prioritize.errors(F.warning()))
# ok
# WARNING CAUGHT

test(prioritize.errors(F.errorAndWarning()))
# I have moved on.ERROR CAUGHT
Matthew Plourde
  • 43,932
  • 7
  • 96
  • 113
4

I've written a handy helper function for evaluation in the pander package, that captures all warnings, errors and anything printed on standard output, along with the raw R object returned in the call:

> library(pander)
> evals('F.errorAndWarning()')
[[1]]
$src
[1] "F.errorAndWarning()"

$result
NULL

$output
NULL

$type
[1] "error"

$msg
$msg$messages
NULL

$msg$warnings
[1] "Warning before the error"

$msg$errors
[1] "error"


$stdout
[1] "I have moved on."

attr(,"class")
[1] "evals"
daroczig
  • 28,004
  • 7
  • 90
  • 124
2

Very extensive and nice work by all three answers, but I think a lot of people are also looking for a short, simple way of handling a warning, but continuing execution. Which can be done quite shortly, as Matthew showed: by invoking a restart (and using withCallingHandlers):

test <- function(FUN) withCallingHandlers(
  expr=FUN(),
  error=function(e) cat("ERROR CAUGHT\n"),
  warning=function(w) {cat('WARNING CAUGHT\n'); invokeRestart(findRestart('muffleWarning'))}
)

This does still print warnings (even if an error is later generated), but execution is continued

Emil Bode
  • 1,784
  • 8
  • 16