[med-svn] [r-cran-futile.logger] 10/12: New upstream version 1.4.3

Andreas Tille tille at debian.org
Fri Sep 29 19:04:28 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository r-cran-futile.logger.

commit e2b1ae41fd4fe25ae71908d87ad1fa4abbc08884
Author: Andreas Tille <tille at debian.org>
Date:   Fri Sep 29 21:02:34 2017 +0200

    New upstream version 1.4.3
---
 DESCRIPTION                        |  23 +++
 MD5                                |  26 +++
 NAMESPACE                          |   6 +
 R/appender.R                       |  99 ++++++++++
 R/constants.R                      |  14 ++
 R/futile.logger-package.R          | 113 +++++++++++
 R/layout.R                         | 183 ++++++++++++++++++
 R/logger.R                         | 379 +++++++++++++++++++++++++++++++++++++
 R/options.R                        |  18 ++
 R/scat.R                           |  24 +++
 README.md                          | 133 +++++++++++++
 debian/README.test                 |   9 -
 debian/changelog                   |  26 ---
 debian/compat                      |   1 -
 debian/control                     |  25 ---
 debian/copyright                   |  18 --
 debian/docs                        |   3 -
 debian/rules                       |   4 -
 debian/source/format               |   1 -
 debian/tests/control               |   3 -
 debian/tests/run-unit-test         |  11 --
 debian/watch                       |   2 -
 man/flog.appender.Rd               |  80 ++++++++
 man/flog.carp.Rd                   |  41 ++++
 man/flog.layout.Rd                 |  95 ++++++++++
 man/flog.logger.Rd                 | 152 +++++++++++++++
 man/flog.remove.Rd                 |  29 +++
 man/flog.threshold.Rd              |  38 ++++
 man/ftry.Rd                        |  28 +++
 man/futile.logger-package.Rd       | 123 ++++++++++++
 man/logger.options.Rd              |  36 ++++
 man/scat.Rd                        |  34 ++++
 tests/testthat.R                   |   3 +
 tests/testthat/test_debug.R        |  20 ++
 tests/testthat/test_json.R         |  44 +++++
 tests/testthat/test_layout.R       |  55 ++++++
 tests/testthat/test_logger.R       |  86 +++++++++
 tests/testthat/test_stringconfig.R |  49 +++++
 38 files changed, 1931 insertions(+), 103 deletions(-)

diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..047fb59
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,23 @@
+Package: futile.logger
+Type: Package
+Title: A Logging Utility for R
+Version: 1.4.3
+Date: 2016-07-10
+Author: Brian Lee Yung Rowe
+Maintainer: Brian Lee Yung Rowe <r at zatonovo.com>
+Depends: R (>= 3.0.0)
+Imports: utils, lambda.r (>= 1.1.0), futile.options
+Suggests: testthat, jsonlite
+Description: Provides a simple yet powerful logging utility. Based loosely on
+    log4j, futile.logger takes advantage of R idioms to make logging a
+    convenient and easy to use replacement for cat and print statements.
+License: LGPL-3
+LazyLoad: yes
+NeedsCompilation: no
+ByteCompile: yes
+Collate: 'options.R' 'appender.R' 'constants.R' 'layout.R' 'logger.R'
+        'scat.R' 'futile.logger-package.R'
+RoxygenNote: 5.0.1
+Packaged: 2016-07-10 13:44:30 UTC; brian
+Repository: CRAN
+Date/Publication: 2016-07-10 16:57:47
diff --git a/MD5 b/MD5
new file mode 100644
index 0000000..eb3a3be
--- /dev/null
+++ b/MD5
@@ -0,0 +1,26 @@
+d614067d8a6f9b6c793d9a3cdb6b0540 *DESCRIPTION
+d61ed2adf506a7b7909f59c852f347ed *NAMESPACE
+1969ced6e73dcd0fa77a863596c898e5 *R/appender.R
+adc433b28d3a7d3af71dd39a1b9db833 *R/constants.R
+0ea1553b425022a30df57b7c1e121fb5 *R/futile.logger-package.R
+c78a7f26382a3350abee1f71c9209a22 *R/layout.R
+0f41f8079924d220d49c59e0e99b90e3 *R/logger.R
+eb3624430fe77aa17a803acf9e383c2f *R/options.R
+fce29de8b2fb63d0315b07bec6bd5e5c *R/scat.R
+5af3301ce507c00309be63d5f2c8bad4 *README.md
+6921ef82af355463a353c91dfc3f120d *man/flog.appender.Rd
+746d421add2129919aea5cf3c7d6a369 *man/flog.carp.Rd
+a5d5136f9eb63de9e4387d9827beb70c *man/flog.layout.Rd
+f021d78c3269a680b6b89d0660507bb7 *man/flog.logger.Rd
+8ff226e5e61a04f2d2d9b58e07c82cd4 *man/flog.remove.Rd
+0c3e06e2c7ae3db202e5f35b9df70a3e *man/flog.threshold.Rd
+df02150ddb649d54e0f82ae9343edf84 *man/ftry.Rd
+1213f19c2224957247267af28edbd73c *man/futile.logger-package.Rd
+b0c25a5578011d180f1766d8c0ba5904 *man/logger.options.Rd
+89f313302a032d9ca6be6f422e509d6f *man/scat.Rd
+b2d6ad4073eda6f23f39f2faf921f2dc *tests/testthat.R
+3962c833c285a6c1dca810336022f000 *tests/testthat/test_debug.R
+25119c8e5a5da1695a9987e7a6ba31b9 *tests/testthat/test_json.R
+b77c63046eae36f5cb9928dc28d015b2 *tests/testthat/test_layout.R
+32c7e9a789e9fc1a107817f03539c01d *tests/testthat/test_logger.R
+dd364c3763eb235f19ea637a7ed304b7 *tests/testthat/test_stringconfig.R
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..4ead997
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,6 @@
+# Generated by roxygen2: do not edit by hand
+
+exportPattern("^[^\\.]")
+import(futile.options)
+import(lambda.r)
+import(utils)
diff --git a/R/appender.R b/R/appender.R
new file mode 100644
index 0000000..afe4fd1
--- /dev/null
+++ b/R/appender.R
@@ -0,0 +1,99 @@
+#' Manage appenders for loggers
+#' 
+#' Provides functions for adding and removing appenders.
+#' 
+#' @section Usage:
+#' # Get the appender for the given logger\cr
+#' flog.appender(name) \%::\% character : Function\cr
+#' flog.appender(name='ROOT')
+#'
+#' # Set the appender for the given logger\cr
+#' flog.appender(fn, name='ROOT')
+#'
+#' # Print log messages to the console\cr
+#' appender.console()
+#' 
+#' # Write log messages to a file\cr
+#' appender.file(file)
+#' 
+#' # Write log messages to console and a file\cr
+#' appender.tee(file)
+#' 
+#' 
+#' @section Details:
+#' Appenders do the actual work of writing log messages to some target.
+#' To use an appender in a logger, you must register it to a given logger.
+#' Use \code{flog.appender} to both access and set appenders.
+#' 
+#' The ROOT logger by default uses \code{appender.console}.
+#' 
+#' \code{appender.console} is a function that writes to the console.
+#' No additional arguments are necessary when registering the appender 
+#' via flog.appender.
+#' 
+#' 
+#' \code{appender.file} writes to a file, so you must pass an additional file
+#' argument to the function. To change the file name, just call
+#' \code{flog.appender(appender.file(file))} again with a new file name.
+#' 
+#' To use your own appender create a function that takes a single argument,
+#' which represents the log message. You need to pass a function reference to
+#' \code{flog.appender}.
+#' 
+#' \code{appender.tee} writes to both the console and file.
+#'
+#' @section Value:
+#' When getting the appender, \code{flog.appender} returns the appender
+#' function.  When setting an appender, \code{flog.appender} has no 
+#' return value.
+#'
+#' @name flog.appender
+#' @aliases appender.console appender.file appender.tee
+#' @param \dots Used internally by lambda.r
+#' @author Brian Lee Yung Rowe
+#' @seealso \code{\link{flog.logger}} \code{\link{flog.layout}}
+#' @keywords data
+#' @examples
+#' \dontrun{
+#' flog.appender(appender.console(), name='my.logger')
+#'
+#' # Set an appender to the logger named 'my.package'. Any log operations from
+#' # this package will now use this appender.
+#' flog.appender(appender.file('my.package.out'), 'my.package')
+#' }
+
+# Get appenders associated with the given logger
+flog.appender(name) %::% character : Function
+flog.appender(name='ROOT') %as%
+{
+  logger <- flog.logger(name)
+  logger$appender
+}
+
+# Set the appender for the given logger
+flog.appender(fn, name='ROOT') %as%
+{
+  flog.logger(name, appender=fn)
+  invisible()
+}
+
+# Some default handlers for use in futile.logger. All handlers need to conform
+# to the below signature: function(line)
+appender.console <- function()
+{
+  function(line) cat(line, sep='')
+}
+
+# Write to a file.
+appender.file <- function(file)
+{
+  function(line) cat(line, file=file, append=TRUE, sep='')
+}
+
+# Write to a file and to console 
+appender.tee <- function(file){ 
+  function(line) {
+    cat(line, sep='') 
+    cat(line, file=file, append=TRUE, sep='')
+  }
+}
diff --git a/R/constants.R b/R/constants.R
new file mode 100644
index 0000000..f6c6cdb
--- /dev/null
+++ b/R/constants.R
@@ -0,0 +1,14 @@
+FATAL <- 1L
+names(FATAL) <- "FATAL"
+ERROR <- 2L
+names(ERROR) <- "ERROR"
+WARN <- 4L
+names(WARN) <- "WARN"
+INFO <- 6L
+names(INFO) <- "INFO"
+DEBUG <- 8L
+names(DEBUG) <- "DEBUG"
+TRACE <- 9L
+names(TRACE) <- "TRACE"
+
+
diff --git a/R/futile.logger-package.R b/R/futile.logger-package.R
new file mode 100644
index 0000000..7c58135
--- /dev/null
+++ b/R/futile.logger-package.R
@@ -0,0 +1,113 @@
+#' A Logging Utility for R
+#' 
+#' This package implements a logging system inspired by log4j. The basic idea
+#' of layouts, appenders, and loggers is faithful to log4j, while the
+#' implementation and idiom is all R. This means that support for hierarchical
+#' loggers, custom appenders, custom layouts is coupled with a simple and
+#' intuitive functional syntax.
+#' 
+#' \tabular{ll}{
+#' Package: \tab futile.logger\cr
+#' Type: \tab Package\cr
+#' Version: \tab 1.4.3\cr
+#' Date: \tab 2016-07-10\cr
+#' License: \tab LGPL-3\cr
+#' LazyLoad: \tab yes\cr
+#' }
+#' 
+#' The latest version of futile.logger introduces zero-configuration semantics
+#' out of the box. This means that you can use the default configuration as is.
+#' It is also easy to interactively change the configuration of the ROOT
+#' logger, as well as create new loggers. Since loggers form a hierarchy based
+#' on their name, the ROOT logger is the starting point of the hierarchy and
+#' always exists. By default the ROOT logger is defined with a simple layout,
+#' printing to the console, with an INFO threshold. This means that writing to
+#' any logger with a threshold of INFO or higher will write to the console.
+#' 
+#' All of the logging functions take a format string so it is easy to add
+#' arbitrary values to log messages.
+#' 
+#' > flog.info("This song is just \%s words \%s", 7, "long")
+#' 
+#' Thresholds range from most verbose to least verbose: TRACE, DEBUG, INFO,
+#' WARN, ERROR, FATAL. You can easily change the threshold of the ROOT logger
+#' by calling > flog.threshold(TRACE) which changes will print all log messages
+#' from every package. To suppress most logging by default but turn on all
+#' debugging for a logger 'my.logger', you would execute 
+#'
+#' > flog.threshold(ERROR)\cr
+#' > flog.threshold(TRACE, name='my.logger')
+#'
+#' Any arbitrary logger can be defined simply by specifying it in any
+#' futile.logger write operation (futile.threshold, futile.appender,
+#' futile.layout). If the logger hasn't been defined, then it will be defined
+#' dynamically. Any unspecified options will be copied from the parent logger.
+#' 
+#' When writing log messages, futile.logger will search the hierarchy based on
+#' the logger name. In our example, if 'my.logger' hasn't been defined then
+#' futile.logger will look for a logger named 'my' and finally the ROOT logger.
+#' 
+#' Functions calling futile.logger from a package are automatically assigned a
+#' logger that has the name of the package. Suppose we have log messages in a
+#' package called 'my.package'. Then any function that calls futile.logger from
+#' within the package will automatically be assigned a default logger of
+#' 'my.package' instead of ROOT. This means that it is easy to change the log
+#' setting of any package that uses futile.logger for logging by just updating
+#' the logger for the given package. For instance suppose you want to output
+#' log message for my.package to a file instead.
+#' 
+#' > flog.appender(appender.file('my.package.log'), name='my.package')
+#' 
+#' Now all log statements in the package my.package will be written to a file
+#' instead of the console. All other log messages will continue to be written
+#' to the console.
+#' 
+#' Appenders do the actual work of writing log messages to a writeable target,
+#' whether that is a console, a file, a URL, database, etc. When creating an
+#' appender, the implementation-specific options are passed to the appender at
+#' instantiation. The package defines two appender generator functions:
+#' 
+#' \describe{
+#'   \item{appender.file}{Write to a file}
+#'   \item{appender.console}{Write to the console}
+#' }
+#' 
+#' Each of these functions returns the actual appender function, so be sure to
+#' actually call the function!
+#' 
+#' Layouts are responsible for formatting messages. This operation usually
+#' consists of adding the log level, a timestamp, plus some pretty-printing to
+#' make the log messages easy on the eyes. The package supplies several layouts:
+#' 
+#' \describe{
+#'   \item{layout.simple}{Writes messages with a default format}
+#'   \item{layout.json}{Generates messages in a JSON format}
+#'   \item{layout.format}{Define your own format}
+#'   \item{layout.tracearg}{Print a variable name along with its value}
+#' }
+#' 
+#' @name futile.logger-package
+#' @aliases futile.logger-package futile.logger flog.namespace
+#' @docType package
+#' @exportPattern "^[^\\.]"
+#' @import utils lambda.r futile.options
+#' @author Brian Lee Yung Rowe <r@@zatonovo.com>
+#' @seealso \code{\link{flog.logger}}, \code{\link{flog.threshold}},
+#' \code{\link{flog.layout}}, \code{\link{flog.appender}}
+#' @keywords package attribute logic
+#' @examples
+#' 
+#' flog.debug("This %s print", "won't")
+#' flog.warn("This %s print", "will")
+#'   
+#' flog.info("This inherits from the ROOT logger", name='logger.a')
+#' flog.threshold(DEBUG, name='logger.a')
+#' flog.debug("logger.a has now been set to DEBUG", name='logger.a')
+#' flog.debug("But the ROOT logger is still at INFO (so this won't print)")
+#' 
+#' \dontrun{
+#' flog.appender(appender.file("other.log"), name='logger.b')
+#' flog.info("This writes to a %s", "file", name='logger.b')
+#' }
+#' 
+NULL
diff --git a/R/layout.R b/R/layout.R
new file mode 100644
index 0000000..cce6916
--- /dev/null
+++ b/R/layout.R
@@ -0,0 +1,183 @@
+#' Manage layouts within the 'futile.logger' sub-system
+#' 
+#' Provides functions for managing layouts. Typically 'flog.layout' is only
+#' used when manually creating a logging configuration.
+#' 
+#' @section Usage:
+#' # Get the layout function for the given logger\cr
+#' flog.layout(name) \%::\% character : Function\cr
+#' flog.layout(name='ROOT')
+#' 
+#' # Set the layout function for the given logger\cr
+#' flog.layout(fn, name='ROOT')
+#' 
+#' # Decorate log messages with a standard format\cr
+#' layout.simple(level, msg, ...)
+#' 
+#' # Generate log messages as JSON\cr
+#' layout.json(level, msg, ...)
+#'
+#' # Decorate log messages using a custom format\cr
+#' layout.format(format, datetime.fmt="%Y-%m-%d %H:%M:%S")
+#'
+#' # Show the value of a single variable
+#' layout.tracearg(level, msg, ...)
+#' 
+#' @section Details:
+#' Layouts are responsible for formatting messages so they are human-readable.
+#' Similar to an appender, a layout is assigned to a logger by calling 
+#' \code{flog.layout}. The \code{flog.layout} function is used internally
+#' to get the registered layout function. It is kept visible so 
+#' user-level introspection is possible.
+#' 
+#' \code{layout.simple} is a pre-defined layout function that 
+#' prints messages in the following format:\cr
+#'   LEVEL [timestamp] message
+#'
+#' This is the default layout for the ROOT logger.
+#' 
+#' \code{layout.format} allows you to specify the format string to use 
+#' in printing a message. The following tokens are available.
+#' \describe{
+#' \item{~l}{Log level}
+#' \item{~t}{Timestamp}
+#' \item{~n}{Namespace}
+#' \item{~f}{The calling function}
+#' \item{~m}{The message}
+#' }
+#'
+#' \code{layout.json} converts the message and any additional objects provided
+#' to a JSON structure. E.g.:
+#' 
+#' flog.info("Hello, world", cat='asdf')
+#'  
+#' yields something like
+#' 
+#' \{"level":"INFO","timestamp":"2015-03-06 19:16:02 EST","message":"Hello, world","func":"(shell)","cat":["asdf"]\}
+#' 
+#' \code{layout.tracearg} is a special layout that takes a variable
+#' and prints its name and contents.
+#' 
+#' @name flog.layout
+#' @aliases layout.simple layout.format layout.tracearg layout.json
+#' @param \dots Used internally by lambda.r
+#' @author Brian Lee Yung Rowe
+#' @seealso \code{\link{flog.logger}} \code{\link{flog.appender}}
+#' @keywords data
+#' @examples
+#' # Set the layout for 'my.package'
+#' flog.layout(layout.simple, name='my.package')
+#'
+#' # Update the ROOT logger to use a custom layout
+#' layout <- layout.format('[~l] [~t] [~n.~f] ~m')
+#' flog.layout(layout)
+#' 
+#' # Create a custom logger to trace variables
+#' flog.layout(layout.tracearg, name='tracer')
+#' x <- 5
+#' flog.info(x, name='tracer')
+NULL
+
+# Get the layout for the given logger
+flog.layout(name) %::% character : Function
+flog.layout(name='ROOT') %as%
+{
+  logger <- flog.logger(name)
+  logger$layout
+}
+
+# Set the layout
+flog.layout(fn, name='ROOT') %as%
+{
+  flog.logger(name, layout=fn)
+  invisible()
+}
+
+# This file provides some standard formatters
+# This prints out a string in the following format:
+#   LEVEL [timestamp] message
+layout.simple <- function(level, msg, ...)
+{
+  the.time <- format(Sys.time(), "%Y-%m-%d %H:%M:%S")
+  if (length(list(...)) > 0) {
+    parsed <- lapply(list(...), function(x) ifelse(is.null(x), 'NULL', x))
+    msg <- do.call(sprintf, c(msg, parsed))
+  }
+  sprintf("%s [%s] %s\n", names(level),the.time, msg)
+}
+
+# Generates a list object, then converts it to JSON and outputs it
+layout.json <- function(level, msg, ...) {
+  if (!requireNamespace("jsonlite", quietly=TRUE))
+    stop("layout.json requires jsonlite. Please install it.", call.=FALSE)
+  
+  where <- 1 # to avoid R CMD CHECK issue
+  the.function <- tryCatch(deparse(sys.call(where)[[1]]),
+    error=function(e) "(shell)")
+  the.function <- ifelse(
+    length(grep('flog\\.',the.function)) == 0, the.function, '(shell)')
+  
+  output_list <- list(
+    level=jsonlite::unbox(names(level)),
+    timestamp=jsonlite::unbox(format(Sys.time(), "%Y-%m-%d %H:%M:%S %z")),
+    message=jsonlite::unbox(msg),
+    func=jsonlite::unbox(the.function),
+    additional=...
+  )
+  jsonlite::toJSON(output_list, simplifyVector=TRUE)
+}
+
+# This parses and prints a user-defined format string. Available tokens are
+# ~l - Log level
+# ~t - Timestamp
+# ~n - Namespace
+# ~f - Calling function
+# ~m - Message
+#
+# layout <- layout.format('[~l] [~t] [~n.~f] ~m')
+# flog.layout(layout)
+layout.format <- function(format, datetime.fmt="%Y-%m-%d %H:%M:%S")
+{
+  where <- 1
+  function(level, msg, ...) {
+    if (! is.null(substitute(...))) msg <- sprintf(msg, ...)
+    the.level <- names(level)
+    the.time <- format(Sys.time(), datetime.fmt)
+    the.namespace <- ifelse(flog.namespace() == 'futile.logger','ROOT',flog.namespace())
+    #print(sys.calls())
+    the.function <- tryCatch(deparse(sys.call(where)[[1]]), error=function(e) "(shell)")
+    the.function <- ifelse(length(grep('flog\\.',the.function)) == 0, the.function, '(shell)')
+    #pattern <- c('~l','~t','~n','~f','~m')
+    #replace <- c(the.level, the.time, the.namespace, the.function, msg)
+    message <- gsub('~l',the.level, format, fixed=TRUE)
+    message <- gsub('~t',the.time, message, fixed=TRUE)
+    message <- gsub('~n',the.namespace, message, fixed=TRUE)
+    message <- gsub('~f',the.function, message, fixed=TRUE)
+    message <- gsub('~m',msg, message, fixed=TRUE)
+    sprintf("%s\n", message)
+  }
+}
+
+layout.tracearg <- function(level, msg, ...)
+{
+  the.time <- format(Sys.time(), "%Y-%m-%d %H:%M:%S")
+  if (is.character(msg)) {
+    if (! is.null(substitute(...))) msg <- sprintf(msg, ...)
+  } else {
+    external.call <- sys.call(-2)
+    external.fn <- eval(external.call[[1]])
+    matched.call <- match.call(external.fn, external.call)
+    matched.call <- matched.call[-1]
+    matched.call.names <- names(matched.call)
+
+    ## We are interested only in the msg and ... parameters,
+    ## i.e. in msg and all parameters not explicitly declared
+    ## with the function
+    is.output.param <- matched.call.names == "msg" |
+      !(matched.call.names %in% c(setdiff(names(formals(external.fn)), "...")))
+
+    label <- lapply(matched.call[is.output.param], deparse)
+    msg <- sprintf("%s: %s", label, c(msg, list(...)))
+  }
+  sprintf("%s [%s] %s\n", names(level),the.time, msg)
+}
diff --git a/R/logger.R b/R/logger.R
new file mode 100644
index 0000000..a9bb354
--- /dev/null
+++ b/R/logger.R
@@ -0,0 +1,379 @@
+#' Manage loggers
+#' 
+#' Provides functions for writing log messages and managing loggers. Typically
+#' only the flog.[trace|debug|info|warn|error|fatal] functions need to be used
+#' in conjunction with flog.threshold to interactively change the log level.
+#' 
+#' @section Usage:
+#' # Conditionally print a log statement at TRACE log level\cr
+#' flog.trace(msg, ..., name=flog.namespace(), capture=FALSE)
+#'
+#' # Conditionally print a log statement at DEBUG log level\cr
+#' flog.debug(msg, ..., name=flog.namespace(), capture=FALSE)
+#'
+#' # Conditionally print a log statement at INFO log level\cr
+#' flog.info(msg, ..., name=flog.namespace(), capture=FALSE)
+#'
+#' # Conditionally print a log statement at WARN log level\cr
+#' flog.warn(msg, ..., name=flog.namespace(), capture=FALSE)
+#'
+#' # Conditionally print a log statement at ERROR log level\cr
+#' flog.error(msg, ..., name=flog.namespace(), capture=FALSE)
+#'
+#' # Print a log statement at FATAL log level\cr
+#' flog.fatal(msg, ..., name=flog.namespace(), capture=FALSE)
+#'
+#' # Execute an expression and capture any warnings or errors
+#' ftry(expr, error=stop, finally=NULL)
+#'
+#' @section Additional Usage:
+#' These functions generally do not need to be called by an end user.
+#'
+#' # Get the ROOT logger\cr
+#' flog.logger()
+#' 
+#' # Get the logger with the specified name\cr
+#' flog.logger(name)
+#'
+#' # Set options for the given logger\cr
+#' flog.logger(name, threshold=NULL, appender=NULL, layout=NULL, carp=NULL)
+#' 
+#' @section Details:
+#' These functions represent the high level interface to futile.logger.
+#' 
+#' The primary use case for futile.logger is to write out log messages. There
+#' are log writers associated with all the predefined log levels: TRACE, DEBUG,
+#' INFO, WARN, ERROR, FATAL. Log messages will only be written if the log level
+#' is equal to or more urgent than the current threshold. By default the ROOT
+#' logger is set to INFO.
+#' 
+#' > flog.debug("This won't print") \cr
+#' > flog.info("But this \%s", 'will') \cr
+#' > flog.warn("As will \%s", 'this')
+#' 
+#' Typically, the built in log level constants are used in the call, which
+#' conform to the log4j levels (from least severe to most severe): TRACE,
+#' DEBUG, INFO, WARN, ERROR, FATAL. It is not a strict requirement to use these
+#' constants (any numeric value will work), though most users should find this
+#' level of granularity sufficient.
+#' 
+#' Loggers are hierarchical in the sense that any requested logger that is
+#' undefined will fall back to its most immediate defined parent logger. The
+#' absolute parent is ROOT, which is guaranteed to be defined for the system
+#' and cannot be deleted. This means that you can specify a new logger
+#' directly.
+#' 
+#' > flog.info("This will fall back to 'my', then 'ROOT'", name='my.logger')
+#' 
+#' You can also change the threshold or any other setting associated with a
+#' logger. This will create an explicit logger where any unspecified options
+#' are copied from the parent logger.
+#' 
+#' > flog.appender(appender.file("foo.log"), name='my') \cr
+#' > flog.threshold(ERROR, name='my.logger') \cr
+#' > flog.info("This won't print", name='my.logger') \cr
+#' > flog.error("This %s print to a file", 'will', name='my.logger') \cr
+#' 
+#' If you define a logger that you later want to remove, use flog.remove.
+#' 
+#' The option 'capture' allows you to print out more complicated data
+#' structures without a lot of ceremony. This variant doesn't accept format
+#' strings and instead appends the value to the next line of output. Consider 
+#'
+#' > m <- matrix(rnorm(12), nrow=3) \cr
+#' > flog.info("Matrix:",m, capture=TRUE)
+#'
+#' which preserves the formatting, whereas using capture=FALSE will have 
+#' a cluttered output due to recycling.
+#' 
+#' @name flog.logger
+#' @aliases flog.trace flog.debug flog.info flog.warn flog.error flog.fatal
+#' @param msg The message to log
+#' @param name The logger name to use
+#' @param capture Capture print output of variables instead of interpolate
+#' @param \dots Optional arguments to populate the format string
+#' @param expr An expression to evaluate
+#' @param finally An optional expression to evaluate at the end
+#' @author Brian Lee Yung Rowe
+#' @seealso \code{\link{flog.threshold}} \code{\link{flog.remove}}
+#' \code{\link{flog.carp}} \code{\link{flog.appender}} \code{\link{flog.layout}}
+#' @keywords data
+#' @examples
+#' 
+#' flog.threshold(DEBUG)
+#' flog.debug("This debug message will print")
+#' 
+#' flog.threshold(WARN)
+#' flog.debug("This one won't")
+#' 
+#' m <- matrix(rnorm(12), nrow=3)
+#' flog.info("Matrix:",m, capture=TRUE)
+#' 
+#' ftry(log(-1))
+#' 
+#' \dontrun{
+#' s <- c('FCX','AAPL','JPM','AMZN')
+#' p <- TawnyPortfolio(s)
+#'
+#' flog.threshold(TRACE,'tawny')
+#' ws <- optimizePortfolio(p, RandomMatrixDenoiser())
+#' z <- getIndexComposition()
+#'
+#' flog.threshold(WARN,'tawny')
+#' ws <- optimizePortfolio(p, RandomMatrixDenoiser())
+#' z <- getIndexComposition()
+#'
+#' }
+NULL
+
+.log_level <- function(msg, ..., level, name, capture)
+{
+  logger <- flog.logger(name)
+  if (level > logger$threshold && (is.null(logger$carp) || !logger$carp)) {
+    return(invisible())
+  }
+
+  appender <- flog.appender(name)
+  layout <- flog.layout(name)
+  if (capture) {
+    values <- paste(capture.output(print(...)), collapse='\n')
+    message <- c(layout(level, msg), "\n", values, "\n")
+  } else {
+    message <- layout(level, msg, ...)
+  }
+  if (level <= logger$threshold) appender(message)
+  invisible(message)
+}
+
+# Get the namespace that a function resides in. If no namespace exists, then
+# return NULL.
+# <environment: namespace:lambda.r>
+flog.namespace <- function(where=1)
+{
+  s <- capture.output(str(topenv(environment(sys.function(where))), give.attr=FALSE))
+  if (length(grep('lambda.r',s)) > 0)
+    s <- attr(sys.function(-5), 'topenv')
+
+  if (length(grep('namespace', s)) < 1) return('ROOT')
+
+  ns <- sub('.*namespace:([^>]+)>.*','\\1', s)
+  ifelse(is.null(ns), 'ROOT', ns)
+}
+
+
+flog.trace <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
+  .log_level(msg, ..., level=TRACE,name=name, capture=capture)
+}
+
+flog.debug <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
+  .log_level(msg, ..., level=DEBUG,name=name, capture=capture)
+}
+
+flog.info <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
+  .log_level(msg, ..., level=INFO,name=name, capture=capture)
+}
+
+flog.warn <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
+  .log_level(msg, ..., level=WARN,name=name, capture=capture)
+}
+
+flog.error <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
+  .log_level(msg, ..., level=ERROR,name=name, capture=capture)
+}
+
+flog.fatal <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
+  .log_level(msg, ..., level=FATAL,name=name, capture=capture)
+}
+
+#' Wrap a try block in futile.logger
+#'
+#' This function integrates futile.logger with the error and warning system
+#' so problems can be caught both in the standard R warning system, while
+#' also being emitted via futile.logger.
+#'
+#' @name ftry
+#' @param expr The expression to evaluate in a try block
+#' @param error An error handler
+#' @param finally Pass-through to tryCatch finally
+#' @author Brian Lee Yung Rowe
+#' @keywords data
+#' @examples
+#' ftry(log(-1))
+ftry <- function(expr, error=stop, finally=NULL) {
+  w.handler <- function(e) flog.warn("%s", e)
+  e.handler <- function(e) { flog.error("%s", e); error(e) }
+  tryCatch(expr, warning=w.handler, error=e.handler, finally)
+}
+
+# By default, use the package namespace or use the 'ROOT' logger.
+flog.logger() %as%
+{
+  flog.logger(flog.namespace())
+}
+
+flog.logger(name) %as%
+{
+  if (nchar(name) < 1) name <- 'ROOT'
+  #cat(sprintf("Searching for logger %s\n", name))
+
+  key <- paste("logger", name, sep='.')
+  # TODO: Search hierarchy
+  os <- logger.options(key)
+  if (! is.null(os)) return(os)
+  if (name == 'ROOT') {
+    logger <- list(name=name,
+      threshold=INFO, 
+      appender=appender.console(),
+      layout=layout.simple)
+    logger.options(update=list(key, logger))
+    return(logger)
+  }
+
+  parts <- strsplit(name, '.', fixed=TRUE)[[1]]
+  parent <- paste(parts[1:length(parts)-1], collapse='.')
+  flog.logger(parent)
+}
+
+flog.logger(name, threshold=NULL, appender=NULL, layout=NULL, carp=NULL) %as%
+{
+  logger <- flog.logger(name)
+  if (!is.null(threshold)) logger$threshold <- threshold
+  if (!is.null(appender)) logger$appender <- appender
+  if (!is.null(layout)) logger$layout <- layout
+  if (!is.null(carp)) logger$carp <- carp
+  
+  key <- paste("logger", name, sep='.')
+  logger.options(update=list(key, logger))
+  invisible()
+}
+
+
+#' Remove a logger
+#' 
+#' In the event that you no longer wish to have a logger registered,
+#' use this function to remove it. Then any references to this
+#' logger will inherit the next available logger in the hierarchy.
+#'
+#' @section Usage:
+#' # Remove a logger\cr
+#' flog.remove(name)
+#' 
+#' @name flog.remove
+#' @param name The logger name to use
+#' @author Brian Lee Yung Rowe
+#' @keywords data
+#' @examples
+#' flog.threshold(ERROR, name='my.logger')
+#' flog.info("Won't print", name='my.logger')
+#' flog.remove('my.logger')
+#' flog.info("Will print", name='my.logger')
+flog.remove('ROOT') %as% { invisible() }
+flog.remove(name) %as% 
+{
+  key <- paste("logger", name, sep='.')
+  logger.options(update=list(key, NULL))
+  invisible()
+}
+
+#' Get and set the threshold for a logger
+#'
+#' The threshold affects the visibility of a given logger. When a log
+#' statement is called, e.g. \code{flog.debug('foo')}, futile.logger
+#' compares the threshold of the logger with the level implied in the
+#' log command (in this case DEBUG). If the log level is at or higher
+#' in priority than the logger threshold, a message will print.
+#' Otherwise the command will silently return.
+#'
+#' @section Usage:
+#' # Get the threshold for the given logger\cr
+#' flog.threshold(name) \%::\% character : character \cr
+#' flog.threshold(name=ROOT)
+#'
+#' # Set the threshold for the given logger\cr
+#' flog.threshold(threshold, name=ROOT)
+#' 
+#' @name flog.threshold
+#' @param threshold integer The new threshold for the given logger
+#' @param name character The name of the logger
+#' @author Brian Lee Yung Rowe
+#' @keywords data
+#' @examples
+#' flog.threshold(ERROR)
+#' flog.info("Won't print")
+#' flog.threshold(INFO)
+#' flog.info("Will print")
+# Set the threshold
+flog.threshold('TRACE', name='ROOT') %as% flog.threshold(TRACE, name)
+flog.threshold('trace', name='ROOT') %as% flog.threshold(TRACE, name)
+flog.threshold('DEBUG', name='ROOT') %as% flog.threshold(DEBUG, name)
+flog.threshold('debug', name='ROOT') %as% flog.threshold(DEBUG, name)
+flog.threshold('INFO', name='ROOT') %as% flog.threshold(INFO, name)
+flog.threshold('info', name='ROOT') %as% flog.threshold(INFO, name)
+flog.threshold('WARN', name='ROOT') %as% flog.threshold(WARN, name)
+flog.threshold('warn', name='ROOT') %as% flog.threshold(WARN, name)
+flog.threshold('ERROR', name='ROOT') %as% flog.threshold(ERROR, name)
+flog.threshold('error', name='ROOT') %as% flog.threshold(ERROR, name)
+flog.threshold('FATAL', name='ROOT') %as% flog.threshold(FATAL, name)
+flog.threshold('fatal', name='ROOT') %as% flog.threshold(FATAL, name)
+
+flog.threshold(threshold, name='ROOT') %as%
+{
+  flog.logger(name, threshold=threshold)
+  invisible()
+}
+
+# Get the threshold
+flog.threshold(name) %::% character : character
+flog.threshold(name='ROOT') %as%
+{
+  logger <- flog.logger(name)
+  names(logger$threshold)
+}
+
+
+#' Always return the log message
+#'
+#' Indicate whether the logger will always return the log message
+#' despite the threshold.
+#' 
+#' This is a special option to allow the return value of the flog.*
+#' logging functions to return the generated log message even if
+#' the log level does not exceed the threshold. Note that this 
+#' minorly impacts performance when enabled. This functionality
+#' is separate from the appender, which is still bound to the 
+#' value of the logger threshold.
+#'
+#' @section Usage:
+#' # Indicate whether the given logger should carp\cr
+#' flog.carp(name=ROOT)
+#'
+#' # Set whether the given logger should carp\cr
+#' flog.carp(carp, name=ROOT)
+#'
+#' @name flog.carp
+#' @param carp logical Whether to carp output or not
+#' @param name character The name of the logger
+#' @author Brian Lee Yung Rowe
+#' @keywords data
+#' @examples
+#' flog.carp(TRUE)
+#' x <- flog.debug("Returns this message but won't print")
+#' flog.carp(FALSE)
+#' y <- flog.debug("Returns nothing and prints nothing")
+flog.carp(name) %::% character : logical
+flog.carp(name='ROOT') %as%
+{
+  logger <- flog.logger(name)
+  if (is.null(logger$carp)) FALSE
+  else logger$carp
+}
+
+# Set whether to carp
+flog.carp(carp, name='ROOT') %as%
+{
+  flog.logger(name, carp=carp)
+  invisible()
+}
+
+
+
diff --git a/R/options.R b/R/options.R
new file mode 100644
index 0000000..14d4b89
--- /dev/null
+++ b/R/options.R
@@ -0,0 +1,18 @@
+#' Constants for 'futile.logger'
+#' 
+#' Log level constants and the logger options.
+#'
+#' The logging configuration is managed by 'logger.options', a function
+#' generated by OptionsManager within 'futile.options'.
+#' 
+#' @name logger.options
+#' @aliases FATAL ERROR WARN INFO DEBUG TRACE
+#' @usage logger.options(..., simplify = FALSE, update = list())
+#' @param ... TODO
+#' @param simplify TODO
+#' @param update TODO
+#' @author Brian Lee Yung Rowe
+#' @seealso \code{futile.options}
+#' @keywords data
+logger.options <- OptionsManager('logger.options')
+
diff --git a/R/scat.R b/R/scat.R
new file mode 100644
index 0000000..46dab88
--- /dev/null
+++ b/R/scat.R
@@ -0,0 +1,24 @@
+#' Print formatted messages
+#' 
+#' A replacement for \code{cat} that has built-in sprintf formatting
+#' 
+#' Like \code{cat} but you can use format strings.
+#' 
+#' @param format A format string passed to sprintf
+#' @param use.newline Whether to append a new line at the end
+#' @param \dots Arguments to pass to sprintf for dereferencing
+#' @return A formatted string printed to the console
+#' @author Brian Lee Yung Rowe
+#' @keywords data
+#' @examples
+#' 
+#'   apply(array(2:5),1, function(x) scat('This has happened %s times', x) )
+#' 
+scat <- function(format, ..., use.newline=TRUE)
+{
+  if (use.newline) newline = '\n'
+  else newline = ''
+
+  cat(paste(sprintf(format, ...), newline, sep=''))
+}
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7637e73
--- /dev/null
+++ b/README.md
@@ -0,0 +1,133 @@
+[![Build Status](https://travis-ci.org/zatonovo/futile.logger.png)](https://travis-ci.org/zatonovo/futile.logger)
+
+Overview
+========
+futile.logger is a logging utility for R. Originally built based on log4j, 
+the latest version introduces a new API that is more consistent with R idioms.
+In practice this means an interface that works equally well in the shell for
+interactive use and also in scripts for system use.
+
+The underlying concepts of log4j still exist, e.g. loggers, appenders, and
+formatters. There continues to be a hierarchical system for logger. In 
+addition, there is now automatic package scoping, which means that packages
+are given their own logger namespace so you can interactively turn on and
+off logging for specific packages.
+
+Also included is formatting logic to log list objects (which includes 
+data.frames) in a smart way.
+
+Usage
+=====
+Out of the box, the default `ROOT` logger logs to the console with threshold
+set to INFO.
+
+```R
+flog.info("Hello, %s", "world")
+
+# This won't print by default
+flog.debug("Goodbye, %s", "world")
+
+# Change the log level to debug and try again
+flog.threshold(DEBUG)
+flog.debug("Goodbye, %s", "world")
+
+# Keep an alternate logger at WARN
+flog.threshold(WARN, name='quiet')
+
+# This won't print since it's using the logger named 'quiet'!
+flog.debug("Goodbye, %s", "world", name='quiet')
+
+```
+
+Loggers
+-------
+A logger is simply a namespace bound to a threshold, an appender, and a
+formatter. Loggers are configured automatically whenever they are 
+referenced (for example when changing the threshold) inheriting the settings
+of the root logger. To explicitly create a logger call `log.logger()`.
+
+```R
+flog.logger("tawny", WARN, appender=appender.file('tawny.log'))
+```
+
+To remove a logger, use `log.remove()`. If no such logger exists,
+the command is safely ignored.
+
+```R
+flog.remove("tawny")
+```
+
+Thresholds
+----------
+The logger threshold determines what will be logged for a given logger. Use
+this function to retrieve and also change this threshold.
+
+```R
+# Get the logging threshold for the ROOT logger
+flog.threshold()
+```
+
+The default logger is ROOT. To change the threshold of a different logger, 
+specify the logger argument with a string that represents the logger. Note
+that a log.(debug|info|warn|error) command running from a package will
+automatically be associated with a logger with the name of the package. This
+structure means you can change the log level for a specific package as 
+necessary.
+
+```R
+# Set root logger to DEBUG level to see all log messages
+flog.threshold(DEBUG)
+# Suppress log messages below WARN for logger 'quiet'
+flog.threshold(WARN, name="quiet")
+```
+
+Appenders
+---------
+An appender defines where output is directed. Typically only one appender is
+used per logger, but multiple can be assigned. The package provides the 
+following appenders:
+
++ `appender.console`
++ `appender.file`
++ `appender.tee`
+
+To change the appenders assigned to a logger, use `flog.appender()`:
+```R
+# Change the 'quiet' logger to write to a file
+flog.appender(appender.file('quiet.log'), 'quiet')
+flog.warn("Goodbye, %s", "world", name='quiet')
+```
+
+You can create your own appender by defining a function that accepts a single
+character argument. It is up to you to define the behavior. For example,
+an appender that logs to a URL might look like the following.
+
+```R
+url_appender.gen <- function(url) {
+  conn <- url(url)
+  function(line) {
+    file.write()
+  }
+}
+```
+
+flog.format("futile.matrix", fn)
+
+Layouts
+-------
+A layout defines how a log message is printed. The default layout.simple
+prints log messages using the following format:
+  LEVEL [datetime] Message
+
+The layouts included in the package are:
++ layout.simple - Use a default format
++ layout.format - Provide a customizable format string
++ layout.tracearg - Dump a variable with its name
+
+
+What's New
+==========
++ Function to wrap a try/catch with logging (ftry)
++ Capture output for print statements (for more complex objects)
++ New layout.tracearg
+
diff --git a/debian/README.test b/debian/README.test
deleted file mode 100644
index 8d70ca3..0000000
--- a/debian/README.test
+++ /dev/null
@@ -1,9 +0,0 @@
-Notes on how this package can be tested.
-────────────────────────────────────────
-
-This package can be tested by running the provided test:
-
-cd tests
-LC_ALL=C R --no-save < testthat.R
-
-in order to confirm its integrity.
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index d32aad6..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,26 +0,0 @@
-r-cran-futile.logger (1.4.3-1) unstable; urgency=medium
-
-  * New upstream version
-  * Convert to dh-r
-  * Canonical homepage for CRAN
-
- -- Andreas Tille <tille at debian.org>  Mon, 07 Nov 2016 21:25:47 +0100
-
-r-cran-futile.logger (1.4.1-3) unstable; urgency=medium
-
-  * Add missing test dependency r-cran-testthat
-
- -- Andreas Tille <tille at debian.org>  Fri, 29 Apr 2016 09:43:55 +0200
-
-r-cran-futile.logger (1.4.1-2) unstable; urgency=medium
-
-  * Fix autopkgtest
-  * cme fix dpkg-control
-
- -- Andreas Tille <tille at debian.org>  Thu, 28 Apr 2016 11:38:00 +0200
-
-r-cran-futile.logger (1.4.1-1) unstable; urgency=medium
-
-  * Initial upload (Closes: #787097)
-
- -- Andreas Tille <tille at debian.org>  Thu, 28 May 2015 17:22:13 +0200
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index ec63514..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 85aa260..0000000
--- a/debian/control
+++ /dev/null
@@ -1,25 +0,0 @@
-Source: r-cran-futile.logger
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Andreas Tille <tille at debian.org>
-Section: gnu-r
-Priority: optional
-Build-Depends: debhelper (>= 9),
-               dh-r,
-               r-base-dev,
-               r-cran-lambda.r,
-               r-cran-futile.options
-Standards-Version: 3.9.8
-Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/R/r-cran-futile.logger/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/R/r-cran-futile.logger/
-Homepage: https://cran.r-project.org/package=futile.logger
-
-Package: r-cran-futile.logger
-Architecture: all
-Depends: ${misc:Depends},
-         ${R:Depends}
-Recommends: ${R:Recommends}
-Suggests: ${R:Suggests}
-Description: logging utility for GNU R
- This GNU R package provides a simple yet powerful logging utility. Based
- loosely on log4j, futile.logger takes advantage of R idioms to make logging
- a convenient and easy to use replacement for cat and print statements.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index 0e3735c..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,18 +0,0 @@
-Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Contact: Brian Lee Yung Rowe <r at zatonovo.com>
-Upstream-Name: futile.logger
-Source: https://cran.r-project.org/package=futile.logger
-
-Files: *
-Copyright: 2012-2016 Brian Lee Yung Rowe <r at zatonovo.com>
-License: LGPL-3
-Comment: License is mentioned in the metadata of the file DESCRIPTION
-         and at the download page mentioned above.
-
-Files: debian/*
-Copyright: 2015-2016 Andreas Tille <tille at debian.org>
-License: LGPL-3
-
-License: LGPL-3
- On Debian systems, the complete text of the GNU Lesser General Public
- License version 3 can be found in ‘/usr/share/common-licenses/LGPL-3’.
diff --git a/debian/docs b/debian/docs
deleted file mode 100644
index 960011c..0000000
--- a/debian/docs
+++ /dev/null
@@ -1,3 +0,0 @@
-tests
-debian/README.test
-debian/tests/run-unit-test
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 68d9a36..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/make -f
-
-%:
-	dh $@ --buildsystem R
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/tests/control b/debian/tests/control
deleted file mode 100644
index 051fd72..0000000
--- a/debian/tests/control
+++ /dev/null
@@ -1,3 +0,0 @@
-Tests: run-unit-test
-Depends: @, r-cran-survival, r-cran-testthat
-Restrictions: allow-stderr
diff --git a/debian/tests/run-unit-test b/debian/tests/run-unit-test
deleted file mode 100644
index e13b6b6..0000000
--- a/debian/tests/run-unit-test
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh -e
-
-pkg=r-cran-futile.logger
-if [ "$ADTTMP" = "" ] ; then
-  ADTTMP=`mktemp -d /tmp/${pkg}-test.XXXXXX`
-fi
-cd $ADTTMP
-cp -a /usr/share/doc/${pkg}/tests/* $ADTTMP
-find . -name "*.gz" -exec gunzip \{\} \;
-LC_ALL=C R --no-save < testthat.R
-rm -fr $ADTTMP/*
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index bcbb45d..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,2 +0,0 @@
-version=3
-http://cran.r-project.org/src/contrib/futile.logger_([\d.-]*)\.tar.gz
diff --git a/man/flog.appender.Rd b/man/flog.appender.Rd
new file mode 100644
index 0000000..9761d11
--- /dev/null
+++ b/man/flog.appender.Rd
@@ -0,0 +1,80 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/appender.R
+\name{flog.appender}
+\alias{appender.console}
+\alias{appender.file}
+\alias{appender.tee}
+\alias{flog.appender}
+\title{Manage appenders for loggers}
+\arguments{
+\item{\dots}{Used internally by lambda.r}
+}
+\description{
+Provides functions for adding and removing appenders.
+}
+\section{Usage}{
+
+# Get the appender for the given logger\cr
+flog.appender(name) \%::\% character : Function\cr
+flog.appender(name='ROOT')
+
+# Set the appender for the given logger\cr
+flog.appender(fn, name='ROOT')
+
+# Print log messages to the console\cr
+appender.console()
+
+# Write log messages to a file\cr
+appender.file(file)
+
+# Write log messages to console and a file\cr
+appender.tee(file)
+}
+
+\section{Details}{
+
+Appenders do the actual work of writing log messages to some target.
+To use an appender in a logger, you must register it to a given logger.
+Use \code{flog.appender} to both access and set appenders.
+
+The ROOT logger by default uses \code{appender.console}.
+
+\code{appender.console} is a function that writes to the console.
+No additional arguments are necessary when registering the appender 
+via flog.appender.
+
+
+\code{appender.file} writes to a file, so you must pass an additional file
+argument to the function. To change the file name, just call
+\code{flog.appender(appender.file(file))} again with a new file name.
+
+To use your own appender create a function that takes a single argument,
+which represents the log message. You need to pass a function reference to
+\code{flog.appender}.
+
+\code{appender.tee} writes to both the console and file.
+}
+
+\section{Value}{
+
+When getting the appender, \code{flog.appender} returns the appender
+function.  When setting an appender, \code{flog.appender} has no 
+return value.
+}
+\examples{
+\dontrun{
+flog.appender(appender.console(), name='my.logger')
+
+# Set an appender to the logger named 'my.package'. Any log operations from
+# this package will now use this appender.
+flog.appender(appender.file('my.package.out'), 'my.package')
+}
+}
+\author{
+Brian Lee Yung Rowe
+}
+\seealso{
+\code{\link{flog.logger}} \code{\link{flog.layout}}
+}
+\keyword{data}
+
diff --git a/man/flog.carp.Rd b/man/flog.carp.Rd
new file mode 100644
index 0000000..80b0676
--- /dev/null
+++ b/man/flog.carp.Rd
@@ -0,0 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/logger.R
+\name{flog.carp}
+\alias{flog.carp}
+\title{Always return the log message}
+\arguments{
+\item{carp}{logical Whether to carp output or not}
+
+\item{name}{character The name of the logger}
+}
+\description{
+Indicate whether the logger will always return the log message
+despite the threshold.
+}
+\details{
+This is a special option to allow the return value of the flog.*
+logging functions to return the generated log message even if
+the log level does not exceed the threshold. Note that this 
+minorly impacts performance when enabled. This functionality
+is separate from the appender, which is still bound to the 
+value of the logger threshold.
+}
+\section{Usage}{
+
+# Indicate whether the given logger should carp\cr
+flog.carp(name=ROOT)
+
+# Set whether the given logger should carp\cr
+flog.carp(carp, name=ROOT)
+}
+\examples{
+flog.carp(TRUE)
+x <- flog.debug("Returns this message but won't print")
+flog.carp(FALSE)
+y <- flog.debug("Returns nothing and prints nothing")
+}
+\author{
+Brian Lee Yung Rowe
+}
+\keyword{data}
+
diff --git a/man/flog.layout.Rd b/man/flog.layout.Rd
new file mode 100644
index 0000000..91a2ac8
--- /dev/null
+++ b/man/flog.layout.Rd
@@ -0,0 +1,95 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/layout.R
+\name{flog.layout}
+\alias{flog.layout}
+\alias{layout.format}
+\alias{layout.json}
+\alias{layout.simple}
+\alias{layout.tracearg}
+\title{Manage layouts within the 'futile.logger' sub-system}
+\arguments{
+\item{\dots}{Used internally by lambda.r}
+}
+\description{
+Provides functions for managing layouts. Typically 'flog.layout' is only
+used when manually creating a logging configuration.
+}
+\section{Usage}{
+
+# Get the layout function for the given logger\cr
+flog.layout(name) \%::\% character : Function\cr
+flog.layout(name='ROOT')
+
+# Set the layout function for the given logger\cr
+flog.layout(fn, name='ROOT')
+
+# Decorate log messages with a standard format\cr
+layout.simple(level, msg, ...)
+
+# Generate log messages as JSON\cr
+layout.json(level, msg, ...)
+
+# Decorate log messages using a custom format\cr
+layout.format(format, datetime.fmt="%Y-%m-%d %H:%M:%S")
+
+# Show the value of a single variable
+layout.tracearg(level, msg, ...)
+}
+
+\section{Details}{
+
+Layouts are responsible for formatting messages so they are human-readable.
+Similar to an appender, a layout is assigned to a logger by calling 
+\code{flog.layout}. The \code{flog.layout} function is used internally
+to get the registered layout function. It is kept visible so 
+user-level introspection is possible.
+
+\code{layout.simple} is a pre-defined layout function that 
+prints messages in the following format:\cr
+  LEVEL [timestamp] message
+
+This is the default layout for the ROOT logger.
+
+\code{layout.format} allows you to specify the format string to use 
+in printing a message. The following tokens are available.
+\describe{
+\item{~l}{Log level}
+\item{~t}{Timestamp}
+\item{~n}{Namespace}
+\item{~f}{The calling function}
+\item{~m}{The message}
+}
+
+\code{layout.json} converts the message and any additional objects provided
+to a JSON structure. E.g.:
+
+flog.info("Hello, world", cat='asdf')
+ 
+yields something like
+
+\{"level":"INFO","timestamp":"2015-03-06 19:16:02 EST","message":"Hello, world","func":"(shell)","cat":["asdf"]\}
+
+\code{layout.tracearg} is a special layout that takes a variable
+and prints its name and contents.
+}
+\examples{
+# Set the layout for 'my.package'
+flog.layout(layout.simple, name='my.package')
+
+# Update the ROOT logger to use a custom layout
+layout <- layout.format('[~l] [~t] [~n.~f] ~m')
+flog.layout(layout)
+
+# Create a custom logger to trace variables
+flog.layout(layout.tracearg, name='tracer')
+x <- 5
+flog.info(x, name='tracer')
+}
+\author{
+Brian Lee Yung Rowe
+}
+\seealso{
+\code{\link{flog.logger}} \code{\link{flog.appender}}
+}
+\keyword{data}
+
diff --git a/man/flog.logger.Rd b/man/flog.logger.Rd
new file mode 100644
index 0000000..3254ec8
--- /dev/null
+++ b/man/flog.logger.Rd
@@ -0,0 +1,152 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/logger.R
+\name{flog.logger}
+\alias{flog.debug}
+\alias{flog.error}
+\alias{flog.fatal}
+\alias{flog.info}
+\alias{flog.logger}
+\alias{flog.trace}
+\alias{flog.warn}
+\title{Manage loggers}
+\arguments{
+\item{msg}{The message to log}
+
+\item{name}{The logger name to use}
+
+\item{capture}{Capture print output of variables instead of interpolate}
+
+\item{\dots}{Optional arguments to populate the format string}
+
+\item{expr}{An expression to evaluate}
+
+\item{finally}{An optional expression to evaluate at the end}
+}
+\description{
+Provides functions for writing log messages and managing loggers. Typically
+only the flog.[trace|debug|info|warn|error|fatal] functions need to be used
+in conjunction with flog.threshold to interactively change the log level.
+}
+\section{Usage}{
+
+# Conditionally print a log statement at TRACE log level\cr
+flog.trace(msg, ..., name=flog.namespace(), capture=FALSE)
+
+# Conditionally print a log statement at DEBUG log level\cr
+flog.debug(msg, ..., name=flog.namespace(), capture=FALSE)
+
+# Conditionally print a log statement at INFO log level\cr
+flog.info(msg, ..., name=flog.namespace(), capture=FALSE)
+
+# Conditionally print a log statement at WARN log level\cr
+flog.warn(msg, ..., name=flog.namespace(), capture=FALSE)
+
+# Conditionally print a log statement at ERROR log level\cr
+flog.error(msg, ..., name=flog.namespace(), capture=FALSE)
+
+# Print a log statement at FATAL log level\cr
+flog.fatal(msg, ..., name=flog.namespace(), capture=FALSE)
+
+# Execute an expression and capture any warnings or errors
+ftry(expr, error=stop, finally=NULL)
+}
+
+\section{Additional Usage}{
+
+These functions generally do not need to be called by an end user.
+
+# Get the ROOT logger\cr
+flog.logger()
+
+# Get the logger with the specified name\cr
+flog.logger(name)
+
+# Set options for the given logger\cr
+flog.logger(name, threshold=NULL, appender=NULL, layout=NULL, carp=NULL)
+}
+
+\section{Details}{
+
+These functions represent the high level interface to futile.logger.
+
+The primary use case for futile.logger is to write out log messages. There
+are log writers associated with all the predefined log levels: TRACE, DEBUG,
+INFO, WARN, ERROR, FATAL. Log messages will only be written if the log level
+is equal to or more urgent than the current threshold. By default the ROOT
+logger is set to INFO.
+
+> flog.debug("This won't print") \cr
+> flog.info("But this \%s", 'will') \cr
+> flog.warn("As will \%s", 'this')
+
+Typically, the built in log level constants are used in the call, which
+conform to the log4j levels (from least severe to most severe): TRACE,
+DEBUG, INFO, WARN, ERROR, FATAL. It is not a strict requirement to use these
+constants (any numeric value will work), though most users should find this
+level of granularity sufficient.
+
+Loggers are hierarchical in the sense that any requested logger that is
+undefined will fall back to its most immediate defined parent logger. The
+absolute parent is ROOT, which is guaranteed to be defined for the system
+and cannot be deleted. This means that you can specify a new logger
+directly.
+
+> flog.info("This will fall back to 'my', then 'ROOT'", name='my.logger')
+
+You can also change the threshold or any other setting associated with a
+logger. This will create an explicit logger where any unspecified options
+are copied from the parent logger.
+
+> flog.appender(appender.file("foo.log"), name='my') \cr
+> flog.threshold(ERROR, name='my.logger') \cr
+> flog.info("This won't print", name='my.logger') \cr
+> flog.error("This %s print to a file", 'will', name='my.logger') \cr
+
+If you define a logger that you later want to remove, use flog.remove.
+
+The option 'capture' allows you to print out more complicated data
+structures without a lot of ceremony. This variant doesn't accept format
+strings and instead appends the value to the next line of output. Consider 
+
+> m <- matrix(rnorm(12), nrow=3) \cr
+> flog.info("Matrix:",m, capture=TRUE)
+
+which preserves the formatting, whereas using capture=FALSE will have 
+a cluttered output due to recycling.
+}
+\examples{
+
+flog.threshold(DEBUG)
+flog.debug("This debug message will print")
+
+flog.threshold(WARN)
+flog.debug("This one won't")
+
+m <- matrix(rnorm(12), nrow=3)
+flog.info("Matrix:",m, capture=TRUE)
+
+ftry(log(-1))
+
+\dontrun{
+s <- c('FCX','AAPL','JPM','AMZN')
+p <- TawnyPortfolio(s)
+
+flog.threshold(TRACE,'tawny')
+ws <- optimizePortfolio(p, RandomMatrixDenoiser())
+z <- getIndexComposition()
+
+flog.threshold(WARN,'tawny')
+ws <- optimizePortfolio(p, RandomMatrixDenoiser())
+z <- getIndexComposition()
+
+}
+}
+\author{
+Brian Lee Yung Rowe
+}
+\seealso{
+\code{\link{flog.threshold}} \code{\link{flog.remove}}
+\code{\link{flog.carp}} \code{\link{flog.appender}} \code{\link{flog.layout}}
+}
+\keyword{data}
+
diff --git a/man/flog.remove.Rd b/man/flog.remove.Rd
new file mode 100644
index 0000000..a699970
--- /dev/null
+++ b/man/flog.remove.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/logger.R
+\name{flog.remove}
+\alias{flog.remove}
+\title{Remove a logger}
+\arguments{
+\item{name}{The logger name to use}
+}
+\description{
+In the event that you no longer wish to have a logger registered,
+use this function to remove it. Then any references to this
+logger will inherit the next available logger in the hierarchy.
+}
+\section{Usage}{
+
+# Remove a logger\cr
+flog.remove(name)
+}
+\examples{
+flog.threshold(ERROR, name='my.logger')
+flog.info("Won't print", name='my.logger')
+flog.remove('my.logger')
+flog.info("Will print", name='my.logger')
+}
+\author{
+Brian Lee Yung Rowe
+}
+\keyword{data}
+
diff --git a/man/flog.threshold.Rd b/man/flog.threshold.Rd
new file mode 100644
index 0000000..4af73f3
--- /dev/null
+++ b/man/flog.threshold.Rd
@@ -0,0 +1,38 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/logger.R
+\name{flog.threshold}
+\alias{flog.threshold}
+\title{Get and set the threshold for a logger}
+\arguments{
+\item{threshold}{integer The new threshold for the given logger}
+
+\item{name}{character The name of the logger}
+}
+\description{
+The threshold affects the visibility of a given logger. When a log
+statement is called, e.g. \code{flog.debug('foo')}, futile.logger
+compares the threshold of the logger with the level implied in the
+log command (in this case DEBUG). If the log level is at or higher
+in priority than the logger threshold, a message will print.
+Otherwise the command will silently return.
+}
+\section{Usage}{
+
+# Get the threshold for the given logger\cr
+flog.threshold(name) \%::\% character : character \cr
+flog.threshold(name=ROOT)
+
+# Set the threshold for the given logger\cr
+flog.threshold(threshold, name=ROOT)
+}
+\examples{
+flog.threshold(ERROR)
+flog.info("Won't print")
+flog.threshold(INFO)
+flog.info("Will print")
+}
+\author{
+Brian Lee Yung Rowe
+}
+\keyword{data}
+
diff --git a/man/ftry.Rd b/man/ftry.Rd
new file mode 100644
index 0000000..2aa1031
--- /dev/null
+++ b/man/ftry.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/logger.R
+\name{ftry}
+\alias{ftry}
+\title{Wrap a try block in futile.logger}
+\usage{
+ftry(expr, error = stop, finally = NULL)
+}
+\arguments{
+\item{expr}{The expression to evaluate in a try block}
+
+\item{error}{An error handler}
+
+\item{finally}{Pass-through to tryCatch finally}
+}
+\description{
+This function integrates futile.logger with the error and warning system
+so problems can be caught both in the standard R warning system, while
+also being emitted via futile.logger.
+}
+\examples{
+ftry(log(-1))
+}
+\author{
+Brian Lee Yung Rowe
+}
+\keyword{data}
+
diff --git a/man/futile.logger-package.Rd b/man/futile.logger-package.Rd
new file mode 100644
index 0000000..ff3900b
--- /dev/null
+++ b/man/futile.logger-package.Rd
@@ -0,0 +1,123 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/futile.logger-package.R
+\docType{package}
+\name{futile.logger-package}
+\alias{flog.namespace}
+\alias{futile.logger}
+\alias{futile.logger-package}
+\title{A Logging Utility for R}
+\description{
+This package implements a logging system inspired by log4j. The basic idea
+of layouts, appenders, and loggers is faithful to log4j, while the
+implementation and idiom is all R. This means that support for hierarchical
+loggers, custom appenders, custom layouts is coupled with a simple and
+intuitive functional syntax.
+}
+\details{
+\tabular{ll}{
+Package: \tab futile.logger\cr
+Type: \tab Package\cr
+Version: \tab 1.4.3\cr
+Date: \tab 2016-07-10\cr
+License: \tab LGPL-3\cr
+LazyLoad: \tab yes\cr
+}
+
+The latest version of futile.logger introduces zero-configuration semantics
+out of the box. This means that you can use the default configuration as is.
+It is also easy to interactively change the configuration of the ROOT
+logger, as well as create new loggers. Since loggers form a hierarchy based
+on their name, the ROOT logger is the starting point of the hierarchy and
+always exists. By default the ROOT logger is defined with a simple layout,
+printing to the console, with an INFO threshold. This means that writing to
+any logger with a threshold of INFO or higher will write to the console.
+
+All of the logging functions take a format string so it is easy to add
+arbitrary values to log messages.
+
+> flog.info("This song is just \%s words \%s", 7, "long")
+
+Thresholds range from most verbose to least verbose: TRACE, DEBUG, INFO,
+WARN, ERROR, FATAL. You can easily change the threshold of the ROOT logger
+by calling > flog.threshold(TRACE) which changes will print all log messages
+from every package. To suppress most logging by default but turn on all
+debugging for a logger 'my.logger', you would execute
+
+> flog.threshold(ERROR)\cr
+> flog.threshold(TRACE, name='my.logger')
+
+Any arbitrary logger can be defined simply by specifying it in any
+futile.logger write operation (futile.threshold, futile.appender,
+futile.layout). If the logger hasn't been defined, then it will be defined
+dynamically. Any unspecified options will be copied from the parent logger.
+
+When writing log messages, futile.logger will search the hierarchy based on
+the logger name. In our example, if 'my.logger' hasn't been defined then
+futile.logger will look for a logger named 'my' and finally the ROOT logger.
+
+Functions calling futile.logger from a package are automatically assigned a
+logger that has the name of the package. Suppose we have log messages in a
+package called 'my.package'. Then any function that calls futile.logger from
+within the package will automatically be assigned a default logger of
+'my.package' instead of ROOT. This means that it is easy to change the log
+setting of any package that uses futile.logger for logging by just updating
+the logger for the given package. For instance suppose you want to output
+log message for my.package to a file instead.
+
+> flog.appender(appender.file('my.package.log'), name='my.package')
+
+Now all log statements in the package my.package will be written to a file
+instead of the console. All other log messages will continue to be written
+to the console.
+
+Appenders do the actual work of writing log messages to a writeable target,
+whether that is a console, a file, a URL, database, etc. When creating an
+appender, the implementation-specific options are passed to the appender at
+instantiation. The package defines two appender generator functions:
+
+\describe{
+  \item{appender.file}{Write to a file}
+  \item{appender.console}{Write to the console}
+}
+
+Each of these functions returns the actual appender function, so be sure to
+actually call the function!
+
+Layouts are responsible for formatting messages. This operation usually
+consists of adding the log level, a timestamp, plus some pretty-printing to
+make the log messages easy on the eyes. The package supplies several layouts:
+
+\describe{
+  \item{layout.simple}{Writes messages with a default format}
+  \item{layout.json}{Generates messages in a JSON format}
+  \item{layout.format}{Define your own format}
+  \item{layout.tracearg}{Print a variable name along with its value}
+}
+}
+\examples{
+
+flog.debug("This \%s print", "won't")
+flog.warn("This \%s print", "will")
+  
+flog.info("This inherits from the ROOT logger", name='logger.a')
+flog.threshold(DEBUG, name='logger.a')
+flog.debug("logger.a has now been set to DEBUG", name='logger.a')
+flog.debug("But the ROOT logger is still at INFO (so this won't print)")
+
+\dontrun{
+flog.appender(appender.file("other.log"), name='logger.b')
+flog.info("This writes to a \%s", "file", name='logger.b')
+}
+
+}
+\author{
+Brian Lee Yung Rowe <r at zatonovo.com>
+}
+\seealso{
+\code{\link{flog.logger}}, \code{\link{flog.threshold}},
+\code{\link{flog.layout}}, \code{\link{flog.appender}}
+}
+\keyword{attribute}
+\keyword{logic}
+\keyword{package}
+
diff --git a/man/logger.options.Rd b/man/logger.options.Rd
new file mode 100644
index 0000000..fb7c31e
--- /dev/null
+++ b/man/logger.options.Rd
@@ -0,0 +1,36 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/options.R
+\name{logger.options}
+\alias{DEBUG}
+\alias{ERROR}
+\alias{FATAL}
+\alias{INFO}
+\alias{TRACE}
+\alias{WARN}
+\alias{logger.options}
+\title{Constants for 'futile.logger'}
+\usage{
+logger.options(..., simplify = FALSE, update = list())
+}
+\arguments{
+\item{...}{TODO}
+
+\item{simplify}{TODO}
+
+\item{update}{TODO}
+}
+\description{
+Log level constants and the logger options.
+}
+\details{
+The logging configuration is managed by 'logger.options', a function
+generated by OptionsManager within 'futile.options'.
+}
+\author{
+Brian Lee Yung Rowe
+}
+\seealso{
+\code{futile.options}
+}
+\keyword{data}
+
diff --git a/man/scat.Rd b/man/scat.Rd
new file mode 100644
index 0000000..a7d8472
--- /dev/null
+++ b/man/scat.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/scat.R
+\name{scat}
+\alias{scat}
+\title{Print formatted messages}
+\usage{
+scat(format, ..., use.newline = TRUE)
+}
+\arguments{
+\item{format}{A format string passed to sprintf}
+
+\item{use.newline}{Whether to append a new line at the end}
+
+\item{\dots}{Arguments to pass to sprintf for dereferencing}
+}
+\value{
+A formatted string printed to the console
+}
+\description{
+A replacement for \code{cat} that has built-in sprintf formatting
+}
+\details{
+Like \code{cat} but you can use format strings.
+}
+\examples{
+
+  apply(array(2:5),1, function(x) scat('This has happened \%s times', x) )
+
+}
+\author{
+Brian Lee Yung Rowe
+}
+\keyword{data}
+
diff --git a/tests/testthat.R b/tests/testthat.R
new file mode 100644
index 0000000..48aab7f
--- /dev/null
+++ b/tests/testthat.R
@@ -0,0 +1,3 @@
+library(testthat)
+library(futile.logger)
+test_check("futile.logger")
diff --git a/tests/testthat/test_debug.R b/tests/testthat/test_debug.R
new file mode 100644
index 0000000..b90495b
--- /dev/null
+++ b/tests/testthat/test_debug.R
@@ -0,0 +1,20 @@
+context("debug level logging")
+
+test_that("debug level is logged", {
+    flog.threshold(DEBUG)
+    expect_output(flog.debug("testlog"), "testlog")
+})
+
+test_that("higher levels are logged", {
+    testlog = paste0("testlog ", sample(100, 1))
+    flog.threshold(DEBUG)
+    expect_output(flog.info(testlog), testlog)
+    expect_output(flog.warn(testlog), testlog)
+    expect_output(flog.error(testlog), testlog)
+    expect_output(flog.fatal(testlog), testlog)
+})
+
+test_that("lower levels are not logged", {
+    flog.threshold(DEBUG)
+    expect_silent(flog.trace("testlog"))
+})
diff --git a/tests/testthat/test_json.R b/tests/testthat/test_json.R
new file mode 100644
index 0000000..f8051c7
--- /dev/null
+++ b/tests/testthat/test_json.R
@@ -0,0 +1,44 @@
+if (requireNamespace("jsonlite", quietly=TRUE)) {
+context("JSON: typical usage")
+flog.threshold(INFO)
+flog.layout(layout.json)
+
+test_that("simple string", {
+  raw <- capture.output(flog.info("log message"))
+  aslist <- jsonlite::fromJSON(raw)
+  expect_equal(aslist$level, "INFO")
+  expect_equal(aslist$message, "log message")
+
+  ts <- strptime(aslist$timestamp, "%Y-%m-%d %H:%M:%S %z")
+  expect_true('POSIXt' %in% class(ts))
+})
+
+test_that("additional objects", {
+  raw <- capture.output(
+    flog.info("log message", pet="hamster", weight=12, stuff=c("a", "b")))
+  aslist <- jsonlite::fromJSON(raw)
+  expect_equal(aslist$level, "INFO")
+  expect_equal(aslist$message, "log message")
+  expect_equal(aslist$pet, "hamster")
+  expect_equal(aslist$weight, 12)
+  expect_equal(aslist$stuff, c("a", "b"))
+})
+
+context("JSON: NULL values")
+
+#test_that("NULL message", {
+#  raw <- capture.output(flog.info(NULL))
+#  aslist <- fromJSON(raw)
+#  expect_equal(length(aslist$message), 0)
+#})
+
+test_that("NULL additional objects", {
+  raw <- capture.output(flog.info("log message", nullthing=NULL))
+  aslist <- jsonlite::fromJSON(raw)
+  expect_equal(length(aslist$nullthing), 0)
+})
+
+# knockdown
+flog.layout(layout.simple)
+
+}
diff --git a/tests/testthat/test_layout.R b/tests/testthat/test_layout.R
new file mode 100644
index 0000000..d708ce0
--- /dev/null
+++ b/tests/testthat/test_layout.R
@@ -0,0 +1,55 @@
+# :vim set ff=R
+context("format string")
+test_that("Embedded format string", {
+  flog.threshold(INFO)
+  raw <- capture.output(flog.info("This is a %s message", "log"))
+  #cat("\n[test.default] Raw:",raw,"\n")
+  expect_that(length(grep('INFO', raw)) > 0, is_true())
+  expect_that(length(grep('log message', raw)) > 0, is_true())
+})
+
+test_that("Custom layout dereferences level field", {
+  flog.threshold(INFO)
+  flog.layout(layout.format('xxx[~l]xxx'))
+  raw <- capture.output(flog.info("log message"))
+  flog.layout(layout.simple)
+  expect_that('xxx[INFO]xxx' == raw, is_true())
+  expect_that(length(grep('log message', raw)) == 0, is_true())
+})
+
+context("null values")
+test_that("Raw null value is printed", {
+  raw <- capture.output(flog.info('xxx[%s]xxx', NULL))
+  expect_that(length(grep('xxx[NULL]xxx', raw, fixed=TRUE)) == 1, is_true())
+})
+
+test_that("Single null value is printed", {
+  opt <- list()
+  raw <- capture.output(flog.info('xxx[%s]xxx', opt$noexist))
+  expect_that(length(grep('xxx[NULL]xxx', raw, fixed=TRUE)) == 1, is_true())
+})
+
+test_that("Null is printed amongst variables", {
+  opt <- list()
+  raw <- capture.output(flog.info('aaa[%s]aaa xxx[%s]xxx', 3, opt$noexist))
+  expect_that(length(grep('aaa[3]aaa', raw, fixed=TRUE)) == 1, is_true())
+  expect_that(length(grep('xxx[NULL]xxx', raw, fixed=TRUE)) == 1, is_true())
+})
+
+context("sprintf arguments")
+test_that("no extra arguments are passed", {
+  expect_true(grepl('foobar$', capture.output(flog.info('foobar'))))
+  expect_true(grepl('foobar %s$', capture.output(flog.info('foobar %s'))))
+  expect_true(grepl('10%$', capture.output(flog.info('10%'))))
+})
+test_that("some extra arguments are passed", {
+  expect_true(grepl('foobar$', capture.output(flog.info('foobar', pi))))
+  expect_true(grepl(
+    'foobar foo$',
+    capture.output(flog.info('foobar %s', 'foo'))))
+  expect_true(grepl('100$', capture.output(flog.info('10%d', 0))))
+  expect_true(
+    grepl('foo and bar equals to foobar',
+          capture.output(
+            flog.info('%s and %s equals to %s', 'foo', 'bar', 'foobar'))))
+})
diff --git a/tests/testthat/test_logger.R b/tests/testthat/test_logger.R
new file mode 100644
index 0000000..c024aa9
--- /dev/null
+++ b/tests/testthat/test_logger.R
@@ -0,0 +1,86 @@
+context("root logger")
+test_that("Default settings", {
+  flog.threshold(INFO)
+  raw <- capture.output(flog.info("log message"))
+  #cat("\n[test.default] Raw:",raw,"\n")
+  expect_that(length(grep('INFO', raw)) > 0, is_true())
+  expect_that(length(grep('log message', raw)) > 0, is_true())
+})
+
+test_that("Change root threshold", {
+  flog.threshold(ERROR)
+  raw <- capture.output(flog.info("log message"))
+  #cat("\n[test.change_threshold] Raw:",raw,"\n")
+  expect_that(length(raw) == 0, is_true())
+})
+
+
+context("capture output")
+test_that("Capture works as expected", {
+  flog.threshold(INFO)
+  raw <- capture.output(flog.info("log message", head(cars), capture = TRUE))
+  expect_that(length(raw) == 9, is_true())
+  expect_that(grepl('^INFO',raw[1]), is_true())
+  expect_that(nchar(raw[2]) == 0, is_true())
+  expect_that(grepl('dist$',raw[3]), is_true())
+  })
+
+context("new logger")
+test_that("Create new logger", {
+  flog.threshold(ERROR)
+  flog.threshold(DEBUG, name='my.package')
+  raw.root <- capture.output(flog.info("log message"))
+  raw.mine <- capture.output(flog.info("log message", name='my.package'))
+  expect_that(length(raw.root) == 0, is_true())
+  expect_that(length(grep('INFO', raw.mine)) > 0, is_true())
+  expect_that(length(grep('log message', raw.mine)) > 0, is_true())
+})
+
+context("logger hierarchy")
+test_that("Hierarchy is honored", {
+  flog.threshold(ERROR)
+  flog.threshold(TRACE, name='my')
+  flog.threshold(DEBUG, name='my.package')
+  raw.root <- capture.output(flog.info("log message"))
+  raw.l1 <- capture.output(flog.trace("log message", name='my'))
+  raw.l2 <- capture.output(flog.trace("log message", name='my.package'))
+  expect_that(length(raw.root) == 0, is_true())
+  expect_that(length(grep('TRACE', raw.l1)) > 0, is_true())
+  expect_that(length(grep('log message', raw.l1)) > 0, is_true())
+  expect_that(length(raw.l2) == 0, is_true())
+})
+
+test_that("Hierarchy inheritance", {
+  flog.remove('my.package')
+  flog.remove('my')
+  flog.threshold(ERROR)
+  flog.threshold(TRACE, name='my')
+  raw.root <- capture.output(flog.info("log message"))
+  raw.l1 <- capture.output(flog.trace("log message", name='my'))
+  raw.l2 <- capture.output(flog.trace("log message", name='my.package'))
+  expect_that(length(raw.root) == 0, is_true())
+  expect_that(length(grep('TRACE', raw.l1)) > 0, is_true())
+  expect_that(length(grep('log message', raw.l1)) > 0, is_true())
+  expect_that(length(grep('TRACE', raw.l2)) > 0, is_true())
+  expect_that(length(grep('log message', raw.l2)) > 0, is_true())
+})
+
+
+# Can't test this since test_that calls suppressMessages
+#context("package loggers")
+#test_that("ftry captures warnings", {
+  #raw <- capture.output(ftry(log(-1)))
+  #cat("raw =",raw,'\n')
+  #expect_that(length(grep('WARN', raw)) > 0, is_true())
+  #expect_that(length(grep('simpleWarning', raw)) > 0, is_true())
+#})
+
+context("carp")
+test_that("carp returns output", {
+  expect_that(flog.carp(), is_false())
+  flog.carp(TRUE)
+  flog.threshold(WARN)
+  raw <- flog.debug("foo")
+  expect_that(length(grep('DEBUG', raw)) > 0, is_true())
+})
+
diff --git a/tests/testthat/test_stringconfig.R b/tests/testthat/test_stringconfig.R
new file mode 100644
index 0000000..f41b807
--- /dev/null
+++ b/tests/testthat/test_stringconfig.R
@@ -0,0 +1,49 @@
+context("String configuration")
+
+test_that("trace threshold is set via string", {
+    flog.threshold("trace")
+    expect_equal(flog.threshold(), "TRACE")
+    expect_equivalent(flog.logger()$threshold, 9)
+    flog.threshold("TRACE")
+    expect_equal(flog.threshold(), "TRACE")
+})
+
+test_that("debug threshold is set via string", {
+    flog.threshold("debug")
+    expect_equal(flog.threshold(), "DEBUG")
+    expect_equivalent(flog.logger()$threshold, 8)
+    flog.threshold("DEBUG")
+    expect_equal(flog.threshold(), "DEBUG")
+})
+
+test_that("info threshold is set via string", {
+    flog.threshold("info")
+    expect_equal(flog.threshold(), "INFO")
+    expect_equivalent(flog.logger()$threshold, 6)
+    flog.threshold("INFO")
+    expect_equal(flog.threshold(), "INFO")
+})
+
+test_that("warn threshold is set via string", {
+    flog.threshold("warn")
+    expect_equal(flog.threshold(), "WARN")
+    expect_equivalent(flog.logger()$threshold, 4)
+    flog.threshold("WARN")
+    expect_equal(flog.threshold(), "WARN")
+})
+
+test_that("error threshold is set via string", {
+    flog.threshold("error")
+    expect_equal(flog.threshold(), "ERROR")
+    expect_equivalent(flog.logger()$threshold, 2)
+    flog.threshold("ERROR")
+    expect_equal(flog.threshold(), "ERROR")
+})
+
+test_that("fatal threshold is set via string", {
+    flog.threshold("fatal")
+    expect_equal(flog.threshold(), "FATAL")
+    expect_equivalent(flog.logger()$threshold, 1)
+    flog.threshold("FATAL")
+    expect_equal(flog.threshold(), "FATAL")
+})
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-futile.logger.git



More information about the debian-med-commit mailing list