Search code examples
rerror-handling

How to get the last error


I can get the last calculated value using .Last.value

2 + 2
## [1] 4
.Last.value
## [1] 4

I can access the latest warnings using last.warning.

warning("!!!")
## Warning message:
## !!! 
last.warning
## $`!!!`
##  NULL

I can get the text of the latest error message using geterrmessage()

stop("!!!")
## Error: !!!
geterrmessage()
## [1] "Error: !!!\n"

If I know in advance that an error might be thrown I can use tryCatch to return the error object.

tryCatch(stop("!!!"), error = identity)
## <simpleError in doTryCatch(return(expr), name, parentenv, handler): !!!>

How can I retrieve the last error as an object, after it has been thrown?

For example, if I type

stop("!!!")

Then I want to be able to type getlasterror() or similar and retrieve a simpleError object.


Solution

  • The modern way

    In cases where you are the author of the code, the modern way to solve this is to use the rlang package. You throw errors using abort(), then retrieve the last error with last_error().

    library(rlang)
    f <- function() {
      abort("@@@")
    }
    f()
    ## Error: @@@
    last_error()
    ## <error>
    ## message: @@@
    ## class:   `rlang_error`
    ## backtrace:
    ##  ─base::withCallingHandlers(...)
    ##  ─global::f()
    ## Call `summary(rlang::last_error())` to see the full backtrace
    g <- function() {
      msg <- "###"
      abort(msg)
    }
    g()
    ## Error: ###
    last_error()
    ## <error>
    ## message: ###
    ## class:   `rlang_error`
    ## backtrace:
    ##  ─base::withCallingHandlers(...)
    ##  ─global::g()
    ## Call `summary(rlang::last_error())` to see the full backtrace
    h <- function() {
      err_fn <- abort
      err_fn("$$$")
    }
    h()
    ## Error: $$$
    last_error()
    ## <error>
    ## message: $$$
    ## class:   `rlang_error`
    ## backtrace:
    ##  ─base::withCallingHandlers(...)
    ##  ─global::h()
    ## Call `summary(rlang::last_error())` to see the full backtrace
    

    The limitation of this is that it doesn't work with errors generated by stop().


    The original answer

    Based upon David Arenburg's comment, you can use tryCatch() in conjunction with the traceback.

    get_last_error <- function()
    {
      tr <- .traceback()
      if(length(tr) == 0)
      {
        return(NULL)
      }
      tryCatch(eval(parse(text = tr[[1]])), error = identity)
    }
    

    Examples:

    # before an error is thrown
    get_last_error()
    ## NULL
    
    # after an error at the top level
    stop("!!!")
    ## Error: !!!
    get_last_error()
    ## <simpleError in eval(expr, envir, enclos): !!!>
    
    # after an error inside a function
    f <- function() stop("@@@")
    f()
    ## Error in f() : @@@
    get_last_error()
    ## <simpleError in eval(expr, envir, enclos): @@@>
    

    A limitation:

    When you re-evaluate the error code, all the variables need to be available. So the following examples don't work, for example:

    g <- function()
    {
      msg <- "###"
      stop(msg)
    }
    
    g()
    ## Error in g() : ###
    get_last_error()
    ## <simpleError in stop(msg): object 'msg' not found>
    
    h <- function()
    {
      err_fn <- stop
      err_fn("$$$")
    }
    
    h()
    ## Error in h() : $$$
    get_last_error()
    ## <simpleError in eval(expr, envir, enclos): could not find function "err_fn">
    

    By setting options(error = dump.frames), the call stack at the time of an error is stored in a variable named last.dump in the global environment. The environment last.dump[length(last.dump)] sometimes contains the error object, and sometimes contains the arguments that would create the error.

    debugger(last.dump) allows interactive post-mortem exploration of the stack, as an alternative to having the error object.