Handling Errors Before Warnings in Trycatch

Handling errors before warnings in tryCatch

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)
}

Store errors and warnings with tryCatch() in a list

Here's a version that puts the tryCatch within the loop, and saves the errors and warnings that were generated:

  hospitals_url <- "https://services1.arcgis.com/Hp6G80Pky0om7QvQ/arcgis/rest/services/Hospitals_1/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json"
hospitals_num <- c(0, 2000, 4000, 6000)
hosp_e <- list()
hosp_w <- list()

hosget <- lapply(hospitals_num, function(num) {
tryCatch({
hospitals_url_loop <- paste(hospitals_url, "&resultOffset=", num)
hospitals_json <- fromJSON(hospitals_url_loop)
hospitals_df <- as.data.frame(hospitals_json$features$attributes)},
error = function(e){
hosp_e <<- c(hosp_e, list(e))
print(e)},

warning = function(w){
hosp_w <<- c(hosp_w, list(w))
print(w)}
) })

hospitals_df <- do.call(rbind, hosget)

I didn't adapt your finally code to this rearrangement; I'll leave that to you. But at the end, hosp_e will be a list holding all the errors, and hosp_w will be a list holding all the warnings.

How to correctly log warnings and errors using `tryCatch` in R?

Based on Ronak's helpful comment and the following question How do I save warnings and errors as output from a function?, the code can be simplified as follows:

# Storage.
warns = list()
errs = list()

# Example function.
fun <- function(i) {
# Issue warning.
warning(paste("Warn.", i))

# Stop.
if(i == 3) { stop(paste("Err.", i)) }
}

# Evaluate `fun`.
for (i in 1:4) {
tryCatch(withCallingHandlers(
expr = fun(i),

# Handle the warnings.
warning = function(w) {
warns <<- c(warns, list(w))
invokeRestart("muffleWarning")
}),

# Handle the errors.
error = function(e) {
errs <<- c(errs, list(e))
}
)
}

The output then looks like:

warns

# [[1]]
# <simpleWarning in fun(i): Warn. 1>
#
# [[2]]
# <simpleWarning in fun(i): Warn. 2>
#
# [[3]]
# <simpleWarning in fun(i): Warn. 3>
#
# [[4]]
# <simpleWarning in fun(i): Warn. 4>

errs

# [[1]]
# <simpleError in fun(i): Err. 3>

More information and links are provided in the question linked above.

R tryCatch handling one kind of error

You could use try to handle errors:

result <- try(log("a"))

if(class(result) == "try-error"){
error_type <- attr(result,"condition")

print(class(error_type))
print(error_type$message)

if(error_type$message == "non-numeric argument to mathematical function"){
print("Do stuff")
}else{
print("Do other stuff")
}
}

# [1] "simpleError" "error" "condition"
# [1] "non-numeric argument to mathematical function"
# [1] "Do stuff"

R tryCatch but retain the expression result in the case of a warning

Here's a base solution with a custom function myCatch(), which has a form similar to tryCatch() (and identical to withCallingHandlers()). Feel free to adapt it, especially in the areas designated by my # ADAPT... comments.

By request, I have also Updated myCatch() to accept a user-defined function custom_fun. Based on the result from expr, custom_fun will process any warning object thrown when evaluating expr, and its output will be returned as diagnostics (alongside results).

myCatch <- function(# The expression to execute.
expr,
# Further arguments to tryCatch().
...,
# User-defined function to extract diagnostic info from
# warning object, based on output that resulted from expr.
custom_fun = function(result, w){return(w)}) {
######################
## Default Settings ##
######################

# Defaults to NULL results and empty list of diagnostics.
DEFAULT_RESULTS <- NULL
DEFAULT_DIAGNOSTICS <- NULL

# Defaults to standard R error message, rather than a ponderous traceback
# through the error handling stacks themselves; also returns the error object
# itself as the results.
DEFAULT_ERROR <- function(e){
message("Error in ", deparse(e$call), " : ", e$message)
return(e)
}


################
## Initialize ##
################

# Initialize output to default settings.
res <- DEFAULT_RESULTS
diag <- DEFAULT_DIAGNOSTICS
err <- DEFAULT_ERROR

# Adjust error handling if specified by user.
if("error" %in% names(list(...))) {
err <- list(...)$error
}


#######################
## Handle Expression ##
#######################

res <- tryCatch(
expr = {
withCallingHandlers(
expr = expr,
# If expression throws a warning, record diagnostics without halting,
# so as to store the result of the expression.
warning = function(w){
parent <- parent.env(environment())
parent$diag <- w
}
)
},
error = err,
...
)


############
## Output ##
############

# Package the results as desired.
return(list(result = res,
diagnostics = custom_fun(res, diag)))
}

Application

For your purposes, use myCatch() like so

x <- myCatch(someLongRunningFunctionThatMightGenerateWarnings())

or more generally

x <- myCatch(expr = {
# ...
# Related code.
# ...
someLongRunningFunctionThatMightGenerateWarnings()
},
# ...
# Further arguments like 'finally' to tryCatch().
# ...
custom_fun = function(result, w){
# ...
# Extract warning info from 'w'.
# ...
})

and feel free to customize error or finally as you would with tryCatch(). If you do customize warning, your diagnostics will still be preserved in the output, but you will lose your intended output for result (which will instead become the return value you specified in warning).

If we followed your specific example here, and used myCatch() like so

output <- myCatch(
log(-5),
custom_fun = function(result, w){paste(as.character(result), "with warning", w$message)}
)
output

then R would display the warning message

Warning message:
In log(-5) : NaNs produced

and give us the following output:

$result
[1] NaN

$diagnostics
[1] "NaN with warning NaNs produced"

Further Examples

When we apply myCatch() to certain example expressions, using simply the default for custom_fun, here are the results:

Normal

output_1 <- myCatch(expr = {log(2)},
finally = {message("This is just like using 'finally' for tryCatch().")})
output_1

will display the custom message

This is just like using 'finally' for tryCatch().

and give us the output:

$result
[1] 0.6931472

$diagnostics
NULL

Warning

output_2 <- myCatch(expr = {log(-1)})
output_2

will display the warning message

Warning message:
In log(-1) : NaNs produced

and give us the output:

$result
[1] NaN

$diagnostics
<simpleWarning in log(-1): NaNs produced>

Error (Default)

output_3 <- myCatch(expr = {log("-1")})
output_3

will gracefully handle the error and display its message

Error in log("-1") : non-numeric argument to mathematical function

and still give us the output (with an error object for results):

$result
<simpleError in log("-1"): non-numeric argument to mathematical function>

$diagnostics
NULL

Error (Custom)

output_4 <- myCatch(expr = {log("-1")}, error = function(e){stop(e)})
output_4

will kill myCatch() and immediately throw an error, with a ponderous traceback through the handling functions (here tryCatch()) within myCatch():

Error in log("-1") : non-numeric argument to mathematical function 

6. stop(e)
5. value[[3L]](cond)
4. tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]),
names[nh], parentenv, handlers[[nh]])
3. tryCatchList(expr, classes, parentenv, handlers)
2. tryCatch(expr = {
withCallingHandlers(expr = expr, warning = function(w) {
parent <- parent.env(environment())
parent$diag <- w ...
1. myCatch(expr = {
log("-1")
}, error = function(e) {
stop(e) ...

Since myCatch() was interrupted, it returns no value to be stored in output_, which leaves us with

Error: object 'output_4' not found

Disregarding simple warnings/errors in tryCatch()

I think you're looking for the difference between tryCatch, which catches a condition and continues evaluation from the environment where the tryCatch was defined, versus withCallingHandlers, which allows you to 'handle' a condition and then continue on from the location where the condition occurred. Take a look at warning (or the help page for warning, but that's less fun), especially the lines

    withRestarts({
.Internal(.signalCondition(cond, message, call))
.Internal(.dfltWarn(message, call))
}, muffleWarning = function() NULL)

This says -- signal a condtion, but insert a 'restart' where the condition was signaled from. Then you'd

withCallingHandlers({
warning("curves ahead")
2
}, warning = function(w) {
## what are you going to do with the warning?
message("warning occurred: ", conditionMessage(w))
invokeRestart("muffleWarning")
})

Although withCallingHandlers is often used with warnings and tryCatch with errors, there is nothing to stop one from 'handling' an error or catching a warning if that is the appropriate action.



Related Topics



Leave a reply



Submit