There are many ways one may want to register changes in data.
That is why lumberjack
allows you to write your own loggers. Here you can
read how to do it.
In short, a logger is a reference object with the following methods:
$add(meta, input, output)
. This is a function that computes the
difference between input
and output
and adds it to a log. The meta
argument is a list
with two elements:
expr
The expression used to turn input
into output
src
The same expression, but turned into a string.$dump()
This function writes dumps the current logging info somewhere.
Often this will be a file, but it really can be any place where R can send data.There are several systems in R to build such an object. We recommend using R6 classes or Reference classes.
Below we give an example in each system. The example loggers only register
whether something has ever changed. A dump
results in a simple message on
screen.
An introduction to R6 classes can be found here
Let us define the 'trivial' logger.
library(R6)
trivial <- R6Class("trivial",
public = list(
changed = NULL
, initialize = function(){
self$changed <- FALSE
}
, add = function(meta, input, output){
self$changed <- self$changed | !identical(input, output)
}
, dump = function(){
msg <- if(self$changed) "" else "not "
cat(sprintf("The data has %schanged\n",msg))
}
)
)
Here is how to use it.
library(lumberjack)
out <- women %L>%
start_log(trivial$new()) %L>%
identity() %L>%
dump_log(stop=TRUE)
## The data has not changed
out <- women %L>%
start_log(trivial$new()) %L>%
head() %L>%
dump_log(stop=TRUE)
## The data has changed
Reference classes (RC) come with the R recommended methods
package.
An introduction can be found here. Here is how
to define the trivial
logger as a reference class.
library(methods)
trivial <- setRefClass("trivial",
fields = list(
changed = "logical"
),
methods = list(
initialize = function(){
.self$changed = FALSE
}
, add = function(meta, input, output){
.self$changed <- .self$changed | !identical(input,output)
}
, dump = function(){
msg <- if( .self$changed ) "" else "not "
cat(sprintf("The data has %schanged\n",msg))
}
)
)
## code for methods in class "trivial" was not checked for suspicious field assignments (recommended package 'codetools' not available?)
And here is how to use it.
library(lumberjack)
out <- women %L>%
start_log(trivial()) %L>%
identity() %L>%
dump_log(stop=TRUE)
## The data has not changed
out <- women %L>%
start_log(trivial()) %L>%
head() %L>%
dump_log(stop=TRUE)
## The data has changed
Observe that there are subtle differences between R6 and Reference classes (RC).
self
, in RC this is done with .self
.classname$new()
, an RC object is initialized with classname()
. If you have a package that has interesting functionality that can be offered
also inside a logger, you might consider exporting a logger object that works
with lumberjack
. To keep things uniform, we give the following advice.
Most package authors use roxygen2
to generate documentation. Below is an example of how to document the class
and its methods. To show how to document arguments, we included and
allcaps
argument in the dump function.
#' The trivial logger.
#'
#' The trivial logger only registers whether something has changed at all.
#' A `dump` leads to an informative message on the console.
#'
#' @section Creating a logger:
#' \code{trivial$new()}
#'
#' @section Dump options:
#' \code{$dump(allcaps)}
#' \tabular{ll}{
#' \code{allcaps}\tab \code{[logical]} print message in capitals?
#' }
#'
#'
#' @docType class
#' @format An \code{R6} class object.
#'
#' @examples
#' out <- women %L>%
#' start_log(trivial$new()) %L>%
#' head() %L>%
#' dump_log(stop=TRUE)
#'
#'
#' @export
trivial <- R6Class("trivial",
public = list(
changed = NULL
, initialize = function(){
self$changed <- FALSE
}
, add = function(meta, input, output){
self$changed <- self$changed | !identical(input, output)
}
, dump = function(allcaps=FALSE){
msg <- if(self$changed) "" else "not "
msg <- sprintf("The data has %schanged\n",msg)
if (allcaps) msg <- toupper(msg)
cat(msg)
)
)
Once you have exported a logger, it is a good idea to add the line
Enhances: lumberjack
To your DESCRIPTION
file. It can then be found by other users via lumberjack's
CRAN webpage.
We briefly summarize the lumberjack API.
If you implement a logging object, the following methods are mandatory:
add(meta, input, output)
meta
: a list with components expr
(an expression) and src
(a string)input
: data before processingoutput
: data after processingdump(...)
...
: options to pass to other methods.The following methods are optional
stop()
called by stop_log()
before removing the logger from the data.