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 |
#' \dontrun{
|
|
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 |
}
|