R: Catch Errors and Continue Execution After Logging The Stacktrace (No Traceback Available with Trycatch)

Save traceback on error using tryCatch

It turns out that the latest version of the evaluate package has a function called try_capture_stack which is a pretty good implementation of this.

get stack trace on tryCatch'ed error in R

For interactive use one might trace(stop, quote(print(sys.calls()))) to print the call stack at the time stop() is invoked.

From ?tryCatch,

 The function 'tryCatch' evaluates its expression argument in a
context where the handlers provided in the '...' argument are
available.

whereas

 Calling handlers are established by 'withCallingHandlers'...
the handler is called... in the context where the condition
was signaled...

so

>     withCallingHandlers(x(), error=function(e) print(sys.calls()))
[[1]]
withCallingHandlers(x(), error = function(e) print(sys.calls()))

[[2]]
x()

[[3]]
y()

[[4]]
z()

[[5]]
stop("asdf")

[[6]]
.handleSimpleError(function (e)
print(sys.calls()), "asdf", quote(z()))

[[7]]
h(simpleError(msg, call))

Error in z() : asdf

This is thwarted if there is an inner tryCatch

withCallingHandlers({
tryCatch(x(), error=function(e) stop("oops"))
}, error=function(e) print(sys.calls()))

as we only have access to the call stack after the tryCatch has 'handled' the error.

Printing stack trace and continuing after error occurs in R

I ended up writing a general-purpose logger that produces Java-like logging messages when the standard R "message", "warning" and "stop" methods are called. It includes timestamps, and stack traces for warnings and above.

Many thanks to Man Group for permission to distribute this! Thanks also to Bob Albright, whose answer gave me a leg-up to what I was looking for.

withJavaLogging = function(expr, silentSuccess=FALSE, stopIsFatal=TRUE) {
hasFailed = FALSE
messages = list()
warnings = list()
logger = function(obj) {
# Change behaviour based on type of message
level = sapply(class(obj), switch, debug="DEBUG", message="INFO", warning="WARN", caughtError = "ERROR",
error=if (stopIsFatal) "FATAL" else "ERROR", "")
level = c(level[level != ""], "ERROR")[1]
simpleMessage = switch(level, DEBUG=,INFO=TRUE, FALSE)
quashable = switch(level, DEBUG=,INFO=,WARN=TRUE, FALSE)

# Format message
time = format(Sys.time(), "%Y-%m-%d %H:%M:%OS3")
txt = conditionMessage(obj)
if (!simpleMessage) txt = paste(txt, "\n", sep="")
msg = paste(time, level, txt, sep=" ")
calls = sys.calls()
calls = calls[1:length(calls)-1]
trace = limitedLabels(c(calls, attr(obj, "calls")))
if (!simpleMessage && length(trace) > 0) {
trace = trace[length(trace):1]
msg = paste(msg, " ", paste("at", trace, collapse="\n "), "\n", sep="")
}

# Output message
if (silentSuccess && !hasFailed && quashable) {
messages <<- append(messages, msg)
if (level == "WARN") warnings <<- append(warnings, msg)
} else {
if (silentSuccess && !hasFailed) {
cat(paste(messages, collapse=""))
hasFailed <<- TRUE
}
cat(msg)
}

# Muffle any redundant output of the same message
optionalRestart = function(r) { res = findRestart(r); if (!is.null(res)) invokeRestart(res) }
optionalRestart("muffleMessage")
optionalRestart("muffleWarning")
}
vexpr = withCallingHandlers(withVisible(expr),
debug=logger, message=logger, warning=logger, caughtError=logger, error=logger)
if (silentSuccess && !hasFailed) {
cat(paste(warnings, collapse=""))
}
if (vexpr$visible) vexpr$value else invisible(vexpr$value)
}

To use it, just wrap it around your code:

withJavaLogging({
// Your code here...
})

For a quieter output in the absence of errors (useful for tests!), set the silentSuccess flag. Messages will only be output if an error occurs, to give context to the failure.

To achieve the original goal (dump stack trace + carry on), just use try:

try(withJavaLogging({
// Your code here...
}, stopIsFatal=FALSE))

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

How to continue function when error is thrown in withCallingHandlers in R

You can just wrap each call to testingFunction with a call to tryCatch.:

counter <- 0
testForExpectedError <- function(expr) {
tryCatch(expr, error=function(err) {
print(paste(err$message))
if(err$message == paste("The function should throw this error message",
"at the right time.")){
counter <<- counter + 1
}
})
}

testForExpectedError(testingFunction(df0, df1))
testForExpectedError(testingFunction(df2, df3))
testForExpectedError(testingFunction(df4, df5))

stopifnot(counter == 2)


Related Topics



Leave a reply



Submit