4

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?

Joachim Schork
  • 2,025
  • 3
  • 25
  • 48
  • @akrun Thank you for your response. The expected output is a character string object containing the character string 'Error: unexpected numeric constant in "5 5"'. – Joachim Schork Mar 12 '21 at 16:49
  • 1
    Could you tell us what you need this for? Isn’t this an [XY problem](https://meta.stackexchange.com/q/66377/1968)? – Konrad Rudolph Mar 12 '21 at 17:00
  • @KonradRudolph Thank you for getting back to me! 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. The main purpose is to illustrate my R code and its output on a website. I could copy/paste the error message manually. However, I'm trying to automatize this process. – Joachim Schork Mar 13 '21 at 09:32
  • 1
    Ah, thanks. Unfortunately I don’t see how this would be possible without replicating the logic of `stop` manually. It’s weird that R uses different formats for different types of error (syntax error, logic error that throws), otherwise you could just paste `'Error:'` in front of the `conditionMessage()`. – Konrad Rudolph Mar 13 '21 at 10:17
  • @KonradRudolph I agree, I find it very strange. Unfortunately, the more I research about it the more I'm afraid that it is not possible. Anyway, thank you very much for your help Konrad! – Joachim Schork Mar 13 '21 at 14:44

2 Answers2

2

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,

barboulotte
  • 395
  • 2
  • 8
  • 2
    It's more a limitation than a bug, reprex doesn't handle errors that happen at the parser level, `5 5` is not syntatic code. Following OP's explanations as to why he wants this, it would seem that reprex would be a good choice however, and that he'd be better of fixing the syntax of his code before running the reprex. – moodymudskipper Mar 17 '21 at 00:00
  • 1
    @Moody_Mudskipper Thank you for the comment. I updated the message. – barboulotte Mar 17 '21 at 05:18
  • 2
    @barboulotte Thank you for the response! This is a nice solution for the types of errors in the first part of your response. Unfortunately, I need a solution for all types of errors, so the `5 5` error is still problematic. – Joachim Schork Mar 17 '21 at 08:51
  • 1
    I updated my message with syntax test and @daroczig pander package to get error at parser level. This works also with R Markdown. – barboulotte Mar 21 '21 at 08:00
  • 1
    @barboulotte Thank you for the update. I see two remaining issues: 1) `test_eval("5 5")` still returns a slightly different error message. 2) I'm not able to save the output as character string (e.g. `x <- test_eval("5 5")`). – Joachim Schork Mar 22 '21 at 10:26
  • 1
    The idea is to use it with with reprex or R Markdown. You can try to modify the error message in the function. You can save the output of pander as character string and modify it. `x <- paste("Error:", pander::evals(text_in)[[1]]$msg$errors); ...; message(x)`. I also opened an issue with `pander`: https://github.com/Rapporter/pander/issues/349 – barboulotte Mar 22 '21 at 13:54
  • 1
    @barboulotte Ah ok I got the idea. I will check it later and let you know if it solved all parts of my problem. I'll assign the bounty points to your answer now, because the bounty is expiring in less than an hour and your answer is definitely the closest to what I was looking for. Thank you very much for all your effort and help! – Joachim Schork Mar 22 '21 at 15:20
  • Thank you for the bounty. Let know if you succeed. Let see if packages are updated. – barboulotte Mar 22 '21 at 17:47
  • @barboulotte I am now able to save the error messages with your code as character string. Unfortunately, there are still cases where the output is not exactly the same. For example, `test_eval("5 5")` is returning `at character 3 in line 1` in between the original error message. – Joachim Schork Mar 23 '21 at 10:37
  • 1
    Yes, it's the output of pander. I modified the error message in the function. – barboulotte Mar 23 '21 at 11:33
  • @barboulotte Sorry for the late response, and thank you for the updated code. I also hope that the pander package gets updated so that the exact console error is captured, since this would be a cleaner version. However, until then your workaround with the modified output looks fine. Thanks again for your help! – Joachim Schork Mar 29 '21 at 08:25
  • 1
    Maybe can you add a thumbs up reaction on the Github issues. – barboulotte Mar 30 '21 at 20:53
  • @barboulotte I think I cannot do that (maybe due to a too low user score, I'm not very active on GitHub). – Joachim Schork Apr 06 '21 at 06:24
1

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

Waldi
  • 39,242
  • 6
  • 30
  • 78
  • Thanks a lot, this is a great solution! I have one issue with your code when I'm dealing with error messages that have more than one line. For example, `writeLines("list()[[0]]",tmp)` is cut off after the first line of error message. Do you maybe know a way how to return the entire error message, no matter how many lines it has? Thanks again, Joachim – Joachim Schork Mar 16 '21 at 08:15
  • `err` contains all the information, try `print(err)`, see my edit – Waldi Mar 16 '21 at 08:24
  • Thanks for getting back to me! The problem is that I do not know in the forefront how long my error message will be, and I need to extract the entire error message automatically without manually printing the error output stored in err. So I would somehow have to determine automatically the number of vector elements that I have to extract from err to get the entire error message. Do you have an idea how to do that? – Joachim Schork Mar 16 '21 at 08:40
  • see my edit : I removed the last line of `err` which AFAIK is always `Execution stopped` – Waldi Mar 16 '21 at 08:47
  • For example, the error message produced by `writeLines("as.Date(10101)", tmp)` also returns `"Calls: as.Date -> as.Date.numeric"`, which is not returned, when I run the line of code manually. Furthermore, there are differences when error messages are returned by packages (e.g. ggplot2). If I run `writeLines("library('ggplot2'); data <- data.frame(x = LETTERS[1:5], y = c(3, 1, 6, 3, 5)); ggplot(data, aes(x, y)) + geom_point() + geom_line()", tmp)`, the message `Execution stopped` is not returned at all. Furthermore, the ggplot2 error is split into two lines, which should not be the case. – Joachim Schork Mar 16 '21 at 10:15
  • 1
    @Joachim, I gave the path for a possible solution to this tricky problem, but you'll probably have to handle specific cases like removing "Execution stopped" if it appears, etc... – Waldi Mar 16 '21 at 14:44
  • 1
    OK I see, thank you very much for your help! This is definitely much closer to the final solution compared to what I had before. Thanks a lot for your time! – Joachim Schork Mar 16 '21 at 15:23