| 1 |
#' Registers a logger instance in a given logging namespace. |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' |
|
| 5 |
#' @note It's a thin wrapper around the `logger` package. |
|
| 6 |
#' |
|
| 7 |
#' @details Creates a new logging namespace specified by the `namespace` argument. |
|
| 8 |
#' When the `layout` and `level` arguments are set to `NULL` (default), the function |
|
| 9 |
#' gets the values for them from system variables or R options. |
|
| 10 |
#' When deciding what to use (either argument, an R option or system variable), the function |
|
| 11 |
#' picks the first non `NULL` value, checking in order: |
|
| 12 |
#' 1. Function argument. |
|
| 13 |
#' 2. System variable. |
|
| 14 |
#' 3. R option. |
|
| 15 |
#' |
|
| 16 |
#' `layout` and `level` can be set as system environment variables, respectively: |
|
| 17 |
#' * `teal.log_layout` as `TEAL.LOG_LAYOUT`, |
|
| 18 |
#' * `teal.log_level` as `TEAL.LOG_LEVEL`. |
|
| 19 |
#' |
|
| 20 |
#' If neither the argument nor the environment variable is set the function uses the following R options: |
|
| 21 |
#' * `options(teal.log_layout)`, which is passed to [logger::layout_glue_generator()], |
|
| 22 |
#' * `options(teal.log_level)`, which is passed to [logger::log_threshold()] |
|
| 23 |
#' |
|
| 24 |
#' |
|
| 25 |
#' The logs are output to `stdout` by default. Check `logger` for more information |
|
| 26 |
#' about layouts and how to use `logger`. |
|
| 27 |
#' |
|
| 28 |
#' @seealso The package vignettes for more help: `browseVignettes("teal.logger")`.
|
|
| 29 |
#' |
|
| 30 |
#' @param namespace (`character(1)` or `NA`)\cr |
|
| 31 |
#' the name of the logging namespace |
|
| 32 |
#' @param layout (`character(1)`)\cr |
|
| 33 |
#' the log layout. Alongside the standard logging variables provided by the `logging` package |
|
| 34 |
#' (e.g. `pid`) the `token` variable can be used which will write the last 8 characters of the |
|
| 35 |
#' shiny session token to the log. |
|
| 36 |
#' @param level (`character(1)` or `call`) the log level. Can be passed as |
|
| 37 |
#' character or one of the `logger`'s objects. |
|
| 38 |
#' See [logger::log_threshold()] for more information. |
|
| 39 |
#' |
|
| 40 |
#' @return `invisible(NULL)` |
|
| 41 |
#' @export |
|
| 42 |
#' |
|
| 43 |
#' @examples |
|
| 44 |
#' options(teal.log_layout = "{msg}")
|
|
| 45 |
#' options(teal.log_level = "ERROR") |
|
| 46 |
#' register_logger(namespace = "new_namespace") |
|
| 47 |
#' \donttest{
|
|
| 48 |
#' logger::log_info("Hello from new_namespace", namespace = "new_namespace")
|
|
| 49 |
#' } |
|
| 50 |
#' |
|
| 51 |
register_logger <- function(namespace = NA_character_, |
|
| 52 |
layout = NULL, |
|
| 53 |
level = NULL) {
|
|
| 54 | 9x |
if (!((is.character(namespace) && length(namespace) == 1) || is.na(namespace))) {
|
| 55 | 1x |
stop("namespace argument to register_logger must be a scalar character or NA.")
|
| 56 |
} |
|
| 57 | ||
| 58 | 6x |
if (is.null(level)) level <- Sys.getenv("TEAL.LOG_LEVEL")
|
| 59 | 5x |
if (is.null(level) || level == "") level <- getOption("teal.log_level", default = "INFO")
|
| 60 | ||
| 61 | 8x |
tryCatch( |
| 62 | 8x |
logger::log_threshold(level, namespace = namespace), |
| 63 | 8x |
error = function(condition) {
|
| 64 | 1x |
stop(paste( |
| 65 | 1x |
"The log level passed to logger::log_threshold was invalid.", |
| 66 | 1x |
"Make sure you pass or set the correct log level.", |
| 67 | 1x |
"See `logger::log_threshold` for more information" |
| 68 |
)) |
|
| 69 |
} |
|
| 70 |
) |
|
| 71 | ||
| 72 | 6x |
if (is.null(layout)) layout <- Sys.getenv("TEAL.LOG_LAYOUT")
|
| 73 | 7x |
if (is.null(layout) || layout == "") {
|
| 74 | 5x |
layout <- getOption( |
| 75 | 5x |
"teal.log_layout", |
| 76 | 5x |
default = "[{level}] {format(time, \"%Y-%m-%d %H:%M:%OS4\")} pid:{pid} token:[{token}] {ans} {msg}"
|
| 77 |
) |
|
| 78 |
} |
|
| 79 | 7x |
tryCatch( |
| 80 | 7x |
expr = {
|
| 81 | 7x |
logger::log_layout(layout_teal_glue_generator(layout), namespace = namespace) |
| 82 | 7x |
logger::log_appender(logger::appender_file(nullfile()), namespace = namespace) |
| 83 | 7x |
logger::log_success("Set up the logger", namespace = namespace)
|
| 84 | 6x |
logger::log_appender(logger::appender_stdout, namespace = namespace) |
| 85 |
}, |
|
| 86 | 7x |
error = function(condition) {
|
| 87 | 1x |
stop(paste( |
| 88 | 1x |
"Error setting the layout of the logger.", |
| 89 | 1x |
"Make sure you pass or set the correct log layout.", |
| 90 | 1x |
"See `logger::layout` for more information." |
| 91 |
)) |
|
| 92 |
} |
|
| 93 |
) |
|
| 94 | ||
| 95 | 6x |
invisible(NULL) |
| 96 |
} |
|
| 97 | ||
| 98 | ||
| 99 |
#' Generate log layout function using common variables available via glue syntax including shiny session token |
|
| 100 |
#' |
|
| 101 |
#' @inheritParams register_logger |
|
| 102 |
#' @return function taking `level` and `msg` arguments - keeping the original call creating the generator |
|
| 103 |
#' in the generator attribute that is returned when calling log_layout for the currently used layout |
|
| 104 |
#' @details this function behaves in the same way as [logger::layout_glue_generator()] |
|
| 105 |
#' but allows the shiny session token (last 8 chars) to be included in the logging layout |
|
| 106 |
#' @keywords internal |
|
| 107 |
layout_teal_glue_generator <- function(layout) {
|
|
| 108 | 7x |
force(layout) |
| 109 | 7x |
structure( |
| 110 | 7x |
function(level, msg, namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), |
| 111 | 7x |
.topenv = parent.frame()) {
|
| 112 | 7x |
if (!inherits(level, "loglevel")) {
|
| 113 | ! |
stop("Invalid log level, see ?logger::log_levels")
|
| 114 |
} |
|
| 115 | 7x |
with(logger::get_logger_meta_variables( |
| 116 | 7x |
log_level = level, namespace = namespace, .logcall = .logcall, .topcall = .topcall, |
| 117 | 7x |
.topenv = .topenv |
| 118 |
), {
|
|
| 119 | 7x |
token <- substr(shiny::getDefaultReactiveDomain()$token, 25, 32) |
| 120 | 7x |
if (length(token) == 0) {
|
| 121 | 7x |
token <- "" |
| 122 |
} |
|
| 123 | 7x |
glue::glue(layout) |
| 124 |
}) |
|
| 125 |
}, |
|
| 126 | 7x |
generator = deparse(match.call()) |
| 127 |
) |
|
| 128 |
} |
| 1 |
#' Logs the basic information about the session. |
|
| 2 |
#' |
|
| 3 |
#' @return `invisible(NULL)` |
|
| 4 |
#' @export |
|
| 5 |
#' |
|
| 6 |
log_system_info <- function() {
|
|
| 7 | 1x |
paste_pkgs_name_with_version <- function(names) {
|
| 8 | 2x |
vapply( |
| 9 | 2x |
names, |
| 10 | 2x |
FUN = function(name) paste(name, utils::packageVersion(name)), |
| 11 | 2x |
FUN.VALUE = character(1), |
| 12 | 2x |
USE.NAMES = FALSE |
| 13 |
) |
|
| 14 |
} |
|
| 15 | ||
| 16 | 1x |
info <- utils::sessionInfo() |
| 17 | ||
| 18 | 1x |
logger::log_trace("Platform: { info$platform }")
|
| 19 | 1x |
logger::log_trace("Running under: { info$running }")
|
| 20 | 1x |
logger::log_trace("{ info$R.version$version.string }")
|
| 21 | 1x |
logger::log_trace("Base packages: { paste(info$basePkgs, collapse = ' ') }")
|
| 22 | ||
| 23 |
# Paste package names and versions |
|
| 24 | 1x |
pasted_names_and_versions <- paste(paste_pkgs_name_with_version(names(info$otherPkgs)), collapse = ", ") |
| 25 | 1x |
logger::log_trace("Other attached packages: { pasted_names_and_versions }")
|
| 26 | ||
| 27 | 1x |
pasted_names_and_versions <- paste(paste_pkgs_name_with_version(names(info$loadedOnly)), collapse = ", ") |
| 28 | 1x |
logger::log_trace("Loaded packages: { pasted_names_and_versions }")
|
| 29 |
} |
| 1 |
#' Suppress logger logs |
|
| 2 |
#' |
|
| 3 |
#' This function suppresses `logger` when running tests via `testthat`. |
|
| 4 |
#' To suppress logs for a single test, add this function |
|
| 5 |
#' call within the `testthat::test_that` expression. To suppress logs for an entire |
|
| 6 |
#' test file, call this function at the start of the file. |
|
| 7 |
#' |
|
| 8 |
#' @return `NULL` invisible |
|
| 9 |
#' @export |
|
| 10 |
#' @examples |
|
| 11 |
#' testthat::test_that("An example test", {
|
|
| 12 |
#' suppress_logs() |
|
| 13 |
#' testthat::expect_true(TRUE) |
|
| 14 |
#' }) |
|
| 15 |
#' |
|
| 16 |
suppress_logs <- function() {
|
|
| 17 | ! |
old_log_appenders <- lapply(logger::log_namespaces(), function(ns) logger::log_appender(namespace = ns)) |
| 18 | ! |
old_log_namespaces <- logger::log_namespaces() |
| 19 | ! |
logger::log_appender(logger::appender_file(nullfile()), namespace = logger::log_namespaces()) |
| 20 | ! |
withr::defer_parent(mapply(logger::log_appender, old_log_appenders, old_log_namespaces)) |
| 21 | ! |
invisible(NULL) |
| 22 |
} |
| 1 |
.onLoad <- function(libname, pkgname) { # nolint
|
|
| 2 |
# Set up the teal logger instance |
|
| 3 | ! |
register_logger("teal.logger")
|
| 4 | ! |
invisible() |
| 5 |
} |