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.
reportr
to handle standard messagesQuestion
reporting levelThe 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.
reportr
to handle standard messagesIt 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
<- function() message("In f()") 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.
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.
<- function(xs) {
f <- numeric(0)
ys 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 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,
<- 3
x 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.
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.
<- function() {
f 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
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,
<- function(x) if (!is.numeric(x)) stop("x must be numeric")
f <- function(x) f(x)
g
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.
Question
reporting levelIn 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.
<- function() {
f <- ask("What is your name?", default="Nobody")
name report(OL$Info, "Hello, #{name}")
invisible(name)
}
setOutputLevel(Info)
f()
## * QUESTION: What is your name? User
## * INFO: Hello, User
setOutputLevel(Error)
f())
(## [1] "Nobody"