Configuration

Brandon Moretz

2022-03-12

library(dyn.log)

Configurations

The dyn.log package is configuration-driven to provide the client flexibility with both the look & feel of log layouts/formatting (aesthetics), and how it interacts with its container environment. This vignette will focus less on how the logger works, and more on how to make it work for you.

There are two main ways to set the logging configuration:

  • Explicitly pass a configuration file to the init_logger function.
    • init_logger(file_path = “~/package/config.yaml”)
  • Global Option
    • options(“dyn.log.config” = “~/package/config.yaml”)

If you work on a large project (i.e., comprised of many packages) setting the global option in your .Rprofile is the suggested approach; it will save you a great deal of time.

Templates

The package comes with a few preconfigured logging setups that are common starting points for your bespoke configuration. To see the configuration templates call the get_configurations method:

configs <- get_configurations()
configs
default knitr object
default.yaml knitr.yaml object.yaml

This will return a list of configurations and the yaml file path on your local system.

There are two main ways to utilize these ‘OTB’ templates, both having the basic idea of copying a pre-configured template from the package to your local working environment, and then setting the logger to look for that file going-forward.

Package

To use a custom logging configuration in a package:

file.copy(from = configs$default, to = "inst/logging.yaml")

Which will make the new template part of your package, to load it, you can:

Put this in your packages’ “.onLoad” handler:

init_logger(config_file = system.file("logging.yaml", package = "yourpkg"))

Or, put this line in your .Rprofile, and whenver you attach the dyn.log package, the logger will automatically load your custom configuration.

options("dyn.log.config" = system.file("logging.yaml", package = "yourpkg"))

Script Directory

Usage of a custom configuration in a directory/project is basically the same:

# use here to get your workspace root
file.copy(from = configs$default, 
          to = file.path(here::here(), "logging.yaml"))

then in your .Rprofile:

library(dyn.log)

init_logger("logging.yaml")

Or,

options("dyn.log.config" = file.path(here::here(), "logging.yaml"))

And whenever the dyn.log package gets attached, it will be auto-configure for you.

Integration

Now we’ll take look at customizing log layouts for types in your project.

If your client code leverages the use of R6 classes, you can specify a custom layout for standalone objects or objects that are part of a hierarchy by using the association property. For example, TestObject is a helper object defined in the testthat folder of dyn.log:

The object hierarchy is declared as follows:

TestObject <- R6::R6Class(
  classname = "TestObject",

  public = list(

    cls_name = NULL,

    initialize = function() {
      private$id <- private$generate_id()
      self$cls_name <- private$get_class_name()
    },

    identifier = function() {
      invisible(private$id)
    },

    class_name = function() {
      invisible(self$cls_name)
    },

    invoke_logger = function() {
      a <- "test"; b <- 123; c <- 100L

      Logger$trace("these are some variables: {a} - {b} - {c}")
    }
  ),

  private = list(
    id = NULL,

    generate_id = function(n = 15) {
      paste0(sample(LETTERS, n, TRUE), collapse =  "")
    },

    get_class_name = function() {
      calls <- as.character(sys.calls())
      calls <- calls[max(which(stringr::str_detect(calls, "\\$new\\(.*\\)")))]
      stopifnot(length(calls) == 1)
      invisible(stringr::str_remove(calls, "\\$new\\(.*\\)"))
    }
  )
)

DerivedTestObject <- R6::R6Class(
  classname = "DerivedTestObject",
  inherit = TestObject,
  public = list(

    initialize = function() {
      super$initialize()
    },

    invoke_logger = function() {
      a <- "derived test"; b <- 321; c <- 200L

      Logger$trace("variables in derived: {a} - {b} - {c}")
    }
  )
)

The layout configuration for “TestObject” looks like this:

layouts:
- association: TestObject
  seperator: ' '
  new_line: \n
  formats: !expr list(
           new_fmt_literal(crayon::cyan$bold, 'Class:'),
           new_fmt_cls_field(crayon::bgCyan$silver$bold, 'cls_name'),
           new_fmt_literal(crayon::cyan$bold, 'Object Id:'),
           new_fmt_cls_field(crayon::bgCyan$silver$bold, 'id'),
           new_fmt_line_break(),
           new_fmt_log_level(),
           new_fmt_timestamp(crayon::silver$italic),
           new_fmt_exec_scope(crayon::magenta$bold, 'calling_fn'),
           new_fmt_log_msg())
init_logger(configs$object)

test_obj <- DerivedTestObject$new()

test_obj$invoke_logger()
Class: DerivedTestObject Object Id: NMYSHIYQMBWMXJI
TRACE [03/12/2022 18:53:50 -0500] test_obj$invoke_logger variables in derived: derived test - 321 - 200

As you can see, the logger knows to log contextual information about any object derived from “TestObject.” TestObject exposes two properties, one public and one private:

  • id: private property which is a random string unique to the object instance.
  • cls_name”: a public property which is calculated when the object is instantiated to get the correct run-time class name (since it is derived).

This can be a powerful mechanic for templatizing important contextual information in your client applications or scripts.