Capture Error Messages as Character String in R

Viewed 744

I would like to capture error messages that are returned by my R code in a character string.

This thread explains how to capture error messages of functions. However, when I try to capture the error messages outside of functions, the error message is formatted differently.

Consider the following example R code:

5 5
# Error: unexpected numeric constant in "5 5"

If I execute the previous R code, the RStudio console returns the error message Error: unexpected numeric constant in "5 5".

However, if I try to capture this output, the error message looks differently:

library("evaluate")

evaluate("5 5")[[2]]
# <simpleError: <text>:1:3: unexpected numeric constant
# 1: 5 5
# ^>

My expected output is a data object containing the following character string:

my_output <- 'Error: unexpected numeric constant in "5 5"'
my_output
# [1] "Error: unexpected numeric constant in \"5 5\""

Question: How could I save the error of an R code as a character string?

2 Answers

I try to write a function that updates my R code so that all RStudio console output is shown as a comment within the code.

I think the reprex package and R Markdown are doing what you want.

You can test the syntax and combine with pander package to deal with errors that occur at the parser level. pander does not produce the exact console error but works with reprex and R markdown.

with reprex package

test_eval <- function(text_in){
  if(class(try(parse(text = text_in),silent=TRUE)) == "expression") {
    eval(parse(text = text_in))
  } else {
    x <- pander::evals(text_in)[[1]]$msg$errors
    x <- paste0(tolower(substr(x, 1, 1)), substr(x, 2, nchar(x)))
    x <- paste("Error:", x)
    x <- qdapRegex::rm_between(x, "at", ":", extract=FALSE, replacement="in")
    x <- gsub("` ", "\"", x)
    x <- gsub("`", "\"", x)
    message(x)
  }
}

test_eval("5 5")
#> Error: unexpected numeric constant in "5 5"
test_eval("\"a\" \"a\"")
#> Error: unexpected string constant in ""a" "a""
test_eval("head(iris)")
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1          5.1         3.5          1.4         0.2  setosa
#> 2          4.9         3.0          1.4         0.2  setosa
#> 3          4.7         3.2          1.3         0.2  setosa
#> 4          4.6         3.1          1.5         0.2  setosa
#> 5          5.0         3.6          1.4         0.2  setosa
#> 6          5.4         3.9          1.7         0.4  setosa
test_eval("list()[[0]]")
#> Error in list()[[0]]: attempt to select less than one element in get1index <real>
test_eval("as.Date(10101)")
#> Error in as.Date.numeric(10101): 'origin' must be supplied
test_eval("library('ggplot2')")
test_eval("data <- data.frame(x = LETTERS[1:5], y = c(3, 1, 6, 3, 5))")
test_eval("ggplot(data, aes(x, y)) + geom_point() + geom_line()")
#> Error:   You're passing a function as global data.
#>   Have you misspelled the `data` argument in `ggplot()`

with R Markdown

---
title: Test
output:
  html_document: default
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

```{r, collapse=TRUE, error=TRUE}
test_eval <- function(text_in){
  if(class(try(parse(text = text_in),silent=TRUE)) == "expression") {
    eval(parse(text = text_in))
  } else {
    message(paste("Error:", pander::evals(text_in)[[1]]$msg$errors))
  }
}

test_eval("5 5")
test_eval("a a")
test_eval("head(iris)")
test_eval("list()[[0]]")
test_eval("as.Date(10101)")
test_eval("library('ggplot2')")
test_eval("data <- data.frame(x = LETTERS[1:5], y = c(3, 1, 6, 3, 5))")
test_eval("ggplot(data, aes(x, y)) + geom_point() + geom_line()")
```
test_eval <- function(text_in){
  if(class(try(parse(text = text_in),silent=TRUE)) == "expression") {
    eval(parse(text = text_in))
  } else {
    x <- pander::evals(text_in)[[1]]$msg$errors
    x <- paste0(tolower(substr(x, 1, 1)), substr(x, 2, nchar(x)))
    x <- paste("Error:", x)
    x <- qdapRegex::rm_between(x, "at", ":", extract=FALSE, replacement="in")
    x <- gsub("` ", "\"", x)
    x <- gsub("`", "\"", x)
    message(x)
  }
}

test_eval("5 5")
## Error: unexpected numeric constant in "5 5"
test_eval("\"a\" \"a\"")
## Error: unexpected string constant in ""a" "a""
test_eval("head(iris)")
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa
test_eval("list()[[0]]")
## Error in list()[[0]]: attempt to select less than one element in get1index <real>
test_eval("as.Date(10101)")
## Error in as.Date.numeric(10101): 'origin' must be supplied
test_eval("library('ggplot2')")
test_eval("data <- data.frame(x = LETTERS[1:5], y = c(3, 1, 6, 3, 5))")
test_eval("ggplot(data, aes(x, y)) + geom_point() + geom_line()")
## Error:   You're passing a function as global data.
##   Have you misspelled the `data` argument in `ggplot()`

reprexpackage and R Markdown are using evaluate package. Maybe the test could be done in this package. New issue on Github: https://github.com/r-lib/evaluate/issues/101.

Also opened an issue with pander: https://github.com/Rapporter/pander/issues/349.

Regards,

You could write the code in a temporary file and read it using Rscript through a system call.
The intern option allows to capture the output :

tmp <- tempfile()
on.exit(unlink(tmp))
writeLines("5 5",tmp)

err <- suppressWarnings(system(paste('RScript ',tmp),intern = TRUE))
err[1:length(err)-1]
#[1] "Error : unexpected numeric constant in \"5 5\""

print(err)
#[1] "Error : unexpected numeric constant in \"5 5\""
#[2] "Execution stopped"                              
#attr(,"status")
#[1] 1

Related