hadley / adv-r

Advanced R: a book
http://adv-r.hadley.nz
Other
2.36k stars 1.71k forks source link

traceback for rendering R Markdown non-interactively no longer works #1792

Open kelly-sovacool opened 1 week ago

kelly-sovacool commented 1 week ago

It used to be possible to get an informative traceback when rendering R Markdown non-interactively, where the traceback would only show function calls for code within the R Markdown notebook, as long as the notebook contained this code from the Debugging chapter:

options(rlang_trace_top_env = rlang::current_env())
options(error = function() {
    sink()
    print(rlang::trace_back(bottom = sys.frame(-1)), simplify = "none")
})

However, this behavior seems to have gone away with newer versions of R, knitr, rmarkdown, and rlang, and instead the traceback contains numerous knitr & rmarkdown function calls but nothing from the R Markdown source file itself.

example

Here's a simple notebook.Rmd that uses the above code and throws an error:

---
title: "example notebook"
output:
    html_document
params:
    to_error: true
---

```{r setup}
options(rlang_trace_top_env = rlang::current_env())
options(error = function() {
    sink()
    print(rlang::trace_back(bottom = sys.frame(-1)), simplify = "none")
})
knitr::opts_chunk$set(message = FALSE)
throw_error <- function(to_error) {
    func(to_error)
}
func <- function(to_error) {
    if (to_error) stop('ERROR')
}
message(paste("to_error:", params$to_error, "(", class(params$to_error), ")"))
throw_error(params$to_error)

Rendered from a terminal with:

```sh
R -e "rmarkdown::render('notebook.Rmd')"

where the global error option is NULL:

getOption('error')

NULL

older versions

With older versions:

The output is helpful, only showing code from the R Markdown file in the traceback:

processing file: notebook.Rmd
  |..................                                                    |  25%
  ordinary text without R code

  |...................................                                   |  50%
label: setup
  |....................................................                  |  75%
  ordinary text without R code

  |......................................................................| 100%
label: error
to_error: TRUE ( logical )
Quitting from lines 19-27 (notebook.Rmd) 
Error in func(to_error) : ERROR
Calls: <Anonymous> ... withCallingHandlers -> withVisible -> eval -> eval -> throw_error -> func
    █
 1. └─global::throw_error(params$to_error)
 2.   └─global::func(to_error)
 3.     └─base::stop("ERROR")

Warning message:
In sink() : no sink to remove

newer versions

But with newer versions:

The output is extremely verbose and includes rmarkdown & knitr calls in the stack, but crucially doesn't include underlying code from the R Markdown file.

processing file: notebook.Rmd
  |..............................................................| 100% [error]Error in `func()`:
! ERROR
Backtrace:
  1. rmarkdown::render("notebook.Rmd")
  2. knitr::knit(knit_input, knit_output, envir = envir, quiet = quiet)
  3. knitr:::process_file(text, output)
  6. knitr:::process_group(group)
  7. knitr:::call_block(x)
     ...
 14. base::withRestarts(...)
 15. base (local) withRestartList(expr, restarts)
 16. base (local) withOneRestart(withRestartList(expr, restarts[-nr]), restarts[[nr]])
 17. base (local) docall(restart$handler, restartArgs)
 19. evaluate (local) fun(base::quote(`<smplErrr>`))
     ▆
  1. ├─rmarkdown::render("notebook.Rmd")
  2. │ └─knitr::knit(knit_input, knit_output, envir = envir, quiet = quiet)
  3. │   └─knitr:::process_file(text, output)
  4. │     ├─xfun:::handle_error(...)
  5. │     ├─base::withCallingHandlers(...)
  6. │     └─knitr:::process_group(group)
  7. │       └─knitr:::call_block(x)
  8. │         └─knitr:::block_exec(params)
  9. │           └─knitr:::eng_r(options)
 10. │             ├─knitr:::in_input_dir(...)
 11. │             │ └─knitr:::in_dir(input_dir(), expr)
 12. │             └─knitr (local) evaluate(...)
 13. │               └─evaluate::evaluate(...)
 14. │                 └─base::withRestarts(...)
 15. │                   └─base (local) withRestartList(expr, restarts)
 16. │                     └─base (local) withOneRestart(withRestartList(expr, restarts[-nr]), restarts[[nr]])
 17. │                       └─base (local) docall(restart$handler, restartArgs)
 18. │                         ├─base::do.call("fun", lapply(args, enquote))
 19. │                         └─evaluate (local) fun(base::quote(`<smplErrr>`))
 20. │                           └─base::signalCondition(cnd)
 21. └─knitr (local) `<fn>`(`<smplErrr>`)
 22.   └─rlang::entrace(e)
 23.     └─rlang::cnd_signal(entraced)
 24.       └─rlang:::signal_abort(cnd)
 25.         └─base::stop(fallback)

Quitting from lines 19-27 [error] (notebook.Rmd)

Did something regress in one or some of these newer package versions? Or am I overlooking something about the R environment? How can I get a traceback showing only code from the R Markdown file, but using these newer versions of R, rmarkdown, knitr, and rlang?