diff --git a/DESCRIPTION b/DESCRIPTION index c117820..c916b07 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -33,5 +33,5 @@ Suggests: Encoding: UTF-8 LazyLoad: yes Roxygen: list(old_usage = TRUE) -RoxygenNote: 7.1.1 +RoxygenNote: 7.2.3 VignetteBuilder: knitr diff --git a/NAMESPACE b/NAMESPACE index 89a5483..a5c3aaf 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -34,6 +34,7 @@ export(logformat) export(logger) export(loglevel) export(simple_log_layout) +export(stderr_appender) export(syslog_appender) export(tcp_appender) export(verbosity) diff --git a/NEWS.md b/NEWS.md index 15f5612..858bde0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,16 @@ # log4r 0.4.2 +* New `stderr_appender()` that writes to stderr (useful for RMarkdown docs). + +* `json_log_layout()` gains `include_timestamp` argument which can be set + to `FALSE` if whatever system collating your logs already adds a timestamp. + +* `logger()` uses a more complex algorithm to determine the default `logger`. + It uses `stderr_appender()` if you're running in knitr (so the result appears + in the log, and not in your doc) and it uses `simple_layout()` if you're + in a situation that's likely to automatically timestamps (i.e. running on + CI platform or in Posit connect). + * Fixes a crash where `logfmt_log_layout()` would not correctly handle memory reallocation of the underlying buffer. diff --git a/R/appenders.R b/R/appenders.R index 76e22a1..abc3ac6 100644 --- a/R/appenders.R +++ b/R/appenders.R @@ -7,8 +7,8 @@ #' the nature of the destination, the format of the messages may be controlled #' using a \strong{\link[=layouts]{Layout}}. #' -#' The most basic appenders log messages to the console or to a file; these are -#' described below. +#' The most basic appenders log messages to the console (stdout), stderr, +#' or to a file; these are described below. #' #' For implementing your own appenders, see Details. #' @@ -40,6 +40,12 @@ console_appender <- function(layout = default_log_layout()) { file_appender(file = "", layout = layout) } +#' @export +#' @rdname appenders +stderr_appender <- function(layout = default_log_layout()) { + file_appender(file = stderr(), layout = layout) +} + #' @param file The file to write messages to. #' @param append When \code{TRUE}, the file is not truncated when opening for #' the first time. diff --git a/R/create.logger.R b/R/create.logger.R index cc07763..7cf9a28 100644 --- a/R/create.logger.R +++ b/R/create.logger.R @@ -36,6 +36,11 @@ function(logfile = 'logfile.log', level = 'FATAL', logformat = NULL) #' level will be discarded. See \code{\link{loglevel}}. #' @param appenders The logging appenders; both single appenders and a #' \code{list()} of them are supported. See \code{\link{appenders}}. +#' The default value, `NULL`, creates an appender that should work well +#' in your current environment. For example, if you are inside an `.Rmd` +#' or `.qmd` it will log to `stderr()`, and if you're running on a CI +#' service or in Posit connect, it will suppress the timestamps (since +#' they will typically be added automatically). #' #' @return An object of class \code{"logger"}. #' @@ -54,8 +59,12 @@ function(logfile = 'logfile.log', level = 'FATAL', logformat = NULL) #' for information on controlling the behaviour of the logger object. #' #' @export -logger <- function(threshold = "INFO", appenders = console_appender()) { +logger <- function(threshold = "INFO", appenders = NULL) { threshold <- as.loglevel(threshold) + + if (is.null(appenders)) { + appenders <- default_appender() + } if (!is.list(appenders)) { appenders <- list(appenders) } @@ -68,3 +77,28 @@ logger <- function(threshold = "INFO", appenders = console_appender()) { class = "logger" ) } + +default_appender <- function() { + if (on_ci() || on_connect()) { + layout <- simple_log_layout() + } else { + layout <- default_log_layout() + } + + if (is_knitr()) { + stderr_appender(layout) + } else { + console_appender(layout) + } +} + +is_knitr <- function() { + isTRUE(getOption("knitr.in.progress", FALSE)) +} + +on_ci <- function() { + isTRUE(as.logical(Sys.getenv("CI"))) +} +on_connect <- function() { + identical(Sys.getenv("RSTUDIO_PRODUCT"), "CONNECT") +} diff --git a/R/layouts.R b/R/layouts.R index aeb2b25..4f1116c 100644 --- a/R/layouts.R +++ b/R/layouts.R @@ -83,12 +83,13 @@ logfmt_log_layout <- function() { #' @details \code{json_log_layout} requires the \code{jsonlite} package. #' #' @rdname layouts -#' @aliases json_log_layout +#' @param include_timestamp Include the current timestamp in the output? #' @export -json_log_layout <- function() { +json_log_layout <- function(include_timestamp = TRUE) { if (!requireNamespace("jsonlite", quietly = TRUE)) { stop("The 'jsonlite' package is required to use this JSON layout.") } + force(include_timestamp) time_format <- "%Y-%m-%dT%H:%M:%SZ" function(level, ...) { @@ -97,7 +98,9 @@ json_log_layout <- function() { fields <- list(message = paste0(fields, collapse = "")) } fields$level <- as.character(level) - fields$time <- fmt_current_time(time_format, TRUE) + if (include_timestamp) { + fields$time <- fmt_current_time(time_format, TRUE) + } jsonlite::toJSON(fields, auto_unbox = TRUE) } } diff --git a/man/appenders.Rd b/man/appenders.Rd index 3ca4558..6ca854e 100644 --- a/man/appenders.Rd +++ b/man/appenders.Rd @@ -3,11 +3,14 @@ \name{appenders} \alias{appenders} \alias{console_appender} +\alias{stderr_appender} \alias{file_appender} \title{Appenders} \usage{ console_appender(layout = default_log_layout()) +stderr_appender(layout = default_log_layout()) + file_appender(file, append = TRUE, layout = default_log_layout()) } \arguments{ @@ -26,8 +29,8 @@ In \href{https://logging.apache.org/log4j/}{log4j} etymology, the nature of the destination, the format of the messages may be controlled using a \strong{\link[=layouts]{Layout}}. -The most basic appenders log messages to the console or to a file; these are -described below. +The most basic appenders log messages to the console (stdout), stderr, +or to a file; these are described below. For implementing your own appenders, see Details. } diff --git a/man/layouts.Rd b/man/layouts.Rd index 12ebede..89ef417 100644 --- a/man/layouts.Rd +++ b/man/layouts.Rd @@ -17,12 +17,14 @@ bare_log_layout() logfmt_log_layout() -json_log_layout() +json_log_layout(include_timestamp = TRUE) } \arguments{ \item{time_format}{A valid format string for timestamps. See \code{\link[base]{strptime}}. For some layouts this can be \code{NA} to elide the timestamp.} + +\item{include_timestamp}{Include the current timestamp in the output?} } \description{ In \href{https://logging.apache.org/log4j/}{log4j} etymology, diff --git a/man/logger.Rd b/man/logger.Rd index 771c44a..8719cc8 100644 --- a/man/logger.Rd +++ b/man/logger.Rd @@ -4,14 +4,19 @@ \alias{logger} \title{Create Logger Objects} \usage{ -logger(threshold = "INFO", appenders = console_appender()) +logger(threshold = "INFO", appenders = NULL) } \arguments{ \item{threshold}{The logging threshold level. Messages with a lower priority level will be discarded. See \code{\link{loglevel}}.} \item{appenders}{The logging appenders; both single appenders and a -\code{list()} of them are supported. See \code{\link{appenders}}.} +\code{list()} of them are supported. See \code{\link{appenders}}. +The default value, `NULL`, creates an appender that should work well +in your current environment. For example, if you are inside an `.Rmd` +or `.qmd` it will log to `stderr()`, and if you're running on a CI +service or in Posit connect, it will suppress the timestamps (since +they will typically be added automatically).} } \value{ An object of class \code{"logger"}. diff --git a/tests/testthat/test-layouts.R b/tests/testthat/test-layouts.R index dd6b11f..340ca9d 100644 --- a/tests/testthat/test-layouts.R +++ b/tests/testthat/test-layouts.R @@ -59,5 +59,5 @@ test_that("JSON layouts work correctly", { }) test_that("Wonky times formats are caught early", { - expect_error(default_log_layout(strrep("%Y", 30)), regex = "Invalid") + expect_error(default_log_layout(strrep("%Y", 30)), regexp = "Invalid") })