8

Displaying error locations with options(show.error.locations = TRUE) doesn't seem to work when handling exceptions with tryCatch. I am trying to display location of the error but I don't know how:

options(show.error.locations = TRUE)

tryCatch({
    some_function(...)
}, error = function (e, f, g) {
    e <<- e
    cat("ERROR: ", e$message, "\nin ")
    print(e$call) 
})

If I then look at the variable e, the location doesn't seem to be there:

> str(e)
List of 2
 $ message: chr "missing value where TRUE/FALSE needed"
 $ call   : language if (index_smooth == "INDEX") {     rescale <- 100/meanMSI[plotbaseyear] ...
 - attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

If I don't trap the error, it is printed on the console along with source file and line number. How to do it with tryCatch?

Tomas
  • 57,621
  • 49
  • 238
  • 373

2 Answers2

4

Context

As noted by Willem van Doesburg, it is not possible to use the traceback() function to display where the error occured with tryCatch(), and to my knowledge there is currently no practical way to store the position of the error with base functions in R while using tryCatch .

The idea of a separate error handler

The possible solution I found consists of two parts, the main one is writing an error handler similar to that of Chrispy from "printing stack trace and continuing after error occurs in R" which produces a log with the position of the error. The second part is capturing this output into a variable, similarly to what was suggested by Ben Bolker in "is it possible to redirect console output to a variable".

The call stack in R seems to be purged when an error is raised and then handled (I might be wrong so any information is welcomed), hence we need to capture the error while it is occuring.

Script with an error

I used an example from one of your previous questions regarding where and R error occured with the following function stored in a file called "TestError.R" which I call in my example bellow:

# TestError.R
f2 <- function(x)
{
    if (is.null(x)) "x is Null"
    if (x==1) "foo"
}

f <- function(x)
{
  f2(x)
}


# The following line will raise an error if executed
f(NULL)

Error tracing function

This is the function I adapted form Chrispy's code as I mentionned above. Upon execution, if an error is raised, the code underneath will print where the error occured, in the case of the above function, it will print : "Error occuring: Test.R#9: f2(x)" and "Error occuring: Test.R#14: f(NULL)" meaning the error result from a trouble with the f(NULL) function at line 14 which references the f2() function at line 9

# Error tracing function
withErrorTracing = function(expr, silentSuccess=FALSE) {
    hasFailed = FALSE
    messages = list()
    warnings = list()

    errorTracer = function(obj) {

        # Storing the call stack 
        calls = sys.calls()
        calls = calls[1:length(calls)-1]
        # Keeping the calls only
        trace = limitedLabels(c(calls, attr(obj, "calls")))

        # Printing the 2nd and 3rd traces that contain the line where the error occured
        # This is the part you might want to edit to suit your needs
        print(paste0("Error occuring: ", trace[length(trace):1][2:3]))

        # 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),  error=errorTracer)
    if (silentSuccess && !hasFailed) {
        cat(paste(warnings, collapse=""))
    }
    if (vexpr$visible) vexpr$value else invisible(vexpr$value)
}

Storing the error position and the message

We call the script TestError.R above and capture the printed output in a variable, here called errorStorage with which we can deal later on or simply display.

errorStorage <- capture.output(tryCatch({
    withErrorTracing({source("TestError.R")})
    }, error = function(e){
        e <<- e
        cat("ERROR: ", e$message, "\nin ")
        print(e$call)
}))

Hence we keep the value of e with the call and message as well as the position of the error location. The errorStorage output should be as follow:

[1] "[1] \"Error occuring: Test.R#9: f2(x)\"    \"Error occuring: Test.R#14: f(NULL)\""
[2] "ERROR:  argument is of length zero "                                        
[3] "in if (x == 1) \"foo\""

Hoping this might help.

Community
  • 1
  • 1
  • Excellent code snippet :-) I just want to add that you can see the file names and line numbers in the call stack only if you enable this via two options (for packages you even have to do this **before** you install them): `options(keep.source = TRUE); options(keep.source.pkgs = TRUE)`. For details the [FAQ of the package `tryCatchLog`](https://github.com/aryoda/tryCatchLog#the-stack-trace-does-not-contain-r-file-names-and-line-number-for-my-packages-how-can-i-enable-this) – R Yoda Jan 10 '21 at 08:46
0

You can use traceback() in the error handler to show the call stack. Errors in a tryCatch don't produce line numbers. See also the help on traceback. If you use your tryCatch statements defensively, this will help you narrow down the location of the error.

Here is a working example:

## Example of Showing line-number in Try Catch

# set this variable to "error", "warning" or empty ('') to see the different scenarios
case <- "error"

result <- "init value"

tryCatch({

  if( case == "error") {
    stop( simpleError("Whoops:  error") )
  }

  if( case == "warning") {
    stop( simpleWarning("Whoops:  warning") )
  }

  result <- "My result"
},
warning = function (e) {
  print(sprintf("caught Warning: %s", e))
  traceback(1, max.lines = 1)
},
error = function(e) {
  print(sprintf("caught Error: %s", e))
  traceback(1, max.lines = 1)
},
finally = {
  print(sprintf("And the result is: %s", result))
})
  • 1
    I don't quite get it, "By default `traceback()` prints the call stack of the last uncaught error" as is explained in the [documentation of R](https://stat.ethz.ch/R-manual/R-devel/library/base/html/traceback.html), so if we catch the error with `tryCatch`, using `traceback` would not lead us to the line where the error occured (tried it calling a script with an error on purpose on line 14 and it didn't show it). – Pierre Chevallier Nov 18 '16 at 08:48
  • 1
    Indeed, as I mentioned errors inside a `tryCatch()` don't produce a traceback. by calling `traceback()`in the Catch part you at least get the line number of the catch clause. That is unfortunately the best possible result from my understanding. – Willem van Doesburg Nov 18 '16 at 09:18
  • In that case unfortunately *this is not an answer to the question*. Thanks @PierreChevallier – Tomas Nov 18 '16 at 10:55