3

In previous versions of knitr, I was able to set the chunk option message = FALSE and it would both have the documented feature of preventing messages from going to the text document (markdown in this case) and would also have the undocumented feature of disabling the interception of messages, allowing me to capture messages within the chunk to reformat and output to markdown as I wished. With the new version of knitr (1.43), this no longer seems to work and thus has broken a fair amount of my report generation code. Setting message = FALSE now seems to result in messages being intercepted without allowing code within the chunk to capture them with functions like capture.output(). My workaround has been to write functions in chunks that I can use to capture messages in inline R code, where any messages generated seem to be left alone by knitr. This makes for pretty sloppy code and is less than convenient, and it seems to me like knitr should retain a feature allowing me to tell a chunk to ignore message and error reporting and let me do with them as I please in terms of reformatting them and sending them to the text document. I am not sure this would be considered a bug, but it does seem like an undocumented feature change (or at least I cannot find the documentation of the desired behavior).

Anyone know of a more predictable and stable way to prevent knitr from interfering with custom message and error management within chunks? (I know that I can use inline code, but for reasons I don't want to detail here, I would rather use chunks.)

In old versions of knitr, setting the chunk option message = FALSE used to allow me to capture messages within the chunk and format them for output myself. I was expecting this to continue to work with new versions of knitr.

  • 2
    `capture.output` is fundamentally the wrong approach for capturing *messages*. R has a better mechanism for that, via `withCallingHandlers`. I don’t know whether that works in knitr with `message = FALSE`, but if it doesn’t work that would be worth reporting as an issue to the package maintainer. – Konrad Rudolph Jul 06 '23 at 13:56
  • Thanks! Use of `withCallingHandlers` to capture the messages does not have the same problem. What's a little embarrassing is that I was already using that function to capture warnings, so all I had to do was add a message argument to my existing call. If you get a chance, I wouldn't mind a quick clarification on why use of capture.output is fundamentally wrong. Though it is at least empirically clear that there is something inconsistent about how it operates that is triggered by use in a chunk, so I am inclined to believe you. – Robert Payn Jul 06 '23 at 14:45
  • 1
    “fundamentally wrong” simply because it does not just capture `message`s, it indiscriminately captures *all* output to stdout and stderr. So every `cat`, `writeLines`, `print`, … call going to the standard output/error streams would be captured inside a single string vector, without any way of distinguishing their provenance. – Konrad Rudolph Jul 06 '23 at 14:55
  • 1
    It's academic at this point, but the `type` argument to `capture.output()` (which is ultimately passed to `sink()`) can be set to `"messages"` to discriminate capture of only the messages stream. This was the configuration I was using and it seemed to work outside of code chunks in knitr and it worked in code chunks in older versions of knitr. However, I'm not going to argue against `withCallingHandlers()` being the better option for my needs (and perhaps the majority of use cases) and seems to play nicer with however knitr sets up the environment for evaluation of R code in chunks. – Robert Payn Jul 06 '23 at 16:53
  • Thanks for your time in clarification. I think it turned me toward the root of the problem. The `capture.output()` function tries to read the messages stream that knitr takes over in chunks, but the `withCallingHandlers()` doesn't care about the messages output stream and watches for the message events directly. – Robert Payn Jul 06 '23 at 16:54
  • Ah, I had forgotten about that parameter. Yeah, with that it _should_ work, of course. But using the condition mechanism provided by R is still conceptually cleaner, I’d argue (purely academically). `sink` (via `capture.output`) messes around with the deep internals of the standard streams at the C level, and I’m not surprised that this would interfere with knitr’s handling (as you surmise as well). – Konrad Rudolph Jul 06 '23 at 17:01
  • `message = FALSE` in old versions of knitr is equivalent to `message = NA` in new versions: https://yihui.org/en/2022/12/message-false/ – Yihui Xie Jul 08 '23 at 07:01

1 Answers1

1

As noted in the comments of the original question, Konrad Rudolph suggested what I suspect is a robust solution to use withCallingHandlers rather than capture.output to intercept messages. I tried it and this seems to work fine in a knitr chunk.

See comments above, but I suspect this is because capture.output() tries to read the messages output stream (when argument type = "message"), but the withCallingHandlers() function does not care about the output streams and just watches for the message events directly.