Summary
A minimal yet realistic failure: rlang::inform() messages are not captured by a simple sink() call because they are sent to the message output stream, not the standard output stream. By redirecting both streams—type = "output" for print()/cat() and type = "message" for rlang::inform()/warning()/abort()—unit tests and logging frameworks can capture the full user‑visible output.
Root Cause
rlang::inform()(and the family of rlang messaging functions) write to the message connection (message()/cat(..., file = stderr())internally).sink()without atypeargument redirects standard output (stdout).- Therefore, a plain
sink()only capturesprint(),cat(), etc., while messages emitted byrlangbypass it. - The error “file must be NULL or an already open connection” appears when attempting
sink(file="Sunk.txt", type="message")becausesink()expects a file name or a pre‑opened connection; passing a string directly for the message stream is disallowed.
Why This Happens in Real Systems
- Separation of concerns: stdout is for regular program output; stderr (message stream) is for diagnostics, warnings, and informative prompts.
- Interactivity and tooling: IDEs and REPLs capture stderr separately to provide colored warnings or interrupt controls.
- Testing hygiene: Keeping diagnostics on stderr prevents accidental consumption by consumers that parse stdout.
Real-World Impact
- Unit tests that rely on
sink()to capture all output will miss useful information fromrlang::inform(). - Debugging prints may be silently discarded, leading to confusion when diagnostics appear on the console but not in log files.
- CI pipelines that redirect logs may contain incomplete traces, making reproducibility harder.
Example or Code (if necessary and relevant)
library(rlang)
HelloWorld <- function() {
rlang::inform(message = "Hello, World!")
}
# Correct capture of both output and message streams
fout <- file("Sunk.txt", open = "wt")
# Redirect standard output (stdout)
sink(fout, type = "output")
# Redirect message stream (stderr)
sink(fout, type = "message")
HelloWorld()
# Stop redirecting
sink(type = "output")
sink(type = "message")
close(fout)
The file Sunk.txt now contains:
Hello, World!
How Senior Engineers Fix It
- Explicitly redirect both streams in test harnesses or logging wrappers.
- Use helper functions to encapsulate the pattern:
redirect_all <- function(fname) { f <- file(fname, open = "wt") sink(f, type = "output") sink(f, type = "message") invisible(f) }
close_all <- function(f) {
sink(type = “output”)
sink(type = “message”)
close(f)
}
3. For production logging, prefer a dedicated logging package (e.g., `crayon`, `logging`) that handles both streams gracefully.
4. When writing reusable utilities, document the need to capture the *message* stream.
## Why Juniors Miss It
- **Assumption of universal capture**: They believe a single `sink()` suffices for all user‑visible output.
- **Ignorance of R’s I/O streams**: Many new developers are unaware that R separates stdout and stderr internally.
- **Overreliance on ad‑hoc examples**: Encountering `print()`/`cat()` examples, they ignore that `rlang` functions solve a different problem (muffling, warnings).
- **Limited error handling**: The cryptic sink error message may discourage deeper investigation.
By clarifying the distinction between output streams and providing a concise pattern to capture both, senior engineers prevent silent loss of diagnostic information in real systems.