Jeroen Ooms
Jeroen Ooms

Reputation: 32988

Logging console history with errors in R or Rstudio

For educational purposes we are logging all commands that students type in the rstudio console during labs. In addition we would like to store if call was successful or raised an error, to identify students which struggling to get the syntax right.

The best I can come up with is something like this:

options(error = function(){
  timestamp("USER ERROR", quiet = TRUE)
})

This adds an ## ERROR comment on the history log when an exception occurs. Thereby we could analyze history files to see which commands were followed by an ## ERROR comment.

However R's internal history system is not well suited for logging because it is in-memory, limited size and needs to be stored manually with savehistory(). Also I would prefer to store log one-line-per-call, i.e. escape linebreaks for multi-line commands.

Is there perhaps a hook or in the R or RStudio console for logging actual executed commands? That would allow me to insert each evaluated expression (and error) in a database along with a username and timestamp.

Upvotes: 3

Views: 1487

Answers (1)

Joshua Ulrich
Joshua Ulrich

Reputation: 176718

A possible solution would be to use addTaskCallback or the taskCallbackManager with a function that writes each top-level command to your database. The callback will only fire on the successful completion of a command, so you would still need to call a logging function on an error.

# error handler
logErr <- function() {
  # turn logging callback off while we process errors separately
  tcbm$suspend(TRUE)
  # turn them back on when we're done
  on.exit(tcbm$suspend(FALSE))
  sc <- sys.calls()
  sclen <- length(sc)  # last call is this function call
  if(sclen > 1L) {
    cat("myError:\n", do.call(paste, c(lapply(sc[-sclen], deparse), sep="\n")), "\n")
  } else {
    # syntax error, so no call stack
    # show the last line entered
    # (this won't be helpful if it's a parse error in a function)
    file1 <- tempfile("Rrawhist")
    savehistory(file1)
    rawhist <- readLines(file1)
    unlink(file1)
    cat("myError:\n", rawhist[length(rawhist)], "\n")
  }
}
options(error=logErr)
# top-level callback handler
log <- function(expr, value, ok, visible) {
  cat(deparse(expr), "\n")
  TRUE
}
tcbm <- taskCallbackManager()
tcbm$add(log, name = "log")

This isn't a complete solution, but I hope it gives you enough to get started. Here's an example of what the output looks like.

> f <- function() stop("error")
f <- function() stop("error") 
> hi
Error: object 'hi' not found
myError:
 hi 
> f()
Error in f() : error
myError:
 f()
stop("error") 

Upvotes: 2

Related Questions