1 Introduction

lgr is a logging framework for R inspired by Apache Log4j and Python logging. It follows an object oriented design implemented through R6 classes. This enables lgr to have a larger set of features than other logging packages for R, and makes it flexible and easy to extend.

If you are not sure if lgr is the right package for you, take a look examples to see what lgr can do for you.

1.1 Quickstart

lgr comes with a so-called root logger that is ready to go after you install it. If you are a package developer you can (and should) set up a new Logger for your package, but more on that later. For many use cases the root Logger will suffice.

1.1.2 Logging to plaintext files

Usually you don’t (only) want to log to the console, you want to log to files. Output of Loggers is managed by Appenders. The root Logger is preconfigured with a console Appender (that is why we see output in the console). Let’s add a file Appender:

1.1.5 What Else

If the examples above have piqued your interest, the rest of this vignette will provide more details on the workings of lgr. Discussing all Appenders and configuration options is beyond the scope of this vignette, please refer to the function reference for that.

2 Usage

2.1 Structure of the logging system

If you want custom logging configurations, you have to understand the structure of the logging process.

• A Logger collects information and dispatches it to its Appenders, and also the Appenders of its Parent Loggers (also see the section on hierarchical logging)
• An Appender writes the log message to destination (the console, a file, a database, etc…).
• A Layout is used by an Appender to format LogEvents. For example, AppenderFile uses LayoutFormat by default to write human readable log events to a text file, but can also use LayoutJson produce machine readable JSON lines logfiles.
• LogEvents are produced by the Logger and dispatched to Appenders. They contain all the information that is being logged (think of it as a row in table). LogEvents usually contain the log level, a timestamp, a message, the name of the calling function, and a reference to the Logger that created it. In addition, a LogEvent can contain any number of custom fields, but not all Appenders/Layouts support custom fields. For example if you use AppenderFormat with LayoutFormat you can only use the standard fields in your log message, while LayoutJson supports custom fields in a quite natural manner. See examples 1 & 2

2.1.1 On R6 classes

The elements described above are R6 classes. R6 is an object orientation system for R that is used by many popular packages such as shiny, dplyr, plumber, roxygen2, and testthat but often behind the scenes and not as exposed as in lgr.

You recognize R6 classes in this package because they are named following the UpperCamelCase naming convention. While there is only one kind of Logger and one kind of LogEvent, there are several subclasses of Appenders and Layouts.

An introduction to R6 classes is beyond the scope of this document, but you can find the official documentation here and there is also this talk on Youtube. In short R6 classes store data (fields) together with functions (methods) and have to be instantiated with <classname>$new(). So if you want to create a new AppenderFile, you do this by calling AppenderFile$new(file = tempfile()).

3.3 With YAML or JSON

You can use YAML and JSON config files with lgr.

You can also pass in YAML/JSON directly as a character string (or vector with one element per line)

4 Examples

4.1 Logging to the console

lgr comes with simple but powerful formatting syntax for LogEvents. Please refer to ?format.LogEvent for the full list of available placeholders.

If this is not enough for you, you can use LayoutGlue based on the awesome glue package. The syntax is a bit more verbose, and AppenderGlue is a bit less performant than AppenderFormat, but the possibilities are endless.

All fields of the [LogEvent] object are exposed through LayoutGlue, so please refer to ?LogEvent for a list of all available Fields.

4.2 Logging to JSON files

JavaScript Object Notation (JSON) is an open-standard file format that uses human-readable text to transmit data objects consisting of attribute–value pairs and array data types (Wikipedia). JSON is the recommended text-based logging format when logging to files 1, as it is human- as well as machine readable. You should only log to a different format if you have very good reasons for it. The easiest way to log to JSON files is with AppenderJson2

4.6 Logging to databases

Logging to databases is simple, though a few aspects can be tricky to configure based on the backend used. For performance reasons database inserts are buffered by default. This works exactly identical as described above for AppenderBuffer. If you want to write each LogEvent directly to the database, just set the buffer size to 0.

4.7 Inject values into LogEvents

By abusing Filters, lgr can modify LogEvents as they are processed. One example for when this is useful is assigning a grouping identifier to a series of log calls.

with_log_value() provides a convenient wrapper to inject values into log calls.

An alternative way to achieve the same is to use one of the preconfigured Filters that come with lgr. This approach is more more comfortable for use within functions.

The result is the same in both cases:

#> INFO  [19:14:54.801] cleaning data {dataset_id: dataset1}
#> INFO  [19:14:54.802] processing data {dataset_id: dataset1}
#> INFO  [19:14:54.802] outputing data {dataset_id: dataset1}

You can use with_log_level() and FilterForceLevel in a similar fashion to modify the log level of events conveniently.

4.8 Temporarily disable logging

Temporary disabling logging for portions of code is straight forward and easy with lgr:

4.9 Adding a custom logger to a package

If you are a package author, it is good practice to define a separate logger for your package. This gives users the ability to easily enable/disable logging on a per-package basis. Loggers must be initialized in the packages .onLoad hook. You can do this by adding the following code to any .R file inside the R/ directory of your package:

You can also just use lgr::use_logger() to generate the appropriate code for your package automatically.

After you set this up you can use lg$fatal(), lg$info(), etc… inside your package. You do not have to define any appenders, since all log events will get redirected to the root Logger (see Inheritance).

5 References

Python Logging

Eric Stenbock: The True Story of A Vampire

1. Technically, the logger does not produce standard JSON files but JSON lines

2. AppenderJson is just an AppenderFile with LayotJson as default Layout and a few extra features