logging
logging.Rmd
How to use it
You can start using the logger simply by activating it and sending a message.
dv.manager::log_activate()
rlang::inform("Logged message")
dv.manager::log_deactivate()
In its default state, the logger will capture and decorate any
‘message’ or ‘warning’ condition. Simply put, any message you send using
message
, warning
, rlang::inform
and rlang::warn
. Usually we will favour the
rlang
functions as adding fields to use in our logger is
simpler.
Using logging levels
The logger can have different print granularities depending on the logging level selected. A different level can be specified in each of the logging messages sent by including a logging level field in it.
dv.manager::log_activate(dv.manager:::log_default_handlers(level = "info"))
rlang::inform("This will be printed", level = "info")
rlang::inform("This will not", level = "debug")
dv.manager::log_deactivate()
The logging levels and their numerical value can be checked with:
dv.manager::log_get_levels()
Logging messages sent from your module
A common use case will be using the logger to send messages from
inside your module. To do this you can include the package name as an
argument when you send the message. This can be done in a simple way
using rlang
dv.manager::log_activate()
rlang::inform("An inform with package name", package = "my_package")
dv.manager::log_deactivate()
You can use the packageName
function to dynamically get
the name of your package.
You can also call log_use_log
from
dv.manager
in the root of the package containing the
module. This will add a file to your R/
directory with two
convenience functions that automatically add the package name.
# Will create an R/utils_logging.R file with two convenience functions
dv.manager::log_use_log()
devtools::load_all()
dv.manager::log_activate()
log_inform("An inform with package name")
log_warning("A warning with package name")
dv.manager::log_deactivate()
Advanced logging
dv.manager
logging system is based on R’s condition system.
This logging system simply specifies a set of
globalCallingHandlers
that will capture and log the
messages, warnings and/or errors as specified by the handlers.
The default handlers
This is a low level logging and that makes it very flexible.
By default, the condition will contain the same fields as any
standard rlang::inform
or rlang::warn
being
the most important:
- message: [character(1)] The message to be logged
It expects that the module developer has added the fields:
- package: [character(1)] The name of the package that sent the
condition (added automatically if
log_info
andlog_warn
were used). - level: [numeric(1)] a level for logging granularity.
The handlers will try to add the following fields to the condition before formatting:
- date: [character(1)] A timestamp for the message.
- ns: [character(1)] The namespace of the module that sent the condition.
- sess_id: [character(1)] The Shiny session id that sent the condition.
- short_sess_id: [character(1)] A truncated version of the sess_id to improve human-readibility.
The default handlers present and format this information.
library(magrittr)
log_default_handlers <- function(level = 999) {
format_str <- "[{date}][{package}|{short_sess_id}|{ns}]:{message}"
cnd_to_str <- function(cnd) {
cnd %>%
dv.manager::log_add_date() %>%
dv.manager::log_add_ns() %>%
dv.manager::log_add_sess_id() %>%
dv.manager::log_add_short_sess_id() %>%
dv.manager::log_format(format_str)
}
list(
message = function(cnd) {
if (dv.manager::log_check_print(cnd, level)) cli::cli_alert_info(cnd_to_str(cnd))
rlang::cnd_muffle(cnd)
},
warning = function(cnd) {
if (dv.manager::log_check_print(cnd, level)) cli::cli_alert_warning(cnd_to_str(cnd))
rlang::cnd_muffle(cnd)
}
)
}
The log_add*
functions add extra fields to the condition
that can be used later by log_format()
to create a logging
message. To print these messages we use the cli
package. The
log_format
function is a convenience function that allows
quick formatting of the condition. It allows using a glue
-like string
where the parameters are fields in the condition. If a field used in the
format is not present an NA
value is returned instead.
Creating custom handlers
The default handlers are convenient, but the logging behavior can be modified by using a different set of handlers…
library(magrittr)
my_handlers <- function() {
format_str <- "Date:{date}|Pkg:{package}|{custom_field}:{message}"
cnd_to_str <- function(cnd) {
cnd %>%
dv.manager::log_add_date() %>%
dv.manager::log_format(format_str)
}
list(
message = function(cnd) {
if (cnd[["print_me"]]) cli::cli_alert_info(cnd_to_str(cnd))
rlang::cnd_muffle(cnd)
},
warning = function(cnd) {
cli::cli_alert_warning(cnd_to_str(cnd))
rlang::cnd_muffle(cnd)
}
)
}
dv.manager::log_activate(my_handlers())
dv.manager:::log_inform("I will print this and its custom_field will be 1", print_me = TRUE, custom_field = "1")
dv.manager:::log_inform("I will print this and its custom_field will be NA", print_me = TRUE)
dv.manager:::log_inform("I will not print this", print_me = FALSE)
dv.manager::log_deactivate()
As you can see we can add new fields for logging, we can supress some messages depending on the fields, etc. This allows filtering logging messages by package, session, logging level, etc. So it adapts to our particular need during development.
Troubleshooting
- When
log_activate
is called, an error similar to the following appears:
Error in globalCallingHandlers(…) : should not be called with handlers on the stack
The error above means you are activating the logger from an
environment that already has defined some calling handlers, usually with
withCallingHandlers
. shiny
in particular does
this when an app starts, therefore you cannot activate logging once you
are inside the application. The logger is based on setting
globalCallingHandlers
and setting those is tricky within
other functions. The recommendation is to make the call to
log_activate
in your root session and not within
functions.