reportr: Message reporting for R

The reportr package for R is a simple alternative to R’s standard functions for producing informative output, such as message(), warning() and stop(). It offers a certain amount more flexibility than these functions, although it is not (yet) a full logging solution. The package is available on CRAN.

Further alternatives in this area include the futile.logger, log4r and logging packages.

Contents

Usage

The key functions in the package are report() and flag(). Both take a reporting level as their first argument, which determines the priority of the message. The functions differ only in that report() reports the message to the console immediately, whereas flag(), like warning(), saves it for reporting later. Flagged messages are reported at the next report() call, to keep output in order.

The setOutputLevel() function determines the minimal reporting level for which output is actually generated; messages below this level are discarded. This is demonstrated below, by creating a message of level Info with the output level set first to Warning (which produces nothing) and then to Info (which allows the message to be reported).

setOutputLevel(Warning)
report(Info, "Test message")

setOutputLevel(Info)
report(Info, "Test message")
## INFO: Test message

The reporting levels available, in ascending order of priority, are currently Debug, Verbose, Info, Warning, Question, Error and Fatal. The Error level raises R’s “abort” condition, like stop(), so execution will stop. The Fatal level is in practice never used, but can be set as the output level to subdue all reporting.

Using reportr to handle standard messages

It is possible to arrange for reportr to handle messages, warnings and errors raised by code that does not itself use report() or flag(), by using the wrapper function withReportrHandlers(). Take, for example, the simple function

f <- function() message("In f()")

Ordinarily calling this function produces

f()
## In f()

Wrapping this will instead give

withReportrHandlers(f())
## * INFO: In f()

Note that calls to message(...) will translate to report(Info, ...), warning(...) to flag(Warning, ...), and stop(...) to report(Error, ...).

This mechanism is not particularly advantageous in itself, but it allows the user to exercise all of the reportr control options over all code, whether or not it was written with reportr in mind. These facilities are laid out below.

Output consolidation

Sometimes the same warning is generated many times, and seeing a lot of replication isn’t particularly helpful. The following function is somewhat artificial, since it does not take advantage of vectorisation in R, but it illustrates the point.

f <- function(xs) {
    ys <- numeric(0)
    for (x in xs) ys <- c(ys,sqrt(x))
    ys
}

Now let’s call the function with input that will produce some warnings.

f(-5:5)
##  [1]      NaN      NaN      NaN      NaN      NaN 0.000000 1.000000 1.414214
##  [9] 1.732051 2.000000 2.236068
## Warning messages:
## 1: In sqrt(x) : NaNs produced
## 2: In sqrt(x) : NaNs produced
## 3: In sqrt(x) : NaNs produced
## 4: In sqrt(x) : NaNs produced
## 5: In sqrt(x) : NaNs produced

This duplication of warnings is verbose and unnecessary, and can be consolidated with reportr:

withReportrHandlers(f(-5:5))
## WARNING: [x5] NaNs produced
##  [1]      NaN      NaN      NaN      NaN      NaN 0.000000 1.000000 1.414214
##  [9] 1.732051 2.000000 2.236068

Notice that the five duplicated warnings are reported in one line in this case, along with an indication that the message was produced five times.

Expression substitution

Expression substitution is an alternative to printf-style syntax for incorporating the values of R expressions into strings. It can make messages more readable, and reduce the need for lots of quotation marks. All reportr messages are passed through the es() function for expression substitution, which is part of the ore package. For example,

x <- 3
report(Info, "The value of x is #{x}")
## INFO: The value of x is 3

Note the #{} syntax. Everything within the curly braces is evaluated as an R expression, and the result inserted into the string. Please see ?ore::es for more details.

Message filtering

Sometimes it may be desirable to discard particular messages that would otherwise be reported at the current output level. There are two global options that allow this, reportrMessageFilterIn and reportrMessageFilterOut, each of which takes a Perl-style regular expression. The “in” filter is applied first, keeping only messages that match its regex, and then the “out” filter, which keeps only messages that do not match its regex.

f <- function() {
    report(Info, "One")
    report(Info, "Two")
    report(Info, "Three")
}

options(reportrMessageFilterIn="^T")
f()
## * INFO: Two
## * INFO: Three

options(reportrMessageFilterIn=NULL, reportrMessageFilterOut="^T")
f()
## * INFO: One

Stack tracing

The Debug reporting level is slightly special. When the current output level is Debug, not only are all messages reported, but stack traces are also automatically provided when the message being reported is of level equal to or above the reportrStackTraceLevel option (the default is Error). For example,

f <- function(x) if (!is.numeric(x)) stop("x must be numeric")
g <- function(x) f(x)

withReportrHandlers(g("text"))
## * * ERROR: x must be numeric (in "f(x)")
## --- Begin stack trace ---
## * g("text")
## * * f(x)
## ---  End stack trace  ---

Notice that the number of asterisks in front of the printed message indicates the depth in the stack of the function reporting the message. This can be useful, in a long stream of output, to determine the structure of the reporting code at a glance. The format of this “prefix” can be customised, however: please see ?report for details.

The Question reporting level

In between the Warning and Error reporting levels is a level called Question. This level is used by a function called ask(), which prompts the user for input and returns the result in a string. If the output level is greater than Question, or the session is not interactive, a customisable default value is returned.

f <- function() {
    name <- ask("What is your name?", default="Nobody")
    report(OL$Info, "Hello, #{name}")
    invisible(name)
}

setOutputLevel(Info)
f()
## * QUESTION: What is your name? User
## * INFO: Hello, User

setOutputLevel(Error)
(f())
## [1] "Nobody"