Search code examples
rsink

capture warnings with capture.output


I am having problems using capture.output() and I can't figure out why, since it is for the most part just a wrapper for sink().

Consider this trivial example using sink():

foo = function() warning("foo")

f = file()
sink(f, type = "message")
foo()
readLines(f)
## [1] "Warning message:" "In foo() : foo"  
close(f)

This works as expected. however, capture.output() does not :

f = file()
capture.output(foo(), file = f, type = "message")
## Warning message:
## In foo() : foo
readLines(f)
## character(0)
close(f)

capture.output() does work for messages though:

bar = function() message("bar")
f = file()
capture.output(bar(), file = f, type = "message")
readLines(f)
## [1] "bar"
close(f)

But according to the documentation both messages and warnings should be captured:

Messages sent to stderr() (including those from message, warning and stop) are captured by type = "message".

What am I missing here?


Solution

  • @MrFlick's comment points to a potential solution, provide you have control over the arguments that are passed to warning(). If you use the argument immediate. = TRUE then capture.output() can retrieve the warning message.

    baz = function() warning("baz", immediate. = TRUE)
    res = capture.output(baz(), type = "message")
    print(res)
    ## [1] "Warning in baz() : baz"
    

    EDIT

    Alternatively, @user2554330 points out you can use options(warn = 1) to globally make warnings print immediately.

    oldopt = getOption("warn")
    options(warn = 1)
    res = capture.output(foo(), type = "message")
    print(res)
    ## [1] "Warning in foo() : foo"
    options(warn = oldopt)
    

    EDIT 2

    For completeness, I think it's helpful to point out this alternative approach using withCallingHandlers, which does not require any changes to options and may be a cleaner solution depending on the application. Consider this example of nested warnings:

    foo = function() {
      warning("foo")
      bar()
    }
    bar = function() { 
      warning("bar")
      baz()
    }
    baz = function() {
      warning("baz")
      TRUE
    }
    # create a container to hold warning messages
    logs = vector("character")
    # function to capture warning messages
    log_fun = function(w) logs <<- append(logs, w$message)
    # function call with message capturing
    withCallingHandlers(foo(), warning = log_fun)
    ## [1] TRUE
    ## Warning messages:
    ## 1: In foo() : foo
    ## 2: In bar() : bar
    ## 3: In baz() : baz
    print(logs)
    ## [1] "foo" "bar" "baz"
    

    Note that withCallingHandlers allows you to specify different behavior for different signal conditions, e.g. warnings and messages could be stored in separate variables.