diff --git a/.Rbuildignore b/.Rbuildignore index 9429e213..8615cfb9 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -17,4 +17,5 @@ dev-resources ^pkgdown$ ^\.github$ revdep +^codecov\.yml$ ^README\.Rmd$ diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 06c74f4b..95874d71 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,12 +1,18 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/master/examples +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +# +# NOTE: This workflow is overkill for most R packages and +# check-standard.yaml is likely a better choice. +# usethis::use_github_action("check-standard") will install it. on: push: branches: [main, master] pull_request: branches: [main, master] -name: R-CMD-check +name: R-CMD-check.yaml + +permissions: read-all jobs: R-CMD-check: @@ -18,18 +24,25 @@ jobs: fail-fast: false matrix: config: - # - {os: macos-latest, r: 'release'} - # - {os: windows-latest, r: 'release'} - # - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - - {os: ubuntu-latest, r: 'release'} - # - {os: ubuntu-latest, r: 'oldrel-1'} + - {os: macos-latest, r: 'release'} + + - {os: windows-latest, r: 'release'} + # use 4.0 or 4.1 to check with rtools40's older compiler + - {os: windows-latest, r: 'oldrel-4'} + + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + - {os: ubuntu-latest, r: 'oldrel-2'} + - {os: ubuntu-latest, r: 'oldrel-3'} + - {os: ubuntu-latest, r: 'oldrel-4'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 @@ -43,7 +56,9 @@ jobs: with: extra-packages: any::rcmdcheck needs: check + pak-version: devel # can remove once pak 0.7.3 is released - uses: r-lib/actions/check-r-package@v2 with: upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..018b8db3 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,34 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: lint.yaml + +permissions: read-all + +jobs: + lint: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::lintr, local::. + needs: lint + + - name: Lint + run: lintr::lint_package() + shell: Rscript {0} + env: + LINTR_ERROR_ON_LINT: true diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index deeceabb..4bbce750 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -1,21 +1,30 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/master/examples +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: branches: [main, master] - tags: ['*'] + pull_request: + branches: [main, master] + release: + types: [published] + workflow_dispatch: + +name: pkgdown.yaml -name: pkgdown +permissions: read-all jobs: pkgdown: runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} permissions: contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 @@ -33,7 +42,8 @@ jobs: shell: Rscript {0} - name: Deploy to GitHub pages 🚀 - uses: JamesIves/github-pages-deploy-action@v4.4.1 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 with: clean: false branch: gh-pages diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 726ac3bb..98822609 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -1,4 +1,4 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/master/examples +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: @@ -6,7 +6,9 @@ on: pull_request: branches: [main, master] -name: test-coverage +name: test-coverage.yaml + +permissions: read-all jobs: test-coverage: @@ -15,7 +17,7 @@ jobs: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: @@ -23,28 +25,37 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::covr + extra-packages: any::covr, any::xml2 needs: coverage - name: Test coverage run: | - covr::codecov( + cov <- covr::package_coverage( quiet = FALSE, clean = FALSE, install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") ) + covr::to_cobertura(cov) shell: Rscript {0} + - uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + - name: Show testthat output if: always() run: | ## -------------------------------------------------------------------- - find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true shell: bash - name: Upload test results if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: coverage-test-failures path: ${{ runner.temp }}/package diff --git a/.lintr b/.lintr new file mode 100644 index 00000000..eb7211d6 --- /dev/null +++ b/.lintr @@ -0,0 +1,12 @@ +linters: linters_with_defaults( + line_length_linter(120), + object_usage_linter(interpret_glue = TRUE), + object_name_linter(styles = c("snake_case", "symbols", "UPPERCASE")) + ) +exclude: '# nolint' +exclude_start: '# nolint start' +exclude_end: '# nolint end' +exclusions: list( + "inst/demo-packages/logger-tester-package/R/tester.R", + "vignettes/migration.Rmd" + ) diff --git a/DESCRIPTION b/DESCRIPTION index f489bdf8..02ae9f35 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,49 +1,54 @@ Type: Package Package: logger -Authors@R: c( - person("Gergely", "Daróczi", , "daroczig@rapporter.net", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-3149-8537")), - person("System1", role = c("fnd")) - ) Title: A Lightweight, Modern and Flexible Logging Utility -Description: Inspired by the the 'futile.logger' R package and 'logging' Python module, this utility provides a flexible and extensible way of formatting and delivering log messages with low overhead. Version: 0.3.0.9000 Date: 2024-03-03 +Authors@R: c( + person("Gergely", "Daróczi", , "daroczig@rapporter.net", role = c("aut", "cre"), + comment = c(ORCID = "0000-0003-3149-8537")), + person("System1", role = "fnd") + ) +Description: Inspired by the the 'futile.logger' R package and 'logging' + Python module, this utility provides a flexible and extensible way of + formatting and delivering log messages with low overhead. +License: AGPL-3 URL: https://daroczig.github.io/logger/ BugReports: https://github.com/daroczig/logger/issues -Encoding: UTF-8 -RoxygenNote: 7.3.2 -License: AGPL-3 +Depends: + R (>= 4.0.0) Imports: - utils -Depends: R (>= 4.0.0) + utils Suggests: - glue, - pander, - jsonlite, - crayon, - slackr (>= 1.4.1), - RPushbullet, - telegram, - testthat (>= 3.0.0), - covr, - knitr, - rmarkdown, - devtools, - roxygen2, - parallel, - rsyslog, - shiny, - callr, - txtq, - botor, - R.utils, - syslognet, - withr + botor, + callr, + covr, + crayon, + devtools, + glue, + jsonlite, + knitr, + pander, + parallel, + R.utils, + rmarkdown, + roxygen2, + RPushbullet, + rsyslog, + shiny, + slackr (>= 1.4.1), + syslognet, + telegram, + testthat (>= 3.0.0), + txtq, + withr Enhances: - logging, - futile.logger, - log4r -VignetteBuilder: knitr + futile.logger, + log4r, + logging +VignetteBuilder: + knitr Config/testthat/edition: 3 Config/testthat/parallel: TRUE +Encoding: UTF-8 Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.2 diff --git a/NEWS.md b/NEWS.md index 4ffcb6f8..67e79efe 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # logger (development version) +* `log_appender()`, `log_layout()` and `log_formatter()` now check that you are calling them with a function, and return the previously set value. + # logger 0.3.0 (2024-03-03) Many unrelated small features, fixes and documentation updates collected over 2+ years. diff --git a/R/appenders.R b/R/appenders.R index 3bc8ce06..a5fff0fb 100644 --- a/R/appenders.R +++ b/R/appenders.R @@ -7,9 +7,14 @@ appender_void <- structure(function(lines) {}, generator = quote(appender_void() #' Append log record to stderr #' @param lines character vector #' @export -#' @seealso This is a [log_appender()], for alternatives, see eg [appender_stdout()], [appender_file()], [appender_tee()], [appender_slack()], [appender_pushbullet()], [appender_telegram()], [appender_syslog()], [appender_kinesis()] and [appender_async()] for evaluate any [log_appender()] function in a background process. +#' @seealso This is a [log_appender()], for alternatives, see eg +#' [appender_stdout()], [appender_file()], [appender_tee()], +#' [appender_slack()], [appender_pushbullet()], +#' [appender_telegram()], [appender_syslog()], +#' [appender_kinesis()] and [appender_async()] for evaluate any +#' [log_appender()] function in a background process. appender_console <- structure(function(lines) { - cat(lines, file = stderr(), sep = '\n') + cat(lines, file = stderr(), sep = "\n") }, generator = quote(appender_console())) @@ -21,23 +26,39 @@ appender_stderr <- appender_console #' Append log record to stdout #' @param lines character vector #' @export -#' @seealso This is a [log_appender()], for alternatives, see eg [appender_console()], [appender_file()], [appender_tee()], [appender_slack()], [appender_pushbullet()] +#' @seealso This is a [log_appender()], for alternatives, see eg [appender_console()], [appender_file()], +#' [appender_tee()], [appender_slack()], [appender_pushbullet()] appender_stdout <- structure(function(lines) { - cat(lines, sep = '\n') + cat(lines, sep = "\n") }, generator = quote(appender_stdout())) #' Append log messages to a file #' -#' Log messages are written to a file with basic log rotation: when max number of lines or bytes is defined to be other than `Inf`, then the log file is renamed with a `.1` suffix and a new log file is created. The renaming happens recursively (eg `logfile.1` renamed to `logfile.2`) until the specified `max_files`, then the oldest file (\code{logfile.{max_files-1}}) is deleted. +#' Log messages are written to a file with basic log rotation: when +#' max number of lines or bytes is defined to be other than `Inf`, +#' then the log file is renamed with a `.1` suffix and a new log file +#' is created. The renaming happens recursively (eg `logfile.1` +#' renamed to `logfile.2`) until the specified `max_files`, then the +#' oldest file (\code{logfile.{max_files-1}}) is deleted. #' @param file path -#' @param append boolean passed to `cat` defining if the file should be overwritten with the most recent log message instead of appending -#' @param max_lines numeric specifying the maximum number of lines allowed in a file before rotating -#' @param max_bytes numeric specifying the maximum number of bytes allowed in a file before rotating -#' @param max_files integer specifying the maximum number of files to be used in rotation +#' @param append boolean passed to `cat` defining if the file should +#' be overwritten with the most recent log message instead of +#' appending +#' @param max_lines numeric specifying the maximum number of lines +#' allowed in a file before rotating +#' @param max_bytes numeric specifying the maximum number of bytes +#' allowed in a file before rotating +#' @param max_files integer specifying the maximum number of files to +#' be used in rotation #' @export #' @return function taking `lines` argument -#' @seealso This is generator function for [log_appender()], for alternatives, see eg [appender_console()], [appender_tee()], [appender_slack()], [appender_pushbullet()], [appender_telegram()], [appender_syslog()], [appender_kinesis()] and [appender_async()] for evaluate any [log_appender()] function in a background process. +#' @seealso This is generator function for [log_appender()], for +#' alternatives, see eg [appender_console()], [appender_tee()], +#' [appender_slack()], [appender_pushbullet()], +#' [appender_telegram()], [appender_syslog()], +#' [appender_kinesis()] and [appender_async()] for evaluate any +#' [log_appender()] function in a background process. #' @examples \dontrun{ #' ## ########################################################################## #' ## simple example logging to a file @@ -51,8 +72,9 @@ appender_stdout <- structure(function(lines) { #' ## rotated after every 3rd line up to max 5 files #' #' ## create a folder storing the log files -#' t <- tempfile(); dir.create(t) -#' f <- file.path(t, 'log') +#' t <- tempfile() +#' dir.create(t) +#' f <- file.path(t, "log") #' #' ## define the file logger with log rotation enabled #' log_appender(appender_file(f, max_lines = 3, max_files = 5L)) @@ -62,106 +84,115 @@ appender_stdout <- structure(function(lines) { #' #' ## see what was logged #' lapply(list.files(t, full.names = TRUE), function(t) { -#' cat('\n##', t, '\n') -#' cat(readLines(t), sep = '\n') +#' cat("\n##", t, "\n") +#' cat(readLines(t), sep = "\n") #' }) #' #' ## enable internal logging to see what's actually happening in the logrotate steps -#' log_threshold(TRACE, namespace = '.logger') +#' log_threshold(TRACE, namespace = ".logger") #' ## run the above commands again #' } -appender_file <- function(file, append = TRUE, max_lines = Inf, max_bytes = Inf, max_files = 1L) { - - force(file) - force(append) - force(max_lines) - force(max_bytes) - force(max_files) +appender_file <- function(file, append = TRUE, max_lines = Inf, max_bytes = Inf, max_files = 1L) { # nolint + force(file) + force(append) + force(max_lines) + force(max_bytes) + force(max_files) - if (!is.integer(max_files) || max_files < 1) { - stop('max_files must be a positive integer') - } + if (!is.integer(max_files) || max_files < 1) { + stop("max_files must be a positive integer") + } - structure( - function(lines) { - if (is.finite(max_lines) | is.finite(max_bytes)) { - - fail_on_missing_package('R.utils') - - n_lines <- tryCatch( - suppressWarnings(R.utils::countLines(file)), - error = function(e) 0) - n_bytes <- ifelse(file.exists(file), file.info(file)$size, 0) - - if (n_lines >= max_lines || n_bytes >= max_bytes) { - log_trace( - 'lines: %s, max_lines: %s, bytes: %s, max_bytes: %s', - n_lines, max_lines, n_bytes, max_bytes, - namespace = '.logger') - log_trace( - 'lines >= max_lines || bytes >= max_bytes: %s', - n_lines >= max_lines || n_bytes >= max_bytes, - namespace = '.logger') - for (i in max_files:1) { - - ## just kill the old file - if (i == 1) { - log_trace('killing the main file: %s', file, namespace = '.logger') - unlink(file) - } else { - - ## rotate the old file - new <- paste(file, i - 1, sep = '.') - if (i == 2) { - old <- file - } else { - old <- paste(file, i - 2, sep = '.') - } - - if (file.exists(old)) { - log_trace('renaming %s to %s', old, new, namespace = '.logger') - file.rename(old, new) - } - - ## kill the rotated, but not needed file - if (i > max_files) { - log_trace('killing the file with too many rotations: %s', new, namespace = '.logger') - unlink(new) - } - - } - } - } + structure( + function(lines) { + if (is.finite(max_lines) | is.finite(max_bytes)) { + fail_on_missing_package("R.utils") + + n_lines <- tryCatch( + suppressWarnings(R.utils::countLines(file)), + error = function(e) 0 + ) + n_bytes <- ifelse(file.exists(file), file.info(file)$size, 0) + + if (n_lines >= max_lines || n_bytes >= max_bytes) { + log_trace( + "lines: %s, max_lines: %s, bytes: %s, max_bytes: %s", + n_lines, max_lines, n_bytes, max_bytes, + namespace = ".logger" + ) + log_trace( + "lines >= max_lines || bytes >= max_bytes: %s", + n_lines >= max_lines || n_bytes >= max_bytes, + namespace = ".logger" + ) + for (i in max_files:1) { + ## just kill the old file + if (i == 1) { + log_trace("killing the main file: %s", file, namespace = ".logger") + unlink(file) + } else { + ## rotate the old file + new <- paste(file, i - 1, sep = ".") + if (i == 2) { + old <- file + } else { + old <- paste(file, i - 2, sep = ".") + } + + if (file.exists(old)) { + log_trace("renaming %s to %s", old, new, namespace = ".logger") + file.rename(old, new) + } + + ## kill the rotated, but not needed file + if (i > max_files) { + log_trace("killing the file with too many rotations: %s", new, namespace = ".logger") + unlink(new) + } } - log_trace('logging %s to %s', shQuote(lines), file, namespace = '.logger') - cat(lines, sep = '\n', file = file, append = append) - }, generator = deparse(match.call())) + } + } + } + log_trace("logging %s to %s", shQuote(lines), file, namespace = ".logger") + cat(lines, sep = "\n", file = file, append = append) + }, + generator = deparse(match.call()) + ) } #' Append log messages to a file and stdout as well #' -#' This appends log messages to both console and a file. The same rotation options are available as in [appender_file()]. +#' This appends log messages to both console and a file. The same +#' rotation options are available as in [appender_file()]. #' @inheritParams appender_file #' @export #' @return function taking `lines` argument -#' @seealso This is generator function for [log_appender()], for alternatives, see eg [appender_console()], [appender_file()], [appender_slack()], [appender_pushbullet()], [appender_telegram()], [appender_syslog()], [appender_kinesis()] and [appender_async()] for evaluate any [log_appender()] function in a background process. +#' @seealso This is generator function for [log_appender()], for +#' alternatives, see eg [appender_console()], [appender_file()], +#' [appender_slack()], [appender_pushbullet()], +#' [appender_telegram()], [appender_syslog()], +#' [appender_kinesis()] and [appender_async()] for evaluate any +#' [log_appender()] function in a background process. appender_tee <- function(file, append = TRUE, max_lines = Inf, max_bytes = Inf, max_files = 1L) { - force(file) - force(append) - force(max_lines) - force(max_bytes) - force(max_files) - structure( - function(lines) { - appender_console(lines) - appender_file(file, append, max_lines, max_bytes, max_files)(lines) - }, generator = deparse(match.call())) + force(file) + force(append) + force(max_lines) + force(max_bytes) + force(max_files) + structure( + function(lines) { + appender_console(lines) + appender_file(file, append, max_lines, max_bytes, max_files)(lines) + }, + generator = deparse(match.call()) + ) } #' Send log messages to a Slack channel -#' @param channel Slack channel name with a hashtag prefix for public channel and no prefix for private channels +#' @param channel Slack channel name with a hashtag prefix for public +#' channel and no prefix for private channels #' @param username Slack (bot) username #' @param icon_emoji optional override for the bot icon #' @param api_token Slack API token @@ -169,71 +200,91 @@ appender_tee <- function(file, append = TRUE, max_lines = Inf, max_bytes = Inf, #' @return function taking `lines` argument #' @export #' @note This functionality depends on the \pkg{slackr} package. -#' @seealso This is generator function for [log_appender()], for alternatives, see eg [appender_console()], [appender_file()], [appender_tee()], [appender_pushbullet()], [appender_telegram()], [appender_syslog()], [appender_kinesis()] and [appender_async()] for evaluate any [log_appender()] function in a background process. -appender_slack <- function(channel = Sys.getenv('SLACK_CHANNEL'), - username = Sys.getenv('SLACK_USERNAME'), - icon_emoji = Sys.getenv('SLACK_ICON_EMOJI'), - api_token = Sys.getenv('SLACK_API_TOKEN'), +#' @seealso This is generator function for [log_appender()], for +#' alternatives, see eg [appender_console()], [appender_file()], +#' [appender_tee()], [appender_pushbullet()], +#' [appender_telegram()], [appender_syslog()], +#' [appender_kinesis()] and [appender_async()] for evaluate any +#' [log_appender()] function in a background process. +appender_slack <- function(channel = Sys.getenv("SLACK_CHANNEL"), + username = Sys.getenv("SLACK_USERNAME"), + icon_emoji = Sys.getenv("SLACK_ICON_EMOJI"), + api_token = Sys.getenv("SLACK_API_TOKEN"), preformatted = TRUE) { + fail_on_missing_package("slackr", "1.4.1") + force(channel) + force(username) + force(icon_emoji) + force(api_token) + force(preformatted) - fail_on_missing_package('slackr', '1.4.1') - force(channel) - force(username) - force(icon_emoji) - force(api_token) - force(preformatted) - - structure( - function(lines) { - slackr::slackr_msg( - text = lines, channel = channel, username = username, - icon_emoji = icon_emoji, token = api_token, preformatted = preformatted) - }, generator = deparse(match.call())) - + structure( + function(lines) { + slackr::slackr_msg( + text = lines, channel = channel, username = username, + icon_emoji = icon_emoji, token = api_token, preformatted = preformatted + ) + }, + generator = deparse(match.call()) + ) } #' Send log messages to Pushbullet -#' @param ... parameters passed to `pbPost`, such as `recipients` or `apikey`, although it's probably much better to set all these in the `~/.rpushbullet.json` as per package docs at +#' @param ... parameters passed to `pbPost`, such as `recipients` or +#' `apikey`, although it's probably much better to set all these +#' in the `~/.rpushbullet.json` as per package docs at +#' #' @export #' @note This functionality depends on the \pkg{RPushbullet} package. -#' @seealso This is generator function for [log_appender()], for alternatives, see eg [appender_console()], [appender_file()], [appender_tee()], [appender_slack()], [appender_telegram()], [appender_syslog()], [appender_kinesis()] and [appender_async()] for evaluate any [log_appender()] function in a background process. +#' @seealso This is generator function for [log_appender()], for +#' alternatives, see eg [appender_console()], [appender_file()], +#' [appender_tee()], [appender_slack()], [appender_telegram()], +#' [appender_syslog()], [appender_kinesis()] and +#' [appender_async()] for evaluate any [log_appender()] function +#' in a background process. #' @export appender_pushbullet <- function(...) { + fail_on_missing_package("RPushbullet") - fail_on_missing_package('RPushbullet') - - structure( - function(lines) { - RPushbullet::pbPost(type = 'note', body = paste(lines, sep = '\n'), ...) - }, generator = deparse(match.call())) - + structure( + function(lines) { + RPushbullet::pbPost(type = "note", body = paste(lines, sep = "\n"), ...) + }, + generator = deparse(match.call()) + ) } #' Send log messages to a Telegram chat -#' @param chat_id Unique identifier for the target chat or username of the target channel (in the format @channelusername) +#' @param chat_id Unique identifier for the target chat or username of +#' the target channel (in the format @channelusername) #' @param bot_token Telegram Authorization token -#' @param parse_mode Message parse mode. Allowed values: Markdown or HTML +#' @param parse_mode Message parse mode. Allowed values: Markdown or +#' HTML #' @return function taking `lines` argument #' @export #' @note This functionality depends on the \pkg{telegram} package. -#' @seealso This is generator function for [log_appender()], for alternatives, see eg [appender_console()], [appender_file()], [appender_tee()], [appender_pushbullet()], [appender_syslog()], [appender_kinesis()] and [appender_async()] for evaluate any [log_appender()] function in a background process. -appender_telegram <- function(chat_id = Sys.getenv('TELEGRAM_CHAT_ID'), - bot_token = Sys.getenv('TELEGRAM_BOT_TOKEN'), - parse_mode = NULL) { - - fail_on_missing_package('telegram') - force(chat_id) - force(bot_token) - force(parse_mode) - - tb <- telegram::TGBot$new(token = bot_token) - structure( - function(lines) { - tb$sendMessage(text = lines, parse_mode = parse_mode, chat_id = chat_id) - }, generator = deparse(match.call())) - +#' @seealso This is generator function for [log_appender()], for +#' alternatives, see eg [appender_console()], [appender_file()], +#' [appender_tee()], [appender_pushbullet()], [appender_syslog()], +#' [appender_kinesis()] and [appender_async()] for evaluate any +#' [log_appender()] function in a background process. +appender_telegram <- function(chat_id = Sys.getenv("TELEGRAM_CHAT_ID"), + bot_token = Sys.getenv("TELEGRAM_BOT_TOKEN"), + parse_mode = NULL) { + fail_on_missing_package("telegram") + force(chat_id) + force(bot_token) + force(parse_mode) + + tb <- telegram::TGBot$new(token = bot_token) + structure( + function(lines) { + tb$sendMessage(text = lines, parse_mode = parse_mode, chat_id = chat_id) + }, + generator = deparse(match.call()) + ) } @@ -243,7 +294,12 @@ appender_telegram <- function(chat_id = Sys.getenv('TELEGRAM_CHAT_ID'), #' @return function taking `lines` argument #' @export #' @note This functionality depends on the \pkg{rsyslog} package. -#' @seealso This is generator function for [log_appender()], for alternatives, see eg [appender_console()], [appender_file()], [appender_tee()], [appender_pushbullet()], [appender_telegram()], [appender_kinesis()] and [appender_async()] for evaluate any [log_appender()] function in a background process. +#' @seealso This is generator function for [log_appender()], for +#' alternatives, see eg [appender_console()], [appender_file()], +#' [appender_tee()], [appender_pushbullet()], +#' [appender_telegram()], [appender_kinesis()] and +#' [appender_async()] for evaluate any [log_appender()] function +#' in a background process. #' @examples \dontrun{ #' if (requireNamespace("rsyslog", quietly = TRUE)) { #' log_appender(appender_syslog("test")) @@ -251,20 +307,20 @@ appender_telegram <- function(chat_id = Sys.getenv('TELEGRAM_CHAT_ID'), #' } #' } appender_syslog <- function(identifier, ...) { - fail_on_missing_package('rsyslog') - rsyslog::open_syslog(identifier = identifier, ...) - structure( - function(lines) { - for (line in lines) { - rsyslog::syslog(line) - } - }, - generator = deparse(match.call()) - ) + fail_on_missing_package("rsyslog") + rsyslog::open_syslog(identifier = identifier, ...) + structure( + function(lines) { + for (line in lines) { + rsyslog::syslog(line) + } + }, + generator = deparse(match.call()) + ) } -#nocov start +# nocov start #' Send log messages to a network syslog server #' @param identifier program/function identification (string). #' @param server machine where syslog daemon runs (string). @@ -275,18 +331,18 @@ appender_syslog <- function(identifier, ...) { #' @note This functionality depends on the \pkg{syslognet} package. #' @examples \dontrun{ #' if (requireNamespace("syslognet", quietly = TRUE)) { -#' log_appender(appender_syslognet("test_app", 'remoteserver')) +#' log_appender(appender_syslognet("test_app", "remoteserver")) #' log_info("Test message.") #' } #' } appender_syslognet <- function(identifier, server, port = 601L) { - fail_on_missing_package('syslognet') + fail_on_missing_package("syslognet") force(identifier) force(server) force(port) structure( function(lines) { - sev <- attr(lines, 'severity', exact = TRUE) + sev <- attr(lines, "severity", exact = TRUE) for (line in lines) { syslognet::syslog(line, sev, app_name = identifier, server = server, port = port) } @@ -294,59 +350,79 @@ appender_syslognet <- function(identifier, server, port = 601L) { generator = deparse(match.call()) ) } -#nocov end +# nocov end #' Send log messages to a Amazon Kinesis stream #' @param stream name of the Kinesis stream -#' @return function taking `lines` and optional `partition_key` argument +#' @return function taking `lines` and optional `partition_key` +#' argument #' @export #' @note This functionality depends on the \pkg{botor} package. -#' @seealso This is generator function for [log_appender()], for alternatives, see eg [appender_console()], [appender_file()], [appender_tee()], [appender_pushbullet()], [appender_telegram()], [appender_syslog()] and [appender_async()] for evaluate any [log_appender()] function in a background process. +#' @seealso This is generator function for [log_appender()], for +#' alternatives, see eg [appender_console()], [appender_file()], +#' [appender_tee()], [appender_pushbullet()], +#' [appender_telegram()], [appender_syslog()] and +#' [appender_async()] for evaluate any [log_appender()] function +#' in a background process. appender_kinesis <- function(stream) { - fail_on_missing_package('botor') - force(stream) - structure( - function(lines, partition_key = NA_character_) { - for (line in lines) { - botor::kinesis()$put_record(StreamName = stream, Data = line, PartitionKey = partition_key) - } - }, - generator = deparse(match.call()) - ) + fail_on_missing_package("botor") + force(stream) + structure( + function(lines, partition_key = NA_character_) { + for (line in lines) { + botor::kinesis()$put_record(StreamName = stream, Data = line, PartitionKey = partition_key) + } + }, + generator = deparse(match.call()) + ) } -#' Delays executing the actual appender function to the future in a background process to avoid blocking the main R session -#' @param appender a [log_appender()] function with a `generator` attribute (TODO note not required, all fn will be passed if not) +#' Delays executing the actual appender function to the future in a +#' background process to avoid blocking the main R session +#' @param appender a [log_appender()] function with a `generator` +#' attribute (TODO note not required, all fn will be passed if +#' not) #' @param batch number of records to process from the queue at once -#' @param namespace `logger` namespace to use for logging messages on starting up the background process -#' @param init optional function to run in the background process that is useful to set up the environment required for logging, eg if the `appender` function requires some extra packages to be loaded or some environment variables to be set etc +#' @param namespace `logger` namespace to use for logging messages on +#' starting up the background process +#' @param init optional function to run in the background process that +#' is useful to set up the environment required for logging, eg if +#' the `appender` function requires some extra packages to be +#' loaded or some environment variables to be set etc #' @return function taking `lines` argument #' @export -#' @note This functionality depends on the \pkg{txtq} and \pkg{callr} packages. The R session's temp folder is used for staging files (message queue and other forms of communication between the parent and child processes). -#' @seealso This function is to be used with an actual [log_appender()], for example [appender_console()], [appender_file()], [appender_tee()], [appender_pushbullet()], [appender_telegram()], [appender_syslog()] or [appender_kinesis()]. +#' @note This functionality depends on the \pkg{txtq} and \pkg{callr} +#' packages. The R session's temp folder is used for staging files +#' (message queue and other forms of communication between the +#' parent and child processes). +#' @seealso This function is to be used with an actual +#' [log_appender()], for example [appender_console()], +#' [appender_file()], [appender_tee()], [appender_pushbullet()], +#' [appender_telegram()], [appender_syslog()] or +#' [appender_kinesis()]. #' @examples \dontrun{ #' appender_file_slow <- function(file) { #' force(file) #' function(lines) { #' Sys.sleep(1) -#' cat(lines, sep = '\n', file = file, append = TRUE) +#' cat(lines, sep = "\n", file = file, append = TRUE) #' } #' } #' #' ## log what's happening in the background -#' log_threshold(TRACE, namespace = 'async_logger') -#' log_appender(appender_console, namespace = 'async_logger') +#' log_threshold(TRACE, namespace = "async_logger") +#' log_appender(appender_console, namespace = "async_logger") #' #' ## start async appender #' t <- tempfile() -#' log_info('Logging in the background to {t}') +#' log_info("Logging in the background to {t}") #' my_appender <- appender_async(appender_file_slow(file = t)) #' #' ## use async appender #' log_appender(my_appender) -#' log_info('Was this slow?') +#' log_info("Was this slow?") #' system.time(for (i in 1:25) log_info(i)) #' #' readLines(t) @@ -354,119 +430,113 @@ appender_kinesis <- function(stream) { #' readLines(t) #' #' ## check on the async appender (debugging, you will probably never need this) -#' attr(my_appender, 'async_writer_queue')$count() -#' attr(my_appender, 'async_writer_queue')$log() +#' attr(my_appender, "async_writer_queue")$count() +#' attr(my_appender, "async_writer_queue")$log() #' -#' attr(my_appender, 'async_writer_process')$get_pid() -#' attr(my_appender, 'async_writer_process')$get_state() -#' attr(my_appender, 'async_writer_process')$poll_process(1) -#' attr(my_appender, 'async_writer_process')$read() +#' attr(my_appender, "async_writer_process")$get_pid() +#' attr(my_appender, "async_writer_process")$get_state() +#' attr(my_appender, "async_writer_process")$poll_process(1) +#' attr(my_appender, "async_writer_process")$read() #' -#' attr(my_appender, 'async_writer_process')$is_alive() -#' attr(my_appender, 'async_writer_process')$read_error() +#' attr(my_appender, "async_writer_process")$is_alive() +#' attr(my_appender, "async_writer_process")$read_error() #' } -appender_async <- function(appender, batch = 1, namespace = 'async_logger', - init = function() log_info('Background process started')) { - - fail_on_missing_package('txtq') - fail_on_missing_package('callr') - - force(appender) - force(batch) - - ## create a storage for the message queue - async_writer_storage <- tempfile() - log_trace(paste('Async writer storage:', async_writer_storage), namespace = 'async_logger') - - ## initialize the message queue - async_writer_queue <- txtq::txtq(async_writer_storage) - - ## start a background process for the async execution of the message queue - ## TODO make it easy to create multiple/parallel background processes? - async_writer_process <- callr::r_session$new() - log_trace(paste('Async writer PID:', async_writer_process$get_pid()), namespace = 'async_logger') - - ## load minimum required packages - async_writer_process$run(function() source(system.file( - 'load-packages-in-background-process.R', - package = 'logger'))) - async_writer_process$run(init) - - ## connect to the message queue - async_writer_process$run(assign, args = list(x = 'async_writer_storage', value = async_writer_storage)) - async_writer_process$run(function() async_writer_queue <<- txtq::txtq(async_writer_storage)) - - ## pass arguments - async_writer_process$run(assign, args = list(x = 'batch', value = batch)) - - ## pass appender - async_writer_tempfile <- tempfile() - saveRDS(appender, async_writer_tempfile) - log_trace(paste('Async appender cached at:', async_writer_tempfile), namespace = 'async_logger') - async_writer_process$run(assign, args = list(x = 'async_writer_tempfile', value = async_writer_tempfile)) - async_writer_process$run(assign, args = list(x = 'appender', value = readRDS(async_writer_tempfile))) - - ## start infinite loop processing log records - async_writer_process$call(function() { - while (TRUE) { - - items <- async_writer_queue$pop(batch) - - if (nrow(items) == 0) { - - ## avoid burning CPU - Sys.sleep(.1) - - } else { - - ## execute the actual appender for each log item - for (i in seq_len(nrow(items))) { - appender(items$message[i]) - } - - ## remove processed log records - async_writer_queue$clean() - - } +appender_async <- function(appender, batch = 1, namespace = "async_logger", + init = function() log_info("Background process started")) { + fail_on_missing_package("txtq") + fail_on_missing_package("callr") + + force(appender) + force(batch) + + ## create a storage for the message queue + async_writer_storage <- tempfile() + log_trace(paste("Async writer storage:", async_writer_storage), namespace = "async_logger") + + ## initialize the message queue + async_writer_queue <- txtq::txtq(async_writer_storage) + + ## start a background process for the async execution of the message queue + ## TODO make it easy to create multiple/parallel background processes? + async_writer_process <- callr::r_session$new() + log_trace(paste("Async writer PID:", async_writer_process$get_pid()), namespace = "async_logger") + + ## load minimum required packages + async_writer_process$run(function() { + source(system.file( + "load-packages-in-background-process.R", + package = "logger" + )) + }) + async_writer_process$run(init) + + ## connect to the message queue + async_writer_process$run(assign, args = list(x = "async_writer_storage", value = async_writer_storage)) + async_writer_process$run(function() async_writer_queue <<- txtq::txtq(async_writer_storage)) + + ## pass arguments + async_writer_process$run(assign, args = list(x = "batch", value = batch)) + + ## pass appender + async_writer_tempfile <- tempfile() + saveRDS(appender, async_writer_tempfile) + log_trace(paste("Async appender cached at:", async_writer_tempfile), namespace = "async_logger") + async_writer_process$run(assign, args = list(x = "async_writer_tempfile", value = async_writer_tempfile)) + async_writer_process$run(assign, args = list(x = "appender", value = readRDS(async_writer_tempfile))) + + ## start infinite loop processing log records + async_writer_process$call(function() { + while (TRUE) { + items <- async_writer_queue$pop(batch) + + if (nrow(items) == 0) { + ## avoid burning CPU + Sys.sleep(.1) + } else { + ## execute the actual appender for each log item + for (i in seq_len(nrow(items))) { + appender(items$message[i]) } - }) - - structure( - - function(lines) { - - ## check if background process still works - if (!isTRUE(async_writer_process$is_alive())) { - stop('FATAL: Async writer process not found') - } - remote_error <- async_writer_process$read_error() - if (remote_error != '') { - stop(paste('FATAL: Async writer failed with', shQuote(remote_error))) - } - remote_event <- async_writer_process$read() - if (!is.null(remote_event) && !is.null(remote_event$error)) { - stop(paste( - 'FATAL: Async writer error of', - shQuote(remote_event$error$message), - 'in', - shQuote(paste(deparse(remote_event$error$call), collapse = ' ')))) - } - - ## write to message queue - for (line in lines) { - async_writer_queue$push(title = as.character(as.numeric(Sys.time())), message = line) - } - }, + ## remove processed log records + async_writer_queue$clean() + } + } + }) - generator = deparse(match.call()), - ## share remote process and queue with parent for debugging purposes - async_writer_storage = async_writer_storage, - async_writer_queue = async_writer_queue, - async_writer_process = async_writer_process) + structure( + function(lines) { + ## check if background process still works + if (!isTRUE(async_writer_process$is_alive())) { + stop("FATAL: Async writer process not found") + } + remote_error <- async_writer_process$read_error() + if (remote_error != "") { + stop(paste("FATAL: Async writer failed with", shQuote(remote_error))) + } + remote_event <- async_writer_process$read() + if (!is.null(remote_event) && !is.null(remote_event$error)) { + stop(paste( + "FATAL: Async writer error of", + shQuote(remote_event$error$message), + "in", + shQuote(paste(deparse(remote_event$error$call), collapse = " ")) + )) + } - ## NOTE no need to clean up, all will go away with the current R session's temp folder + ## write to message queue + for (line in lines) { + async_writer_queue$push(title = as.character(as.numeric(Sys.time())), message = line) + } + }, + generator = deparse(match.call()), + ## share remote process and queue with parent for debugging purposes + async_writer_storage = async_writer_storage, + async_writer_queue = async_writer_queue, + async_writer_process = async_writer_process + ) + ## NOTE no need to clean up, all will go away with the current R session's temp folder } ## TODO other appenders: graylog, datadog, cloudwatch, email via sendmailR, ES etc diff --git a/R/color.R b/R/color.R index 6ef91e99..7d53d065 100644 --- a/R/color.R +++ b/R/color.R @@ -3,65 +3,59 @@ #' Color log messages according to their severity with either a rainbow #' or grayscale color scheme. The greyscale theme assumes a dark background on #' the terminal. -#' +#' #' @param msg String to color. #' @param level see [log_levels()] #' @return A string with ANSI escape codes. #' @export #' @examplesIf requireNamespace("crayon") -#' cat(colorize_by_log_level('foobar', FATAL), '\n') -#' cat(colorize_by_log_level('foobar', ERROR), '\n') -#' cat(colorize_by_log_level('foobar', WARN), '\n') -#' cat(colorize_by_log_level('foobar', SUCCESS), '\n') -#' cat(colorize_by_log_level('foobar', INFO), '\n') -#' cat(colorize_by_log_level('foobar', DEBUG), '\n') -#' cat(colorize_by_log_level('foobar', TRACE), '\n') -#' -#' cat(grayscale_by_log_level('foobar', FATAL), '\n') -#' cat(grayscale_by_log_level('foobar', ERROR), '\n') -#' cat(grayscale_by_log_level('foobar', WARN), '\n') -#' cat(grayscale_by_log_level('foobar', SUCCESS), '\n') -#' cat(grayscale_by_log_level('foobar', INFO), '\n') -#' cat(grayscale_by_log_level('foobar', DEBUG), '\n') -#' cat(grayscale_by_log_level('foobar', TRACE), '\n') +#' cat(colorize_by_log_level("foobar", FATAL), "\n") +#' cat(colorize_by_log_level("foobar", ERROR), "\n") +#' cat(colorize_by_log_level("foobar", WARN), "\n") +#' cat(colorize_by_log_level("foobar", SUCCESS), "\n") +#' cat(colorize_by_log_level("foobar", INFO), "\n") +#' cat(colorize_by_log_level("foobar", DEBUG), "\n") +#' cat(colorize_by_log_level("foobar", TRACE), "\n") +#' +#' cat(grayscale_by_log_level("foobar", FATAL), "\n") +#' cat(grayscale_by_log_level("foobar", ERROR), "\n") +#' cat(grayscale_by_log_level("foobar", WARN), "\n") +#' cat(grayscale_by_log_level("foobar", SUCCESS), "\n") +#' cat(grayscale_by_log_level("foobar", INFO), "\n") +#' cat(grayscale_by_log_level("foobar", DEBUG), "\n") +#' cat(grayscale_by_log_level("foobar", TRACE), "\n") colorize_by_log_level <- function(msg, level) { - - fail_on_missing_package('crayon') - - color <- switch( - attr(level, 'level'), - 'FATAL' = crayon::combine_styles(crayon::bold, crayon::make_style('red1')), - 'ERROR' = crayon::make_style('red4'), - 'WARN' = crayon::make_style('darkorange'), - 'SUCCESS' = crayon::combine_styles(crayon::bold, crayon::make_style('green4')), - 'INFO' = crayon::reset, - 'DEBUG' = crayon::make_style('deepskyblue4'), - 'TRACE' = crayon::make_style('dodgerblue4'), - stop('Unknown log level') - ) - - paste0(color(msg), crayon::reset('')) - + fail_on_missing_package("crayon") + + color <- switch(attr(level, "level"), + "FATAL" = crayon::combine_styles(crayon::bold, crayon::make_style("red1")), + "ERROR" = crayon::make_style("red4"), + "WARN" = crayon::make_style("darkorange"), + "SUCCESS" = crayon::combine_styles(crayon::bold, crayon::make_style("green4")), + "INFO" = crayon::reset, + "DEBUG" = crayon::make_style("deepskyblue4"), + "TRACE" = crayon::make_style("dodgerblue4"), + stop("Unknown log level") + ) + + paste0(color(msg), crayon::reset("")) } #' @export #' @rdname colorize_by_log_level grayscale_by_log_level <- function(msg, level) { - - fail_on_missing_package('crayon') - - color <- switch( - attr(level, 'level'), - 'FATAL' = crayon::make_style('gray100'), - 'ERROR' = crayon::make_style('gray90'), - 'WARN' = crayon::make_style('gray80'), - 'SUCCESS' = crayon::make_style('gray70'), - 'INFO' = crayon::make_style('gray60'), - 'DEBUG' = crayon::make_style('gray50'), - 'TRACE' = crayon::make_style('gray40'), - stop('Unknown log level') - ) - - paste0(color(msg), crayon::reset('')) - + fail_on_missing_package("crayon") + + color <- switch(attr(level, "level"), + "FATAL" = crayon::make_style("gray100"), + "ERROR" = crayon::make_style("gray90"), + "WARN" = crayon::make_style("gray80"), + "SUCCESS" = crayon::make_style("gray70"), + "INFO" = crayon::make_style("gray60"), + "DEBUG" = crayon::make_style("gray50"), + "TRACE" = crayon::make_style("gray40"), + stop("Unknown log level") + ) + + paste0(color(msg), crayon::reset("")) } diff --git a/R/formatters.R b/R/formatters.R index 5ba7ade1..be47278d 100644 --- a/R/formatters.R +++ b/R/formatters.R @@ -3,9 +3,15 @@ #' @inheritParams log_level #' @return character vector #' @export -#' @seealso This is a [log_formatter()], for alternatives, see [formatter_sprintf()], [formatter_glue()], [formatter_glue_safe()], [formatter_glue_or_sprintf()], [formatter_logging()], [formatter_json()], [formatter_pander()] and [skip_formatter()] for marking a string not to apply the formatter on it. -formatter_paste <- structure(function(..., .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - eval(paste(...), envir = .topenv) +#' @seealso This is a [log_formatter()], for alternatives, see +#' [formatter_sprintf()], [formatter_glue()], +#' [formatter_glue_safe()], [formatter_glue_or_sprintf()], +#' [formatter_logging()], [formatter_json()], [formatter_pander()] +#' and [skip_formatter()] for marking a string not to apply the +#' formatter on it. +formatter_paste <- structure(function(..., + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + paste(...) }, generator = quote(formatter_paste())) @@ -15,9 +21,15 @@ formatter_paste <- structure(function(..., .logcall = sys.call(), .topcall = sys #' @inheritParams log_level #' @return character vector #' @export -#' @seealso This is a [log_formatter()], for alternatives, see [formatter_paste()], [formatter_glue()], [formatter_glue_safe()], [formatter_glue_or_sprintf()], [formatter_logging()], [formatter_json()], [formatter_pander()] and [skip_formatter()] for marking a string not to apply the formatter on it. -formatter_sprintf <- structure(function(fmt, ..., .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - eval(sprintf(fmt, ...), envir = .topenv) +#' @seealso This is a [log_formatter()], for alternatives, see +#' [formatter_paste()], [formatter_glue()], +#' [formatter_glue_safe()], [formatter_glue_or_sprintf()], +#' [formatter_logging()], [formatter_json()], [formatter_pander()] +#' and [skip_formatter()] for marking a string not to apply the +#' formatter on it. +formatter_sprintf <- structure(function(fmt, ..., + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + sprintf(fmt, ...) }, generator = quote(formatter_sprintf())) @@ -26,32 +38,35 @@ formatter_sprintf <- structure(function(fmt, ..., .logcall = sys.call(), .topcal #' @inheritParams log_level #' @return character vector #' @export -#' @note Although this is the default log message formatter function, but when \pkg{glue} is not installed, [formatter_sprintf()] will be used as a fallback. -#' @seealso This is a [log_formatter()], for alternatives, see [formatter_paste()], [formatter_sprintf()], [formatter_glue_or_sprintf()], [formatter_glue_safe()], [formatter_logging()], [formatter_json()], [formatter_pander()] and [skip_formatter()] for marking a string not to apply the formatter on it. +#' @note Although this is the default log message formatter function, +#' but when \pkg{glue} is not installed, [formatter_sprintf()] +#' will be used as a fallback. +#' @seealso This is a [log_formatter()], for alternatives, see +#' [formatter_paste()], [formatter_sprintf()], +#' [formatter_glue_or_sprintf()], [formatter_glue_safe()], +#' [formatter_logging()], [formatter_json()], [formatter_pander()] +#' and [skip_formatter()] for marking a string not to apply the +#' formatter on it. #' @importFrom utils str -formatter_glue <- structure(function(..., .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - fail_on_missing_package('glue') - message <- as.character( - tryCatch( - glue::glue(..., .envir = .topenv), - error = function(e) { - stop(paste( - '`glue` failed in `formatter_glue` on:\n\n', - capture.output(str(...)), - '\n\nRaw error message:\n\n', - e$message, - '\n\nPlease consider using another `log_formatter` or', - '`skip_formatter` on strings with curly braces.')) - })) - ## throw warning with logger inputs on empty response - if (length(message) == 0) { - ## disabled until azlogr drops test for no warning here: https://github.com/atalv/azlogr/issues/35 - ## try(warning(paste( - ## 'glue in formatter_glue returned nothing with the following parameters:', - ## paste(..., sep = ' | ') - ## )), silent = TRUE) +formatter_glue <- structure(function(..., + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + fail_on_missing_package("glue") + + withCallingHandlers( + glue::glue(..., .envir = .topenv), + error = function(e) { + args <- paste0(capture.output(str(...)), collapse = "\n") + + stop(paste0( + "`glue` failed in `formatter_glue` on:\n\n", + args, + "\n\nRaw error message:\n\n", + conditionMessage(e), + "\n\nPlease consider using another `log_formatter` or ", + "`skip_formatter` on strings with curly braces." + )) } - message + ) }, generator = quote(formatter_glue())) @@ -60,93 +75,113 @@ formatter_glue <- structure(function(..., .logcall = sys.call(), .topcall = sys. #' @inheritParams log_level #' @return character vector #' @export -#' @seealso This is a [log_formatter()], for alternatives, see [formatter_glue()], [formatter_paste()], [formatter_sprintf()], [formatter_glue()], [formatter_glue_or_sprintf()], [formatter_logging()], [formatter_json()], [formatter_pander()] and [skip_formatter()] for marking a string not to apply the formatter on it. +#' @seealso This is a [log_formatter()], for alternatives, see +#' [formatter_glue()], [formatter_paste()], [formatter_sprintf()], +#' [formatter_glue()], [formatter_glue_or_sprintf()], +#' [formatter_logging()], [formatter_json()], [formatter_pander()] +#' and [skip_formatter()] for marking a string not to apply the +#' formatter on it. #' @importFrom utils str -formatter_glue_safe <- structure(function(..., .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - fail_on_missing_package('glue') - as.character( - tryCatch( - glue::glue_safe(..., .envir = .topenv), - error = function(e) { - stop(paste( - '`glue_safe` failed in `formatter_glue_safe` on:\n\n', - capture.output(str(...)), - '\n\nRaw error message:\n\n', - e$message, - '\n\nPlease consider using another `log_formatter` or', - '`skip_formatter` on strings with curly braces.')) - })) +formatter_glue_safe <- structure(function(..., + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + fail_on_missing_package("glue") + as.character( + tryCatch( + glue::glue_safe(..., .envir = .topenv), + error = function(e) { + stop(paste( + "`glue_safe` failed in `formatter_glue_safe` on:\n\n", + capture.output(str(...)), + "\n\nRaw error message:\n\n", + e$message, + "\n\nPlease consider using another `log_formatter` or", + "`skip_formatter` on strings with curly braces." + )) + } + ) + ) }, generator = quote(formatter_glue_safe())) #' Apply `glue` and `sprintf` #' -#' The best of both words: using both formatter functions in your log messages, which can be useful eg if you are migrating from `sprintf` formatted log messages to `glue` or similar. +#' The best of both words: using both formatter functions in your log +#' messages, which can be useful eg if you are migrating from +#' `sprintf` formatted log messages to `glue` or similar. #' -#' Note that this function tries to be smart when passing arguments to `glue` and `sprintf`, but might fail with some edge cases, and returns an unformatted string. -#' @param msg passed to `sprintf` as `fmt` or handled as part of `...` in `glue` +#' Note that this function tries to be smart when passing arguments to +#' `glue` and `sprintf`, but might fail with some edge cases, and +#' returns an unformatted string. +#' @param msg passed to `sprintf` as `fmt` or handled as part of `...` +#' in `glue` #' @param ... passed to `glue` for the text interpolation #' @inheritParams log_level #' @return character vector +#' @seealso This is a [log_formatter()], for alternatives, see +#' [formatter_paste()], [formatter_sprintf()], [formatter_glue()], +#' [formatter_glue_safe()], [formatter_logging()], +#' [formatter_json()], [formatter_pander()] and [skip_formatter()] +#' for marking a string not to apply the formatter on it. #' @export #' @examples \dontrun{ #' formatter_glue_or_sprintf("{a} + {b} = %s", a = 2, b = 3, 5) -#' formatter_glue_or_sprintf("{pi} * {2} = %s", pi*2) +#' formatter_glue_or_sprintf("{pi} * {2} = %s", pi * 2) #' formatter_glue_or_sprintf("{pi} * {2} = {pi*2}") #' #' formatter_glue_or_sprintf("Hi ", "{c('foo', 'bar')}, did you know that 2*4={2*4}") #' formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4={2*4}") -#' formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4=%s", 2*4) -#' formatter_glue_or_sprintf("Hi %s, did you know that 2*4={2*4}", c('foo', 'bar')) -#' formatter_glue_or_sprintf("Hi %s, did you know that 2*4=%s", c('foo', 'bar'), 2*4) +#' formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4=%s", 2 * 4) +#' formatter_glue_or_sprintf("Hi %s, did you know that 2*4={2*4}", c("foo", "bar")) +#' formatter_glue_or_sprintf("Hi %s, did you know that 2*4=%s", c("foo", "bar"), 2 * 4) #' } -#' @seealso This is a [log_formatter()], for alternatives, see [formatter_paste()], [formatter_sprintf()], [formatter_glue()], [formatter_glue_safe()], [formatter_logging()], [formatter_json()], [formatter_pander()] and [skip_formatter()] for marking a string not to apply the formatter on it. -formatter_glue_or_sprintf <- structure(function(msg, ..., .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { +formatter_glue_or_sprintf <- structure(function(msg, ..., + .logcall = sys.call(), .topcall = sys.call(-1), + .topenv = parent.frame()) { + params <- list(...) - params <- list(...) + ## params without a name are potential sprintf params + sprintfparams <- which(names(params) == "") + if (length(params) > 0 & length(sprintfparams) == 0) { + sprintfparams <- seq_along(params) + } + if (is.null(msg) || length(msg) == 0) { + msg <- "" + } - ## params without a name are potential sprintf params - sprintfparams <- which(names(params) == '') - if (length(params) > 0 & length(sprintfparams) == 0) { - sprintfparams <- seq_along(params) - } - if (is.null(msg) || length(msg) == 0) { - msg <- '' - } + ## early return + if (is.null(msg) || length(msg) == 0) { + return("") + } - ## early return - if (is.null(msg) || length(msg) == 0) { - return('') - } + ## but some unnamed params might belong to glue actually, so + ## let's look for the max number of first unnamed params sprintf expects + sprintftags <- regmatches(msg, gregexpr("%[0-9.+0]*[aAdifeEgGosxX]", msg))[[1]] + sprintfparams <- sprintfparams[seq_len(min(length(sprintftags), length(sprintfparams)))] - ## but some unnamed params might belong to glue actually, so - ## let's look for the max number of first unnamed params sprintf expects - sprintftags <- regmatches(msg, gregexpr('%[0-9.+0]*[aAdifeEgGosxX]', msg))[[1]] - sprintfparams <- sprintfparams[seq_len(min(length(sprintftags), length(sprintfparams)))] - - ## get the actual params instead of indexes - glueparams <- params[setdiff(seq_along(params), sprintfparams)] - sprintfparams <- params[sprintfparams] - - ## first try to apply sprintf - if (length(sprintfparams) > 0) { - sprintfparams[vapply(sprintfparams, is.null, logical(1))] <- 'NULL' - msg <- tryCatch( - do.call(sprintf, c(msg, sprintfparams), envir = .topenv), - error = function(e) msg) - } + ## get the actual params instead of indexes + glueparams <- params[setdiff(seq_along(params), sprintfparams)] + sprintfparams <- params[sprintfparams] - ## then try to glue - fail_on_missing_package('glue') + ## first try to apply sprintf + if (length(sprintfparams) > 0) { + sprintfparams[vapply(sprintfparams, is.null, logical(1))] <- "NULL" msg <- tryCatch( - as.character(sapply(msg, function(msg) { - do.call(glue::glue, c(msg, glueparams), envir = .topenv) - }, USE.NAMES = FALSE)), - error = function(e) msg) + do.call(sprintf, c(msg, sprintfparams), envir = .topenv), + error = function(e) msg + ) + } - ## return - msg + ## then try to glue + fail_on_missing_package("glue") + msg <- tryCatch( + as.character(sapply(msg, function(msg) { + do.call(glue::glue, c(msg, glueparams), envir = .topenv) + }, USE.NAMES = FALSE)), + error = function(e) msg + ) + ## return + msg }, generator = quote(formatter_glue_or_sprintf())) @@ -156,67 +191,89 @@ formatter_glue_or_sprintf <- structure(function(msg, ..., .logcall = sys.call(), #' @return character vector #' @export #' @note This functionality depends on the \pkg{jsonlite} package. +#' @seealso This is a [log_formatter()] potentially to be used with +#' [layout_json_parser()], for alternatives, see +#' [formatter_paste()], [formatter_sprintf()], [formatter_glue()], +#' [formatter_glue_safe()], [formatter_glue_or_sprintf()], +#' [formatter_logging()], [formatter_pander()] and +#' [skip_formatter()] for marking a string not to apply the +#' formatter on it. #' @examples \dontrun{ #' log_formatter(formatter_json) #' log_layout(layout_json_parser()) #' log_info(everything = 42) #' log_info(mtcars = mtcars, species = iris$Species) #' } -#' @seealso This is a [log_formatter()] potentially to be used with [layout_json_parser()], for alternatives, see [formatter_paste()], [formatter_sprintf()], [formatter_glue()], [formatter_glue_safe()], [formatter_glue_or_sprintf()], [formatter_logging()], [formatter_pander()] and [skip_formatter()] for marking a string not to apply the formatter on it. -formatter_json <- structure(function(..., .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - fail_on_missing_package('jsonlite') - eval(as.character(jsonlite::toJSON(list(...), auto_unbox = TRUE)), envir = .topenv) +formatter_json <- structure(function(..., + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + fail_on_missing_package("jsonlite") + as.character(jsonlite::toJSON(list(...), auto_unbox = TRUE)) }, generator = quote(formatter_json())) #' Skip the formatter function #' -#' Adds the `skip_formatter` attribute to an object so that logger will skip calling the formatter function(s). This is useful if you want to preprocess the log message with a custom function instead of the active formatter function(s). Note that the `message` should be a string, and `skip_formatter` should be the only input for the logging function to make this work. -#' @param message character vector directly passed to the appender function in [logger()] +#' Adds the `skip_formatter` attribute to an object so that logger +#' will skip calling the formatter function(s). This is useful if you +#' want to preprocess the log message with a custom function instead +#' of the active formatter function(s). Note that the `message` should +#' be a string, and `skip_formatter` should be the only input for the +#' logging function to make this work. +#' @param message character vector directly passed to the appender +#' function in [logger()] #' @param ... should be never set -#' @return character vector with `skip_formatter` attribute set to `TRUE` +#' @return character vector with `skip_formatter` attribute set to +#' `TRUE` #' @export skip_formatter <- function(message, ...) { - if (!inherits(message, 'character')) { - stop('Cannot skip the formatter function if the log message is not already formatter to a character vector') - } - if (length(list(...)) > 0) { - stop('Cannot skip the formatter function if further arguments are passed besides the actual log message(s)') - } - structure(message, skip_formatter = TRUE) + if (!inherits(message, "character")) { + stop("Cannot skip the formatter function if the log message is not already formatter to a character vector") + } + if (length(list(...)) > 0) { + stop("Cannot skip the formatter function if further arguments are passed besides the actual log message(s)") + } + structure(message, skip_formatter = TRUE) } #' Mimic the default formatter used in the \pkg{logging} package #' -#' The \pkg{logging} package uses a formatter that behaves differently when the input is a string or other R object. If the first argument is a string, then [sprintf()] is being called -- otherwise it does something like [log_eval()] and logs the R expression(s) and the result(s) as well. +#' The \pkg{logging} package uses a formatter that behaves differently +#' when the input is a string or other R object. If the first argument +#' is a string, then [sprintf()] is being called -- otherwise it does +#' something like [log_eval()] and logs the R expression(s) and the +#' result(s) as well. +#' @param ... string and further params passed to `sprintf` or R +#' expressions to be evaluated +#' @inheritParams log_level +#' @return character vector +#' @export +#' @seealso This is a [log_formatter()], for alternatives, see +#' [formatter_paste()], [formatter_glue()], +#' [formatter_glue_safe()], [formatter_glue_or_sprintf()], +#' [formatter_json()], [formatter_pander()] and [skip_formatter()] +#' for marking a string not to apply the formatter on it. #' @examples \dontrun{ #' log_formatter(formatter_logging) -#' log_info('42') +#' log_info("42") #' log_info(42) -#' log_info(4+2) -#' log_info('foo %s', 'bar') -#' log_info('vector %s', 1:3) -#' log_info(12, 1+1, 2 * 2) +#' log_info(4 + 2) +#' log_info("foo %s", "bar") +#' log_info("vector %s", 1:3) +#' log_info(12, 1 + 1, 2 * 2) #' } -#' @param ... string and further params passed to `sprintf` or R expressions to be evaluated -#' @inheritParams log_level -#' @return character vector -#' @export -#' @seealso This is a [log_formatter()], for alternatives, see [formatter_paste()], [formatter_glue()], [formatter_glue_safe()], [formatter_glue_or_sprintf()], [formatter_json()], [formatter_pander()] and [skip_formatter()] for marking a string not to apply the formatter on it. -formatter_logging <- structure(function(..., .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - - params <- list(...) - .logcall <- substitute(.logcall) +formatter_logging <- structure(function(..., + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + params <- list(...) + .logcall <- substitute(.logcall) - if (is.character(params[[1]])) { - return(do.call(sprintf, params, envir = .topenv)) - } - - sapply(1:length(params), function(i) { - paste(deparse(as.list(.logcall)[-1][[i]]), params[[i]], sep = ': ') - }) + if (is.character(params[[1]])) { + return(do.call(sprintf, params, envir = .topenv)) + } + sapply(seq_along(params), function(i) { + paste(deparse(as.list(.logcall)[-1][[i]]), params[[i]], sep = ": ") + }) }, generator = quote(formatter_logging())) @@ -225,21 +282,23 @@ formatter_logging <- structure(function(..., .logcall = sys.call(), .topcall = s #' @param ... optional parameters passed to `pander` #' @inheritParams log_level #' @return character vector +#' @note This functionality depends on the \pkg{pander} package. +#' @export +#' @seealso This is a [log_formatter()], for alternatives, see +#' [formatter_paste()], [formatter_sprintf()], [formatter_glue()], +#' [formatter_glue_safe()], [formatter_glue_or_sprintf()], +#' [formatter_logging()] #' @examples \dontrun{ #' log_formatter(formatter_pander) -#' log_info('42') +#' log_info("42") #' log_info(42) -#' log_info(4+2) +#' log_info(4 + 2) #' log_info(head(iris)) -#' log_info(head(iris), style = 'simple') +#' log_info(head(iris), style = "simple") #' log_info(lm(hp ~ wt, mtcars)) #' } -#' @note This functionality depends on the \pkg{pander} package. -#' @export -#' @seealso This is a [log_formatter()], for alternatives, see [formatter_paste()], [formatter_sprintf()], [formatter_glue()], [formatter_glue_safe()], [formatter_glue_or_sprintf()], [formatter_logging()] -formatter_pander <- structure(function(x, ..., .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - - fail_on_missing_package('pander') - eval(pander::pander_return(x, ...), envir = .topenv) - +formatter_pander <- structure(function(x, ..., + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + fail_on_missing_package("pander") + pander::pander_return(x, ...) }, generator = quote(formatter_pander())) diff --git a/R/helpers.R b/R/helpers.R index d7cde06c..d3d7b43f 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -1,7 +1,12 @@ #' Evaluate an expression and log results -#' @param expr R expression to be evaluated while logging the expression itself along with the result +#' @param expr R expression to be evaluated while logging the +#' expression itself along with the result #' @param level [log_levels()] -#' @param multiline setting to `FALSE` will print both the expression (enforced to be on one line by removing line-breaks if any) and its result on a single line separated by `=>`, while setting to `TRUE` will log the expression and the result in separate sections reserving line-breaks and rendering the printed results +#' @param multiline setting to `FALSE` will print both the expression +#' (enforced to be on one line by removing line-breaks if any) and +#' its result on a single line separated by `=>`, while setting to +#' `TRUE` will log the expression and the result in separate +#' sections reserving line-breaks and rendering the printed results #' @examples \dontrun{ #' log_eval(pi * 2, level = INFO) #' @@ -30,49 +35,46 @@ #' log_eval(f <- log_eval, multiline = TRUE) #' #' ## doing something computationally intensive -#' log_eval(system.time(for(i in 1:100) mad(runif(1000))), multiline = TRUE) +#' log_eval(system.time(for (i in 1:100) mad(runif(1000))), multiline = TRUE) #' } #' @importFrom utils capture.output #' @export log_eval <- function(expr, level = TRACE, multiline = FALSE) { - - ## capture call - expr <- substitute(expr) - exprs <- gsub('\n', ' ', deparse(expr), fixed = TRUE) - - ## evaluate call and store results - timer <- Sys.time() - res <- withVisible(eval.parent(expr)) - - ## log expression and results - if (multiline == FALSE) { - - log_level(level, skip_formatter( - paste( - shQuote(paste(exprs, collapse = ' ')), - '=>', - shQuote(paste(gsub('\n', ' ', deparse(res$value)), collapse = ' '))))) - - } else { - - log_level(level, 'Running expression: ====================') - log_level(level, skip_formatter(exprs)) - log_level(level, 'Results: ===============================') - log_level(level, skip_formatter(capture.output(res$value))) - log_level(level, paste( - 'Elapsed time:', - round(difftime(Sys.time(), timer, units = 'secs'), 2), - 'sec')) - - } - - ## return the results of the call - if (res$visible == TRUE) { - return(res$value) - } else { - return(invisible(res$value)) - } - + ## capture call + expr <- substitute(expr) + exprs <- gsub("\n", " ", deparse(expr), fixed = TRUE) + + ## evaluate call and store results + timer <- Sys.time() + res <- withVisible(eval.parent(expr)) + + ## log expression and results + if (multiline == FALSE) { + log_level(level, skip_formatter( + paste( + shQuote(paste(exprs, collapse = " ")), + "=>", + shQuote(paste(gsub("\n", " ", deparse(res$value)), collapse = " ")) + ) + )) + } else { + log_level(level, "Running expression: ====================") + log_level(level, skip_formatter(exprs)) + log_level(level, "Results: ===============================") + log_level(level, skip_formatter(capture.output(res$value))) + log_level(level, paste( + "Elapsed time:", + round(difftime(Sys.time(), timer, units = "secs"), 2), + "sec" + )) + } + + ## return the results of the call + if (res$visible == TRUE) { + return(res$value) + } else { + return(invisible(res$value)) + } } @@ -83,33 +85,33 @@ log_eval <- function(expr, level = TRACE, multiline = FALSE) { #' @export #' @examples #' log_separator() -#' log_separator(ERROR, separator = '!', width = 60) -#' log_separator(ERROR, separator = '!', width = 100) -#' logger <- layout_glue_generator(format = '{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}') +#' log_separator(ERROR, separator = "!", width = 60) +#' log_separator(ERROR, separator = "!", width = 100) +#' logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") #' log_layout(logger) -#' log_separator(ERROR, separator = '!', width = 100) +#' log_separator(ERROR, separator = "!", width = 100) #' log_layout(layout_blank) -#' log_separator(ERROR, separator = '!', width = 80) +#' log_separator(ERROR, separator = "!", width = 80) #' @seealso [log_with_separator()] log_separator <- function(level = INFO, namespace = NA_character_, - separator = '=', + separator = "=", width = 80, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - - stopifnot(length(separator) == 1, nchar(separator) == 1) - - base_info_chars <- nchar(catch_base_log(level, namespace, .topcall = .topcall, .topenv = .topenv)) - - log_level( - paste(rep(separator, max(0, width - base_info_chars)), collapse = ''), - level = level, - namespace = namespace, - .logcall = .logcall, - .topcall = .topcall, - .topenv = .topenv) + stopifnot(length(separator) == 1, nchar(separator) == 1) + + base_info_chars <- nchar(catch_base_log(level, namespace, .topcall = .topcall, .topenv = .topenv)) + + log_level( + paste(rep(separator, max(0, width - base_info_chars)), collapse = ""), + level = level, + namespace = namespace, + .logcall = .logcall, + .topcall = .topcall, + .topenv = .topenv + ) } @@ -118,57 +120,61 @@ log_separator <- function(level = INFO, #' @inheritParams log_separator #' @export #' @examples -#' log_with_separator('An important message') -#' log_with_separator('Some critical KPI down!!!', separator = '$') -#' log_with_separator('This message is worth a {1e3} words') -#' log_with_separator(paste( -#' 'A very important message with a bunch of extra words that will', -#' 'eventually wrap into a multi-line message for our quite nice demo :wow:')) +#' log_with_separator("An important message") +#' log_with_separator("Some critical KPI down!!!", separator = "$") +#' log_with_separator("This message is worth a {1e3} words") #' log_with_separator(paste( -#' 'A very important message with a bunch of extra words that will', -#' 'eventually wrap into a multi-line message for our quite nice demo :wow:'), -#' width = 60) -#' log_with_separator('Boo!', level = FATAL) +#' "A very important message with a bunch of extra words that will", +#' "eventually wrap into a multi-line message for our quite nice demo :wow:" +#' )) +#' log_with_separator( +#' paste( +#' "A very important message with a bunch of extra words that will", +#' "eventually wrap into a multi-line message for our quite nice demo :wow:" +#' ), +#' width = 60 +#' ) +#' log_with_separator("Boo!", level = FATAL) #' log_layout(layout_blank) -#' log_with_separator('Boo!', level = FATAL) -#' logger <- layout_glue_generator(format = '{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}') +#' log_with_separator("Boo!", level = FATAL) +#' logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") #' log_layout(logger) -#' log_with_separator('Boo!', level = FATAL, width = 120) +#' log_with_separator("Boo!", level = FATAL, width = 120) #' @seealso [log_separator()] -log_with_separator <- function(..., level = INFO, namespace = NA_character_, separator = '=', width = 80) { - - base_info_chars <- nchar(catch_base_log(level, namespace, .topcall = sys.call(-1))) - - log_separator( - level = level, - separator = separator, - width = width, - namespace = namespace, - .logcall = sys.call(), - .topcall = sys.call(-1), - .topenv = parent.frame() +log_with_separator <- function(..., level = INFO, namespace = NA_character_, separator = "=", width = 80) { + base_info_chars <- nchar(catch_base_log(level, namespace, .topcall = sys.call(-1))) + + log_separator( + level = level, + separator = separator, + width = width, + namespace = namespace, + .logcall = sys.call(), + .topcall = sys.call(-1), + .topenv = parent.frame() + ) + + message <- do.call(eval(log_formatter()), list(...)) + message <- strwrap(message, max(0, width - base_info_chars - 4)) + message <- sapply(message, function(m) { + paste0( + separator, " ", m, + paste(rep(" ", max(0, width - base_info_chars - 4 - nchar(m))), collapse = ""), + " ", separator ) + }) - message <- do.call(eval(log_formatter()), list(...)) - message <- strwrap(message, max(0, width - base_info_chars - 4)) - message <- sapply(message, function(m) { - paste0( - separator, ' ', m, - paste(rep(' ', max(0, width - base_info_chars - 4 - nchar(m))), collapse = ''), - ' ', separator) - }) - - log_level(skip_formatter(message), level = level, namespace = namespace, .topenv = parent.frame()) - - log_separator( - level = level, - separator = separator, - width = width, - namespace = namespace, - .logcall = sys.call(), - .topcall = sys.call(-1), - .topenv = parent.frame() - ) + log_level(skip_formatter(message), level = level, namespace = namespace, .topenv = parent.frame()) + + log_separator( + level = level, + separator = separator, + width = width, + namespace = namespace, + .logcall = sys.call(), + .topcall = sys.call(-1), + .topenv = parent.frame() + ) } @@ -178,37 +184,38 @@ log_with_separator <- function(..., level = INFO, namespace = NA_character_, sep #' @param namespace x #' @export #' @examples \dontrun{ -#' log_tictoc('warming up') +#' log_tictoc("warming up") #' Sys.sleep(0.1) -#' log_tictoc('running') +#' log_tictoc("running") #' Sys.sleep(0.1) -#' log_tictoc('running') +#' log_tictoc("running") #' Sys.sleep(runif(1)) -#' log_tictoc('and running') +#' log_tictoc("and running") #' } #' @author Thanks to Neal Fultz for the idea and original implementation! log_tictoc <- function(..., level = INFO, namespace = NA_character_) { + ns <- fallback_namespace(namespace) - ns <- fallback_namespace(namespace) - - on.exit({ - assign(ns, toc, envir = tictocs) - }) - - nsenv <- get(fallback_namespace(namespace), envir = namespaces) - tic <- get0(ns, envir = tictocs, ifnotfound = Sys.time()) - toc <- Sys.time() - tictoc <- difftime(toc, tic) - - log_level( - paste(ns, 'timer', - ifelse(round(tictoc, 2) == 0, 'tic', 'toc'), - round(tictoc, 2), attr(tictoc, 'units') , '-- '), - ..., level = level, namespace = namespace, - .logcall = sys.call(), - .topcall = sys.call(-1), - .topenv = parent.frame()) + on.exit({ + assign(ns, toc, envir = tictocs) + }) + tic <- get0(ns, envir = tictocs, ifnotfound = Sys.time()) + toc <- Sys.time() + tictoc <- difftime(toc, tic) + + log_level( + paste( + ns, "timer", + ifelse(round(tictoc, 2) == 0, "tic", "toc"), + round(tictoc, 2), attr(tictoc, "units"), "-- " + ), + ..., + level = level, namespace = namespace, + .logcall = sys.call(), + .topcall = sys.call(-1), + .topenv = parent.frame() + ) } tictocs <- new.env() @@ -217,7 +224,7 @@ tictocs <- new.env() #' @param expression call #' @export #' @examples \dontrun{ -#' log_failure('foobar') +#' log_failure("foobar") #' log_failure(foobar) #' } log_failure <- function(expression) { diff --git a/R/hooks.R b/R/hooks.R index 83a7b30a..e3f4b56a 100644 --- a/R/hooks.R +++ b/R/hooks.R @@ -1,72 +1,92 @@ - #' Injects a logger call to standard messages #' -#' This function uses `trace` to add a `log_info` function call when `message` is called to log the informative messages with the `logger` layout and appender. +#' This function uses `trace` to add a `log_info` function call when +#' `message` is called to log the informative messages with the +#' `logger` layout and appender. #' @export #' @examples \dontrun{ #' log_messages() -#' message('hi there') +#' message("hi there") #' } log_messages <- function() { - if (any(sapply(globalCallingHandlers()[names(globalCallingHandlers()) == 'message'], - attr, which = 'implements') == 'log_messages')) { - warning('Ignoring this call to log_messages as it was registered previously.') - } else { - globalCallingHandlers( - message = structure(function(m) { - logger::log_level(logger::INFO, m$message, .topcall = m$call) - }, implements = 'log_messages')) - } + if (any(sapply( + globalCallingHandlers()[names(globalCallingHandlers()) == "message"], + attr, + which = "implements" + ) == "log_messages")) { + warning("Ignoring this call to log_messages as it was registered previously.") + } else { + globalCallingHandlers( + message = structure(function(m) { + logger::log_level(logger::INFO, m$message, .topcall = m$call) + }, implements = "log_messages") + ) + } } #' Injects a logger call to standard warnings #' -#' This function uses `trace` to add a `log_warn` function call when `warning` is called to log the warning messages with the `logger` layout and appender. +#' This function uses `trace` to add a `log_warn` function call when +#' `warning` is called to log the warning messages with the `logger` +#' layout and appender. #' @param muffle if TRUE, the warning is not shown after being logged #' @export #' @examples \dontrun{ #' log_warnings() -#' for (i in 1:5) { Sys.sleep(runif(1)); warning(i) } +#' for (i in 1:5) { +#' Sys.sleep(runif(1)) +#' warning(i) #' } -log_warnings <- function(muffle = getOption('logger_muffle_warnings', FALSE)) { - if (any(sapply(globalCallingHandlers()[names(globalCallingHandlers()) == 'warning'], - attr, which = 'implements') == 'log_warnings')) { - warning('Ignoring this call to log_warnings as it was registered previously.') - } else { - globalCallingHandlers( - warning = structure(function(m) { - logger::log_level(logger::WARN, m$message, .topcall = m$call) - if (isTRUE(muffle)) { - invokeRestart('muffleWarning') - } - }, implements = 'log_warnings')) - } +#' } +log_warnings <- function(muffle = getOption("logger_muffle_warnings", FALSE)) { + if (any(sapply( + globalCallingHandlers()[names(globalCallingHandlers()) == "warning"], + attr, + which = "implements" + ) == "log_warnings")) { + warning("Ignoring this call to log_warnings as it was registered previously.") + } else { + globalCallingHandlers( + warning = structure(function(m) { + logger::log_level(logger::WARN, m$message, .topcall = m$call) + if (isTRUE(muffle)) { + invokeRestart("muffleWarning") + } + }, implements = "log_warnings") + ) + } } #' Injects a logger call to standard errors #' -#' This function uses `trace` to add a `log_error` function call when `stop` is called to log the error messages with the `logger` layout and appender. +#' This function uses `trace` to add a `log_error` function call when +#' `stop` is called to log the error messages with the `logger` layout +#' and appender. #' @param muffle if TRUE, the error is not thrown after being logged #' @export #' @examples \dontrun{ #' log_errors() -#' stop('foobar') +#' stop("foobar") #' } -log_errors <- function(muffle = getOption('logger_muffle_errors', FALSE)) { - if (any(sapply(globalCallingHandlers()[names(globalCallingHandlers()) == 'error'], - attr, which = 'implements') == 'log_errors')) { - warning('Ignoring this call to log_errors as it was registered previously.') - } else { - globalCallingHandlers( - error = structure(function(m) { - logger::log_level(logger::ERROR, m$message, .topcall = m$call) - if (isTRUE(muffle)) { - invokeRestart('abort') - } - }, implements = 'log_errors')) - } +log_errors <- function(muffle = getOption("logger_muffle_errors", FALSE)) { + if (any(sapply( + globalCallingHandlers()[names(globalCallingHandlers()) == "error"], + attr, + which = "implements" + ) == "log_errors")) { + warning("Ignoring this call to log_errors as it was registered previously.") + } else { + globalCallingHandlers( + error = structure(function(m) { + logger::log_level(logger::ERROR, m$message, .topcall = m$call) + if (isTRUE(muffle)) { + invokeRestart("abort") + } + }, implements = "log_errors") + ) + } } @@ -83,22 +103,20 @@ log_errors <- function(muffle = getOption('logger_muffle_errors', FALSE)) { #' library(shiny) #' #' ui <- bootstrapPage( -#' numericInput('mean', 'mean', 0), -#' numericInput('sd', 'sd', 1), -#' textInput('title', 'title', 'title'), -#' textInput('foo', 'This is not used at all, still gets logged', 'foo'), -#' passwordInput('password', 'Password not to be logged', 'secret'), -#' plotOutput('plot') +#' numericInput("mean", "mean", 0), +#' numericInput("sd", "sd", 1), +#' textInput("title", "title", "title"), +#' textInput("foo", "This is not used at all, still gets logged", "foo"), +#' passwordInput("password", "Password not to be logged", "secret"), +#' plotOutput("plot") #' ) #' #' server <- function(input, output) { +#' logger::log_shiny_input_changes(input, excluded_inputs = "password") #' -#' logger::log_shiny_input_changes(input, excluded_inputs = 'password') -#' -#' output$plot <- renderPlot({ -#' hist(rnorm(1e3, input$mean, input$sd), main = input$title) -#' }) -#' +#' output$plot <- renderPlot({ +#' hist(rnorm(1e3, input$mean, input$sd), main = input$title) +#' }) #' } #' #' shinyApp(ui = ui, server = server) @@ -107,34 +125,38 @@ log_shiny_input_changes <- function(input, level = INFO, namespace = NA_character_, excluded_inputs = character()) { + fail_on_missing_package("shiny") + fail_on_missing_package("jsonlite") - fail_on_missing_package('shiny') - fail_on_missing_package('jsonlite') - if (!shiny::isRunning()) { - stop('No Shiny app running, it makes no sense to call this function outside of a Shiny app') - } - - input_values <- shiny::isolate(shiny::reactiveValuesToList(input)) - assignInMyNamespace('shiny_input_values', input_values) - log_level(level, skip_formatter(paste( - 'Default Shiny inputs initialized:', - as.character(jsonlite::toJSON(input_values, auto_unbox = TRUE)))), namespace = namespace) + session <- shiny::getDefaultReactiveDomain() + ns <- ifelse(!is.null(session), session$ns(character(0)), "") - shiny::observe({ - old_input_values <- shiny_input_values - new_input_values <- shiny::reactiveValuesToList(input) - names <- unique(c(names(old_input_values), names(new_input_values))) - names <- setdiff(names, excluded_inputs) - for (name in names) { - old <- old_input_values[name] - new <- new_input_values[name] - if (!identical(old, new)) { - log_level(level, 'Shiny input change detected on {name}: {old} -> {new}', namespace = namespace) - } - } - assignInNamespace('shiny_input_values', new_input_values, ns = 'logger') - }) + if (!(shiny::isRunning() || inherits(session, "MockShinySession") || inherits(session, "session_proxy"))) { + stop("No Shiny app running, it makes no sense to call this function outside of a Shiny app") + } + input_values <- shiny::isolate(shiny::reactiveValuesToList(input)) + assignInMyNamespace("shiny_input_values", input_values) + log_level(level, skip_formatter(trimws(paste( + ns, + "Default Shiny inputs initialized:", + as.character(jsonlite::toJSON(input_values, auto_unbox = TRUE)) + ))), namespace = namespace) + shiny::observe({ + old_input_values <- shiny_input_values + new_input_values <- shiny::reactiveValuesToList(input) + names <- unique(c(names(old_input_values), names(new_input_values))) + names <- setdiff(names, excluded_inputs) + for (name in names) { + old <- old_input_values[name] + new <- new_input_values[name] + if (!identical(old, new)) { + message <- trimws("{ns} Shiny input change detected in {name}: {old} -> {new}") + log_level(level, message, namespace = namespace) + } + } + assignInNamespace("shiny_input_values", new_input_values, ns = "logger") + }) } shiny_input_values <- NULL diff --git a/R/layouts.R b/R/layouts.R index 7c5fa439..a352cc83 100644 --- a/R/layouts.R +++ b/R/layouts.R @@ -1,26 +1,29 @@ #' Collect useful information about the logging environment to be used in log messages #' #' Available variables to be used in the log formatter functions, eg in [layout_glue_generator()]: -#' \itemize{ -#' \item levelr: log level as an R object, eg [INFO()] -#' \item level: log level as a string, eg [INFO()] -#' \item time: current time as `POSIXct` -#' \item node: name by which the machine is known on the network as reported by `Sys.info` -#' \item arch: machine type, typically the CPU architecture -#' \item os_name: Operating System's name -#' \item os_release: Operating System's release -#' \item os_version: Operating System's version -#' \item user: name of the real user id as reported by `Sys.info` -#' \item pid: the process identification number of the R session -#' \item node: name by which the machine is known on the network as reported by `Sys.info` -#' \item r_version: R's major and minor version as a string -#' \item ns: namespace usually defaults to `global` or the name of the holding R package of the calling the logging function -#' \item ns_pkg_version: the version of `ns` when it's a package -#' \item ans: same as `ns` if there's a defined [logger()] for the namespace, otherwise a fallback namespace (eg usually `global`) -#' \item topenv: the name of the top environment from which the parent call was called (eg R package name or `GlobalEnv`) -#' \item call: parent call (if any) calling the logging function -#' \item fn: function's (if any) name calling the logging function -#' } +#' +#' * `levelr`: log level as an R object, eg [INFO()] +#' * `level`: log level as a string, eg [INFO()] +#' * `time`: current time as `POSIXct` +#' * `node`: name by which the machine is known on the network as reported by `Sys.info` +#' * `arch`: machine type, typically the CPU architecture +#' * `os_name`: Operating System's name +#' * `os_release`: Operating System's release +#' * `os_version`: Operating System's version +#' * `user`: name of the real user id as reported by `Sys.info` +#' * `pid`: the process identification number of the R session +#' * `node`: name by which the machine is known on the network as reported by `Sys.info` +#' * `r_version`: R's major and minor version as a string +#' * `ns`: namespace usually defaults to `global` or the name of the holding R package +#' of the calling the logging function +#' * `ns_pkg_version`: the version of `ns` when it's a package +#' * `ans`: same as `ns` if there's a defined [logger()] for the namespace, +#' otherwise a fallback namespace (eg usually `global`) +#' * `topenv`: the name of the top environment from which the parent call was called +#' (eg R package name or `GlobalEnv`) +#' * `call`: parent call (if any) calling the logging function +#' * `fn`: function's (if any) name calling the logging function +#' #' @param log_level log level as per [log_levels()] #' @inheritParams log_level #' @return list @@ -29,105 +32,110 @@ #' @seealso [layout_glue_generator()] get_logger_meta_variables <- function(log_level = NULL, namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - - sysinfo <- Sys.info() - timestamp <- Sys.time() - - list( - ns = namespace, - ans = fallback_namespace(namespace), - topenv = top_env_name(.topenv), - fn = deparse_to_one_line(.topcall[[1]]), - call = deparse_to_one_line(.topcall), - - time = timestamp, - levelr = log_level, - level = attr(log_level, 'level'), - - pid = Sys.getpid(), - - ## R and ns package versions - r_version = paste0(R.Version()[c('major', 'minor')], collapse = '.'), - ns_pkg_version = tryCatch(as.character(packageVersion(namespace)), error = function(e) NA_character_), - - ## stuff from Sys.info - node = sysinfo[['nodename']], - arch = sysinfo[['machine']], - os_name = sysinfo[['sysname']], - os_release = sysinfo[['release']], - os_version = sysinfo[['version']], - user = sysinfo[['user']] - ## NOTE might be better to rely on the whoami pkg? - - ## TODO jenkins (or any) env vars => no need to get here, users can write custom layouts - ## TODO seed - ) - + sysinfo <- Sys.info() + timestamp <- Sys.time() + + list( + ns = namespace, + ans = fallback_namespace(namespace), + topenv = top_env_name(.topenv), + fn = deparse_to_one_line(.topcall[[1]]), + call = deparse_to_one_line(.topcall), + time = timestamp, + levelr = log_level, + level = attr(log_level, "level"), + pid = Sys.getpid(), + + ## R and ns package versions + r_version = paste0(R.Version()[c("major", "minor")], collapse = "."), + ns_pkg_version = tryCatch(as.character(packageVersion(namespace)), error = function(e) NA_character_), + + ## stuff from Sys.info + node = sysinfo[["nodename"]], + arch = sysinfo[["machine"]], + os_name = sysinfo[["sysname"]], + os_release = sysinfo[["release"]], + os_version = sysinfo[["version"]], + user = sysinfo[["user"]] + ## NOTE might be better to rely on the whoami pkg? + + ## TODO jenkins (or any) env vars => no need to get here, users can write custom layouts + ## TODO seed + ) } #' Generate log layout function using common variables available via glue syntax #' #' `format` is passed to `glue` with access to the below variables: -#' \itemize{ -#' \item msg: the actual log message -#' \item further variables set by [get_logger_meta_variables()] -#' } -#' @param format `glue`-flavored layout of the message using the above variables -#' @return function taking `level` and `msg` arguments - keeping the original call creating the generator in the `generator` attribute that is returned when calling [log_layout()] for the currently used layout +#' \itemize{ \item msg: the actual log message \item further variables +#' set by [get_logger_meta_variables()] } +#' @param format `glue`-flavored layout of the message using the above +#' variables +#' @return function taking `level` and `msg` arguments - keeping the +#' original call creating the generator in the `generator` attribute +#' that is returned when calling [log_layout()] for the currently +#' used layout #' @export #' @examples \dontrun{ #' example_layout <- layout_glue_generator( -#' format = '{node}/{pid}/{ns}/{ans}/{topenv}/{fn} {time} {level}: {msg}') -#' example_layout(INFO, 'try {runif(1)}') +#' format = "{node}/{pid}/{ns}/{ans}/{topenv}/{fn} {time} {level}: {msg}" +#' ) +#' example_layout(INFO, "try {runif(1)}") #' #' log_layout(example_layout) -#' log_info('try {runif(1)}') +#' log_info("try {runif(1)}") #' } #' @seealso See example calls from [layout_glue()] and [layout_glue_colors()]. layout_glue_generator <- function(format = '{level} [{format(time, "%Y-%m-%d %H:%M:%S")}] {msg}') { + force(format) - force(format) - - structure(function(level, msg, namespace = NA_character_, - .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - - fail_on_missing_package('glue') - if (!inherits(level, 'loglevel')) { - stop('Invalid log level, see ?log_levels') - } - - with(get_logger_meta_variables( - log_level = level, namespace = namespace, - .logcall = .logcall, .topcall = .topcall, .topenv = .topenv), - glue::glue(format)) - - }, generator = deparse(match.call())) + structure(function(level, msg, namespace = NA_character_, + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + fail_on_missing_package("glue") + if (!inherits(level, "loglevel")) { + stop("Invalid log level, see ?log_levels") + } + with( + get_logger_meta_variables( + log_level = level, namespace = namespace, + .logcall = .logcall, .topcall = .topcall, .topenv = .topenv + ), + glue::glue(format) + ) + }, generator = deparse(match.call())) } -#' Format a log record by including the raw message without anything added or modified +#' Format a log record by including the raw message without anything +#' added or modified #' @inheritParams log_level #' @param msg string message #' @return character vector #' @export -#' @seealso This is a [log_layout()], for alternatives, see [layout_simple()], [layout_glue_colors()], [layout_json()], or generator functions such as [layout_glue_generator()] +#' @seealso This is a [log_layout()], for alternatives, see +#' [layout_simple()], [layout_glue_colors()], [layout_json()], or +#' generator functions such as [layout_glue_generator()] layout_blank <- structure(function(level, msg, namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - msg + msg }, generator = quote(layout_blank())) -#' Format a log record by concatenating the log level, timestamp and message +#' Format a log record by concatenating the log level, timestamp and +#' message #' @inheritParams log_level #' @param msg string message #' @return character vector #' @export -#' @seealso This is a [log_layout()], for alternatives, see [layout_blank()], [layout_glue()], [layout_glue_colors()], [layout_json()], [layout_json_parser()], or generator functions such as [layout_glue_generator()] +#' @seealso This is a [log_layout()], for alternatives, see +#' [layout_blank()], [layout_glue()], [layout_glue_colors()], +#' [layout_json()], [layout_json_parser()], or generator functions +#' such as [layout_glue_generator()] layout_simple <- structure(function(level, msg, namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - paste0(attr(level, 'level'), ' [', format(Sys.time(), "%Y-%m-%d %H:%M:%S"), '] ', msg) + paste0(attr(level, "level"), " [", format(Sys.time(), "%Y-%m-%d %H:%M:%S"), "] ", msg) }, generator = quote(layout_simple())) @@ -136,133 +144,156 @@ layout_simple <- structure(function(level, msg, namespace = NA_character_, #' @param msg string message #' @return character vector #' @export -#' @seealso This is a [log_layout()], for alternatives, see [layout_blank()], [layout_glue()], [layout_glue_colors()], [layout_json()], [layout_json_parser()], or generator functions such as [layout_glue_generator()] +#' @seealso This is a [log_layout()], for alternatives, see +#' [layout_blank()], [layout_glue()], [layout_glue_colors()], +#' [layout_json()], [layout_json_parser()], or generator functions +#' such as [layout_glue_generator()] #' @examples \dontrun{ #' log_layout(layout_logging) #' log_info(42) -#' log_info(42, namespace = 'everything') +#' log_info(42, namespace = "everything") #' -#' devtools::load_all(system.file('demo-packages/logger-tester-package', package = 'logger')) +#' devtools::load_all(system.file("demo-packages/logger-tester-package", package = "logger")) #' logger_tester_function(INFO, 42) #' } layout_logging <- structure(function(level, msg, namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - meta <- get_logger_meta_variables( - log_level = level, namespace = namespace, - .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) - paste0(format(Sys.time(), "%Y-%m-%d %H:%M:%S"), ' ', - attr(level, 'level'), ':', - ifelse(meta$ns == 'global', '', meta$ns), ':', - msg) + meta <- get_logger_meta_variables( + log_level = level, namespace = namespace, + .logcall = .logcall, .topcall = .topcall, .topenv = .topenv + ) + paste0( + format(Sys.time(), "%Y-%m-%d %H:%M:%S"), " ", + attr(level, "level"), ":", + ifelse(meta$ns == "global", "", meta$ns), ":", + msg + ) }, generator = quote(layout_logging())) #' Format a log message with `glue` #' -#' By default, this layout includes the log level of the log record as per [log_levels()], the current timestamp and the actual log message -- that you can override via calling [layout_glue_generator()] directly. For colorized output, see [layout_glue_colors()]. +#' By default, this layout includes the log level of the log record as +#' per [log_levels()], the current timestamp and the actual log +#' message -- that you can override via calling +#' [layout_glue_generator()] directly. For colorized output, see +#' [layout_glue_colors()]. #' @inheritParams layout_simple #' @return character vector #' @export -#' @seealso This is a [log_layout()], for alternatives, see [layout_blank()], [layout_simple()], [layout_glue_colors()], [layout_json()], [layout_json_parser()], or generator functions such as [layout_glue_generator()] +#' @seealso This is a [log_layout()], for alternatives, see +#' [layout_blank()], [layout_simple()], [layout_glue_colors()], +#' [layout_json()], [layout_json_parser()], or generator functions +#' such as [layout_glue_generator()] layout_glue <- layout_glue_generator() #' Format a log message with `glue` and ANSI escape codes to add colors -#' +#' #' Colour log levels based on their severity. Log levels are coloured #' with [colorize_by_log_level()] and the messages are coloured with #' [grayscale_by_log_level()]. -#' +#' #' @inheritParams layout_simple #' @return character vector #' @export -#' @examplesIf requireNamespace("crayon") -#' log_layout(layout_glue_colors) -#' log_threshold(TRACE) -#' log_info('Starting the script...') -#' log_debug('This is the second line') -#' log_trace('That is being placed right after the first one.') -#' log_warn('Some errors might come!') -#' log_error('This is a problem') -#' log_debug('Getting an error is usually bad') -#' log_error('This is another problem') -#' log_fatal('The last problem.') -#' @seealso This is a [log_layout()], for alternatives, see [layout_blank()], [layout_simple()], [layout_glue()], [layout_json()], [layout_json_parser()], or generator functions such as [layout_glue_generator()] +#' @seealso This is a [log_layout()], for alternatives, see +#' [layout_blank()], [layout_simple()], [layout_glue()], +#' [layout_json()], [layout_json_parser()], or generator functions +#' such as [layout_glue_generator()] #' @note This functionality depends on the \pkg{crayon} package. +#' @examplesIf requireNamespace("crayon") +#' log_layout(layout_glue_colors) +#' log_threshold(TRACE) +#' log_info("Starting the script...") +#' log_debug("This is the second line") +#' log_trace("That is being placed right after the first one.") +#' log_warn("Some errors might come!") +#' log_error("This is a problem") +#' log_debug("Getting an error is usually bad") +#' log_error("This is another problem") +#' log_fatal("The last problem.") layout_glue_colors <- layout_glue_generator( - format = paste( - '{crayon::bold(colorize_by_log_level(level, levelr))}', - '[{crayon::italic(format(time, "%Y-%m-%d %H:%M:%S"))}]', - '{grayscale_by_log_level(msg, levelr)}')) + format = paste( + "{crayon::bold(colorize_by_log_level(level, levelr))}", + '[{crayon::italic(format(time, "%Y-%m-%d %H:%M:%S"))}]', + "{grayscale_by_log_level(msg, levelr)}" + ) +) #' Generate log layout function rendering JSON -#' @param fields character vector of field names to be included in the JSON +#' @param fields character vector of field names to be included in the +#' JSON #' @return character vector #' @export +#' @note This functionality depends on the \pkg{jsonlite} package. +#' @seealso This is a [log_layout()], for alternatives, see +#' [layout_blank()], [layout_simple()], [layout_glue()], +#' [layout_glue_colors()], [layout_json_parser()], or generator +#' functions such as [layout_glue_generator()] #' @examples \dontrun{ #' log_layout(layout_json()) #' log_info(42) -#' log_info('ok {1:3} + {1:3} = {2*(1:3)}') +#' log_info("ok {1:3} + {1:3} = {2*(1:3)}") #' } -#' @note This functionality depends on the \pkg{jsonlite} package. -#' @seealso This is a [log_layout()], for alternatives, see [layout_blank()], [layout_simple()], [layout_glue()], [layout_glue_colors()], [layout_json_parser()], or generator functions such as [layout_glue_generator()] -layout_json <- function(fields = c('time', 'level', 'ns', 'ans', 'topenv', 'fn', 'node', 'arch', 'os_name', 'os_release', 'os_version', 'pid', 'user', 'msg')) { - - force(fields) - - structure(function(level, msg, namespace = NA_character_, - .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - - fail_on_missing_package('jsonlite') +layout_json <- function(fields = c("time", "level", "ns", "ans", "topenv", "fn", "node", "arch", + "os_name", "os_release", "os_version", "pid", "user", "msg")) { + force(fields) - json <- get_logger_meta_variables( - log_level = level, namespace = namespace, - .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) + structure(function(level, msg, namespace = NA_character_, + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + fail_on_missing_package("jsonlite") - sapply(msg, function(msg) jsonlite::toJSON(c(json, list(msg = msg))[fields], auto_unbox = TRUE)) - - }, generator = deparse(match.call())) + json <- get_logger_meta_variables( + log_level = level, namespace = namespace, + .logcall = .logcall, .topcall = .topcall, .topenv = .topenv + ) + sapply(msg, function(msg) jsonlite::toJSON(c(json, list(msg = msg))[fields], auto_unbox = TRUE)) + }, generator = deparse(match.call())) } -#' Generate log layout function rendering JSON after merging meta fields with parsed list from JSON message -#' @param fields character vector of field names to be included in the JSON +#' Generate log layout function rendering JSON after merging meta +#' fields with parsed list from JSON message +#' @param fields character vector of field names to be included in the +#' JSON #' @export +#' @note This functionality depends on the \pkg{jsonlite} package. +#' @seealso This is a [log_layout()] potentially to be used with +#' [formatter_json()], for alternatives, see [layout_simple()], +#' [layout_glue()], [layout_glue_colors()], [layout_json()] or +#' generator functions such as [layout_glue_generator()] #' @examples \dontrun{ #' log_formatter(formatter_json) #' log_info(everything = 42) #' log_layout(layout_json_parser()) #' log_info(everything = 42) -#' log_layout(layout_json_parser(fields = c('time', 'node'))) +#' log_layout(layout_json_parser(fields = c("time", "node"))) #' log_info(cars = row.names(mtcars), species = unique(iris$Species)) #' } -#' @note This functionality depends on the \pkg{jsonlite} package. -#' @seealso This is a [log_layout()] potentially to be used with [formatter_json()], for alternatives, see [layout_simple()], [layout_glue()], [layout_glue_colors()], [layout_json()] or generator functions such as [layout_glue_generator()] -layout_json_parser <- function(fields = c('time', 'level', 'ns', 'ans', 'topenv', 'fn', 'node', 'arch', 'os_name', 'os_release', 'os_version', 'pid', 'user')) { - - force(fields) +layout_json_parser <- function(fields = c("time", "level", "ns", "ans", "topenv", "fn", "node", "arch", + "os_name", "os_release", "os_version", "pid", "user")) { + force(fields) - structure(function(level, msg, namespace = NA_character_, - .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + structure(function(level, msg, namespace = NA_character_, + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + fail_on_missing_package("jsonlite") - fail_on_missing_package('jsonlite') - - meta <- get_logger_meta_variables( - log_level = level, namespace = namespace, - .logcall = .logcall, .topcall = .topcall, .topenv = .topenv)[fields] - - msg <- jsonlite::fromJSON(msg) - - jsonlite::toJSON(c(meta, msg), auto_unbox = TRUE, null = 'null') + meta <- get_logger_meta_variables( + log_level = level, namespace = namespace, + .logcall = .logcall, .topcall = .topcall, .topenv = .topenv + )[fields] - }, generator = deparse(match.call())) + msg <- jsonlite::fromJSON(msg) + jsonlite::toJSON(c(meta, msg), auto_unbox = TRUE, null = "null") + }, generator = deparse(match.call())) } -#nocov start +# nocov start #' Format a log record for syslognet #' #' Format a log record for syslognet. @@ -275,18 +306,18 @@ layout_json_parser <- function(fields = c('time', 'level', 'ns', 'ans', 'topenv' layout_syslognet <- structure( function(level, msg, namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - ret <- paste(attr(level, 'level'), msg) - attr(ret, 'severity') <- switch( - attr(level, 'level', exact = TRUE), - 'FATAL' = 'CRITICAL', - 'ERROR' = 'ERR', - 'WARN' = 'WARNING', - 'SUCCESS' = 'NOTICE', - 'INFO' = 'INFO', - 'DEBUG' = 'DEBUG', - 'TRACE' = 'DEBUG') + ret <- paste(attr(level, "level"), msg) + attr(ret, "severity") <- switch(attr(level, "level", exact = TRUE), + "FATAL" = "CRITICAL", + "ERROR" = "ERR", + "WARN" = "WARNING", + "SUCCESS" = "NOTICE", + "INFO" = "INFO", + "DEBUG" = "DEBUG", + "TRACE" = "DEBUG" + ) return(ret) }, generator = quote(layout_syslognet()) ) -#nocov end +# nocov end diff --git a/R/levels.R b/R/levels.R index 792e7044..93db36f0 100644 --- a/R/levels.R +++ b/R/levels.R @@ -1,11 +1,13 @@ -log_levels_supported <- c('OFF', 'FATAL', 'ERROR', 'WARN', 'SUCCESS', 'INFO', 'DEBUG', 'TRACE') +log_levels_supported <- c("OFF", "FATAL", "ERROR", "WARN", "SUCCESS", "INFO", "DEBUG", "TRACE") #' Log levels #' -#' The standard Apache logj4 log levels plus a custom level for `SUCCESS`. For the full list of these log levels and suggested usage, check the below Details. +#' The standard Apache logj4 log levels plus a custom level for +#' `SUCCESS`. For the full list of these log levels and suggested +#' usage, check the below Details. #' #' List of supported log levels: -#' +#' #' * `OFF` No events will be logged #' * `FATAL` Severe error that will prevent the application from continuing #' * `ERROR` An error in the application, possibly recoverable @@ -14,46 +16,47 @@ log_levels_supported <- c('OFF', 'FATAL', 'ERROR', 'WARN', 'SUCCESS', 'INFO', 'D #' * `INFO` An event for informational purposes #' * `DEBUG` A general debugging event #' * `TRACE` A fine-grained debug message, typically capturing the flow through the application. -#' @references , +#' @references , +#' #' @name log_levels NULL #' @rdname log_levels #' @export #' @format NULL -OFF <- structure(0L, level = 'OFF', class = c('loglevel', 'integer')) +OFF <- structure(0L, level = "OFF", class = c("loglevel", "integer")) #' @export #' @rdname log_levels #' @format NULL -FATAL <- structure(100L, level = 'FATAL', class = c('loglevel', 'integer')) +FATAL <- structure(100L, level = "FATAL", class = c("loglevel", "integer")) #' @export #' @rdname log_levels #' @format NULL -ERROR <- structure(200L, level = 'ERROR', class = c('loglevel', 'integer')) +ERROR <- structure(200L, level = "ERROR", class = c("loglevel", "integer")) #' @export #' @rdname log_levels #' @format NULL -WARN <- structure(300L, level = 'WARN', class = c('loglevel', 'integer')) +WARN <- structure(300L, level = "WARN", class = c("loglevel", "integer")) #' @export #' @rdname log_levels #' @format NULL -SUCCESS <- structure(350L, level = 'SUCCESS', class = c('loglevel', 'integer')) +SUCCESS <- structure(350L, level = "SUCCESS", class = c("loglevel", "integer")) #' @export #' @rdname log_levels #' @format NULL -INFO <- structure(400L, level = 'INFO', class = c('loglevel', 'integer')) +INFO <- structure(400L, level = "INFO", class = c("loglevel", "integer")) #' @export #' @rdname log_levels #' @format NULL -DEBUG <- structure(500L, level = 'DEBUG', class = c('loglevel', 'integer')) +DEBUG <- structure(500L, level = "DEBUG", class = c("loglevel", "integer")) #' @export #' @rdname log_levels #' @format NULL -TRACE <- structure(600L, level = 'TRACE', class = c('loglevel', 'integer')) +TRACE <- structure(600L, level = "TRACE", class = c("loglevel", "integer")) #' @export print.loglevel <- function(x, ...) { - cat('Log level: ', attr(x, 'level'), '\n', sep = '') + cat("Log level: ", attr(x, "level"), "\n", sep = "") } @@ -65,39 +68,39 @@ print.loglevel <- function(x, ...) { #' as.loglevel(INFO) #' as.loglevel(400L) #' as.loglevel(400) -as.loglevel <- function(x) { - UseMethod('as.loglevel', x) +as.loglevel <- function(x) { # nolint + UseMethod("as.loglevel", x) } #' @export as.loglevel.default <- function(x) { - stop(paste( - 'Do not know how to convert', - shQuote(class(x)[1]), - 'to a logger log-level.' - )) + stop(paste( + "Do not know how to convert", + shQuote(class(x)[1]), + "to a logger log-level." + )) } #' @export as.loglevel.character <- function(x) { - stopifnot( - length(x) == 1, - x %in% log_levels_supported - ) - getFromNamespace(x, 'logger') + stopifnot( + length(x) == 1, + x %in% log_levels_supported + ) + getFromNamespace(x, "logger") } #' @export as.loglevel.integer <- function(x) { - loglevels <- mget(log_levels_supported, envir = asNamespace('logger')) - stopifnot( - length(x) == 1, - x %in% as.integer(loglevels) - ) - loglevels[[which(loglevels == x)]] + loglevels <- mget(log_levels_supported, envir = asNamespace("logger")) + stopifnot( + length(x) == 1, + x %in% as.integer(loglevels) + ) + loglevels[[which(loglevels == x)]] } diff --git a/R/logger.R b/R/logger.R index 0cac3c42..894494cf 100644 --- a/R/logger.R +++ b/R/logger.R @@ -1,71 +1,94 @@ #' Generate logging utility #' -#' A logger consists of a log level `threshold`, a log message `formatter` function, a log record `layout` formatting function and the `appender` function deciding on the destination of the log record. For more details, see the package `README.md`. +#' A logger consists of a log level `threshold`, a log message +#' `formatter` function, a log record `layout` formatting function and +#' the `appender` function deciding on the destination of the log +#' record. For more details, see the package `README.md`. #' #' By default, a general logger definition is created when loading the `logger` package, that uses #' #' * [INFO()] (or as per the `LOGGER_LOG_LEVEL` environment variable override) as the log level threshold #' * [layout_simple()] as the layout function showing the log level, timestamp and log message -#' * [formatter_glue()] (or [formatter_sprintf()] if \pkg{glue} is not installed) as the default formatter function transforming the R objects to be logged to a character vector +#' * [formatter_glue()] (or [formatter_sprintf()] if \pkg{glue} is not installed) as the +#' default formatter function transforming the R objects to be logged to a character vector #' * [appender_console()] as the default log record destination +#' #' @param threshold omit log messages below this [log_levels()] -#' @param formatter function pre-processing the message of the log record when it's not wrapped in a [skip_formatter()] call -#' @param layout function rendering the layout of the actual log record +#' @param formatter function pre-processing the message of the log +#' record when it's not wrapped in a [skip_formatter()] call +#' @param layout function rendering the layout of the actual log +#' record #' @param appender function writing the log record -#' @return A function taking the log `level` to compare with the set threshold, all the `...` arguments passed to the formatter function, besides the standard `namespace`, `.logcall`, `.topcall` and `.topenv` arguments (see [log_level()] for more details). The function invisibly returns a list including the original `level`, `namespace`, all `...` transformed to a list as `params`, the log `message` (after calling the `formatter` function) and the log `record` (after calling the `layout` function), and a list of `handlers` with the `formatter`, `layout` and `appender` functions. +#' @return A function taking the log `level` to compare with the set +#' threshold, all the `...` arguments passed to the formatter +#' function, besides the standard `namespace`, `.logcall`, +#' `.topcall` and `.topenv` arguments (see [log_level()] for more +#' details). The function invisibly returns a list including the +#' original `level`, `namespace`, all `...` transformed to a list as +#' `params`, the log `message` (after calling the `formatter` +#' function) and the log `record` (after calling the `layout` +#' function), and a list of `handlers` with the `formatter`, +#' `layout` and `appender` functions. #' @export -#' @references For more details, see the Anatomy of a Log Request vignette at . -#' @note It's quite unlikely that you need to call this function directly, but instead set the logger parameters and functions at [log_threshold()], [log_formatter()], [log_layout()] and [log_appender()] and then call [log_levels()] and its derivatives, such as [log_info()] directly. +#' @references For more details, see the Anatomy of a Log Request +#' vignette at +#' . +#' @note It's quite unlikely that you need to call this function +#' directly, but instead set the logger parameters and functions at +#' [log_threshold()], [log_formatter()], [log_layout()] and +#' [log_appender()] and then call [log_levels()] and its +#' derivatives, such as [log_info()] directly. #' @examples \dontrun{ #' do.call(logger, logger:::namespaces$global[[1]])(INFO, 42) -#' do.call(logger, logger:::namespaces$global[[1]])(INFO, '{pi}') +#' do.call(logger, logger:::namespaces$global[[1]])(INFO, "{pi}") #' x <- 42 -#' do.call(logger, logger:::namespaces$global[[1]])(INFO, '{x}^2 = {x^2}') +#' do.call(logger, logger:::namespaces$global[[1]])(INFO, "{x}^2 = {x^2}") #' } logger <- function(threshold, formatter, layout, appender) { + force(threshold) + threshold <- validate_log_level(threshold) + force(layout) + force(appender) + + function(level, ..., namespace = NA_character_, + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + res <- list( + level = level, + namespace = namespace, + params = list(...), + handlers = list( + formatter = formatter, + layout = layout, + appender = appender + ), + message = NULL, + record = NULL + ) + + if (level > threshold) { + return(invisible(res)) + } - force(threshold) - threshold <- validate_log_level(threshold) - force(layout) - force(appender) - - function(level, ..., namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - - res <- list( - level = level, - namespace = namespace, - params = list(...), - handlers = list( - formatter = formatter, - layout = layout, - appender = appender - ), - message = NULL, - record = NULL - ) - - if (level > threshold) { - return(invisible(res)) - } - - ## workaround to be able to avoid any formatter function, eg when passing in a string - if (length(res$params) == 1 && isTRUE(attr(res$params[[1]], 'skip_formatter', exact = TRUE))) { - res$message <- res$params[[1]] - } else { - res$message <- do.call(formatter, c(res$params, list( - .logcall = substitute(.logcall), - .topcall = substitute(.topcall), - .topenv = .topenv))) - } - - res$record <- layout( - level, res$message, namespace = namespace, - .logcall = substitute(.logcall), .topcall = substitute(.topcall), .topenv = .topenv) - - appender(res$record) - invisible(res) - + ## workaround to be able to avoid any formatter function, eg when passing in a string + if (length(res$params) == 1 && isTRUE(attr(res$params[[1]], "skip_formatter", exact = TRUE))) { + res$message <- res$params[[1]] + } else { + res$message <- do.call(formatter, c(res$params, list( + .logcall = substitute(.logcall), + .topcall = substitute(.topcall), + .topenv = .topenv + ))) } + + res$record <- layout( + level, res$message, + namespace = namespace, + .logcall = substitute(.logcall), .topcall = substitute(.topcall), .topenv = .topenv + ) + + appender(res$record) + invisible(res) + } } @@ -74,65 +97,64 @@ logger <- function(threshold, formatter, layout, appender) { #' @return string #' @keywords internal fallback_namespace <- function(namespace) { - if (!exists(namespace, envir = namespaces, inherits = FALSE)) { - namespace <- 'global' - } - namespace + if (!exists(namespace, envir = namespaces, inherits = FALSE)) { + namespace <- "global" + } + namespace } -#' Base Logging Function -#' @param fun_name string a full name of log function -#' @param arg see [log_levels()] #' @param namespace logger namespace #' @param index index of the logger within the namespace -#' @return currently set or return log function property -#' @keywords internal -log_config_setter <- function(fun_name, arg, namespace, index) { - - if (length(namespace) > 1) { - for (ns in namespace) { - log_config_setter(fun_name, arg, ns, index) - } - return(invisible()) +#' @return If `value` is `NULL`, will return the currently set value. +#' If `value` is not `NULL`, will return the previously set value. +#' @noRd +log_config_setter <- function(name, value, namespace = "global", index = 1) { + + if (length(namespace) > 1) { + for (ns in namespace) { + log_config_setter(name, value, ns, index) } + return(invisible()) + } - fun_name_base <- strsplit(fun_name, '_')[[1]][2] + configs <- get(fallback_namespace(namespace), envir = namespaces) + config <- configs[[min(index, length(configs))]] + old <- config[[name]] - configs <- get(fallback_namespace(namespace), envir = namespaces) - config <- configs[[min(index, length(configs))]] - - if (fun_name_base == 'threshold') { - if (is.null(arg)) { - return(config[[fun_name_base]]) - } - config[[fun_name_base]] <- validate_log_level(arg) - } else { - if (is.null(arg)) { - res <- config[[fun_name_base]] - if (!is.null(attr(res, 'generator'))) { - res <- parse(text = attr(res, 'generator'))[[1]] - } - return(res) + if (name == "threshold") { + if (is.null(value)) { + return(config[[name]]) + } + config[[name]] <- validate_log_level(value) + } else { + if (is.null(value)) { + res <- config[[name]] + if (!is.null(attr(res, "generator"))) { + res <- parse(text = attr(res, "generator"))[[1]] } - - config[[fun_name_base]] <- arg + return(res) } - configs[[min(index, length(config) + 1)]] <- config - assign(namespace, configs, envir = namespaces) + config[[name]] <- value + } + + configs[[min(index, length(config) + 1)]] <- config + assign(namespace, configs, envir = namespaces) + invisible(old) + } #' Delete an index from a logger namespace #' @inheritParams log_threshold #' @export -delete_logger_index <- function(namespace = 'global', index) { - configs <- get(fallback_namespace(namespace), envir = namespaces) - if (index > length(configs)) { - stop(sprintf('%s namespace has only %i indexes', namespace, length(configs))) - } - configs[index] <- NULL - assign(namespace, configs, envir = namespaces) +delete_logger_index <- function(namespace = "global", index) { + configs <- get(fallback_namespace(namespace), envir = namespaces) + if (index > length(configs)) { + stop(sprintf("%s namespace has only %i indexes", namespace, length(configs))) + } + configs[index] <- NULL + assign(namespace, configs, envir = namespaces) } @@ -157,16 +179,19 @@ delete_logger_index <- function(namespace = 'global', index) { #' log_warn(2) #' #' ## set the log level threshold in all namespaces to ERROR -#' log_threshold(ERROR, namespace = log_namespaces()) +#' log_threshold(ERROR, namespace = log_namespaces()) #' } #' @seealso [logger()], [log_layout()], [log_formatter()], [log_appender()] -log_threshold <- function(level = NULL, namespace = 'global', index = 1) { - log_config_setter(fun_name = 'log_threshold', arg = level, namespace = namespace, index = index) +log_threshold <- function(level = NULL, namespace = "global", index = 1) { + log_config_setter("threshold", level, namespace = namespace, index = index) } #' Get or set log record layout -#' @param layout function defining the structure of a log record, eg [layout_simple()], [layout_glue()] or [layout_glue_colors()], [layout_json()], or generator functions such as [layout_glue_generator()], default NULL +#' @param layout function defining the structure of a log record, eg +#' [layout_simple()], [layout_glue()] or [layout_glue_colors()], +#' [layout_json()], or generator functions such as +#' [layout_glue_generator()], default NULL #' @inheritParams log_threshold #' @export #' @examples \dontrun{ @@ -174,23 +199,35 @@ log_threshold <- function(level = NULL, namespace = 'global', index = 1) { #' log_info(42) #' } #' @seealso [logger()], [log_threshold()], [log_appender()] and [log_formatter()] -log_layout <- function(layout = NULL, namespace = 'global', index = 1) { - log_config_setter(fun_name = 'log_layout', arg = layout, namespace = namespace, index = index) +log_layout <- function(layout = NULL, namespace = "global", index = 1) { + if (!is.null(layout) && !is.function(layout)) { + stop("`layout` must be a function") + } + log_config_setter("layout", layout, namespace = namespace, index = index) } #' Get or set log message formatter -#' @param formatter function defining how R objects are converted into a single string, eg [formatter_paste()], [formatter_sprintf()], [formatter_glue()], [formatter_glue_or_sprintf()], [formatter_logging()], default NULL +#' @param formatter function defining how R objects are converted into +#' a single string, eg [formatter_paste()], [formatter_sprintf()], +#' [formatter_glue()], [formatter_glue_or_sprintf()], +#' [formatter_logging()], default NULL #' @inheritParams log_threshold #' @export -#' @seealso [logger()], [log_threshold()], [log_appender()] and [log_layout()] -log_formatter <- function(formatter = NULL, namespace = 'global', index = 1) { - log_config_setter(fun_name = 'log_formatter', arg = formatter, namespace = namespace, index = index) +#' @seealso [logger()], [log_threshold()], [log_appender()] and +#' [log_layout()] +log_formatter <- function(formatter = NULL, namespace = "global", index = 1) { + if (!is.null(formatter) && !is.function(formatter)) { + stop("`formatter` must be a function") + } + log_config_setter("formatter", formatter, namespace = namespace, index = index) } #' Get or set log record appender function -#' @param appender function delivering a log record to the destination, eg [appender_console()], [appender_file()] or [appender_tee()], default NULL +#' @param appender function delivering a log record to the +#' destination, eg [appender_console()], [appender_file()] or +#' [appender_tee()], default NULL #' @inheritParams log_threshold #' @export #' @examples \dontrun{ @@ -209,22 +246,27 @@ log_formatter <- function(formatter = NULL, namespace = 'global', index = 1) { #' readLines(t) #' } #' @seealso [logger()], [log_threshold()], [log_layout()] and [log_formatter()] -log_appender <- function(appender = NULL, namespace = 'global', index = 1) { - log_config_setter(fun_name = 'log_appender', arg = appender, namespace = namespace, index = index) +log_appender <- function(appender = NULL, namespace = "global", index = 1) { + if (!is.null(appender) && !is.function(appender)) { + stop("`appender` must be a function") + } + log_config_setter("appender", appender, namespace = namespace, index = index) } -#' Find the logger definition(s) specified for the current namespace with a fallback to the global namespace +#' Find the logger definition(s) specified for the current namespace +#' with a fallback to the global namespace #' @return list of function(s) #' @keywords internal #' @importFrom utils getFromNamespace -#' @param namespace override the default / auto-picked namespace with a custom string +#' @param namespace override the default / auto-picked namespace with +#' a custom string get_logger_definitions <- function(namespace = NA_character_, .topenv = parent.frame()) { - namespace <- ifelse(is.na(namespace), top_env_name(.topenv), namespace) - if (!exists(namespace, envir = namespaces, inherits = FALSE)) { - namespace <- 'global' - } - get(namespace, envir = getFromNamespace('namespaces', 'logger')) + namespace <- ifelse(is.na(namespace), top_env_name(.topenv), namespace) + if (!exists(namespace, envir = namespaces, inherits = FALSE)) { + namespace <- "global" + } + get(namespace, envir = getFromNamespace("namespaces", "logger")) } @@ -232,80 +274,87 @@ get_logger_definitions <- function(namespace = NA_character_, .topenv = parent.f #' @return character vector of namespace names #' @export log_namespaces <- function() { - ls(envir = namespaces) + ls(envir = namespaces) } #' Log a message with given log level #' @param level log level, see [log_levels()] for more details -#' @param ... R objects that can be converted to a character vector via the active message formatter function -#' @param namespace string referring to the `logger` environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace. -#' @param .logcall the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression) -#' @param .topcall R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments) -#' @param .topenv original frame of the `.topcall` calling function where the formatter function will be evaluated and that is used to look up the `namespace` as well via `logger:::top_env_name` +#' @param ... R objects that can be converted to a character vector +#' via the active message formatter function +#' @param namespace string referring to the `logger` environment / +#' config to be used to override the target of the message record to +#' be used instead of the default namespace, which is defined by the +#' R package name from which the logger was called, and falls back +#' to a common, global namespace. +#' @param .logcall the logging call being evaluated (useful in +#' formatters and layouts when you want to have access to the raw, +#' unevaluated R expression) +#' @param .topcall R expression from which the logging function was +#' called (useful in formatters and layouts to extract the calling +#' function's name or arguments) +#' @param .topenv original frame of the `.topcall` calling function +#' where the formatter function will be evaluated and that is used +#' to look up the `namespace` as well via `logger:::top_env_name` #' @seealso [logger()] #' @export #' @examples \dontrun{ -#' log_level(INFO, 'hi there') -#' log_info('hi there') +#' log_level(INFO, "hi there") +#' log_info("hi there") #' #' ## output omitted -#' log_debug('hi there') +#' log_debug("hi there") #' #' ## lower threshold and retry #' log_threshold(TRACE) -#' log_debug('hi there') +#' log_debug("hi there") #' #' ## multiple lines -#' log_info('ok {1:3} + {1:3} = {2*(1:3)}') +#' log_info("ok {1:3} + {1:3} = {2*(1:3)}") #' #' log_layout(layout_json()) -#' log_info('ok {1:3} + {1:3} = {2*(1:3)}') +#' log_info("ok {1:3} + {1:3} = {2*(1:3)}") #' #' ## note for the JSON output, glue is not automatically applied -#' log_info(glue::glue('ok {1:3} + {1:3} = {2*(1:3)}')) +#' log_info(glue::glue("ok {1:3} + {1:3} = {2*(1:3)}")) #' } #' @return Invisible `list` of `logger` objects. See [logger()] for more details on the format/ log_level <- function(level, ..., namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - - ## guess namespace - if (is.na(namespace)) { - topenv <- top_env_name(.topenv) - namespace <- ifelse(topenv == 'R_GlobalEnv', 'global', topenv) - } - - definitions <- get_logger_definitions(namespace, .topenv = .topenv) - level <- validate_log_level(level) - - ## super early return (even before evaluating passed parameters) - if (length(definitions) == 1 && level > definitions[[1]]$threshold) { - return(invisible(NULL)) + ## guess namespace + if (is.na(namespace)) { + topenv <- top_env_name(.topenv) + namespace <- ifelse(topenv == "R_GlobalEnv", "global", topenv) + } + + definitions <- get_logger_definitions(namespace, .topenv = .topenv) + level <- validate_log_level(level) + + ## super early return (even before evaluating passed parameters) + if (length(definitions) == 1 && level > definitions[[1]]$threshold) { + return(invisible(NULL)) + } + + log_arg <- list(...) + log_arg$level <- level + log_arg$.logcall <- .logcall + log_arg$.topcall <- if (!is.null(.topcall)) { + .topcall + } else { + ## cannot pass NULL + NA + } + log_arg$.topenv <- .topenv + log_arg$namespace <- namespace + + invisible(lapply(definitions, function(definition) { + if (level > definition$threshold) { + return(NULL) } - log_arg <- list(...) - log_arg$level <- level - log_arg$.logcall <- .logcall - log_arg$.topcall <- if(!is.null(.topcall)) { - .topcall - } else { - ## cannot pass NULL - NA - } - log_arg$.topenv <- .topenv - log_arg$namespace <- namespace - - invisible(lapply(definitions, function(definition) { - - if (level > definition$threshold) { - return(NULL) - } - - log_fun <- do.call(logger, definition) - structure(do.call(log_fun, log_arg), class = 'logger') - - })) - + log_fun <- do.call(logger, definition) + structure(do.call(log_fun, log_arg), class = "logger") + })) } @@ -314,13 +363,13 @@ log_level <- function(level, ..., namespace = NA_character_, #' @return [log_levels()] object #' @keywords internal validate_log_level <- function(level) { - if (inherits(level, 'loglevel')) { - return(level) - } - if (is.character(level) & level %in% log_levels_supported) { - return(get(level)) - } - stop('Invalid log level') + if (inherits(level, "loglevel")) { + return(level) + } + if (is.character(level) && level %in% log_levels_supported) { + return(get(level)) + } + stop("Invalid log level") } @@ -328,43 +377,43 @@ validate_log_level <- function(level) { #' @rdname log_level log_fatal <- function(..., namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - log_level(FATAL, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) + log_level(FATAL, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) } #' @export #' @rdname log_level log_error <- function(..., namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - log_level(ERROR, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) + log_level(ERROR, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) } #' @export #' @rdname log_level log_warn <- function(..., namespace = NA_character_, - .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - log_level(WARN, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + log_level(WARN, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) } #' @export #' @rdname log_level log_success <- function(..., namespace = NA_character_, - .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - log_level(SUCCESS, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + log_level(SUCCESS, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) } #' @export #' @rdname log_level log_info <- function(..., namespace = NA_character_, - .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - log_level(INFO, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) + .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { + log_level(INFO, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) } #' @export #' @rdname log_level log_debug <- function(..., namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - log_level(DEBUG, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) + log_level(DEBUG, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) } #' @export #' @rdname log_level log_trace <- function(..., namespace = NA_character_, .logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) { - log_level(TRACE, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) + log_level(TRACE, ..., namespace = namespace, .logcall = .logcall, .topcall = .topcall, .topenv = .topenv) } @@ -375,22 +424,22 @@ log_trace <- function(..., namespace = NA_character_, #' @export #' @examples \dontrun{ #' log_threshold(TRACE) -#' log_trace('Logging everything!') -#' x <- with_log_threshold({ -#' log_info('Now we are temporarily suppressing eg INFO messages') -#' log_warn('WARN') -#' log_debug('Debug messages are suppressed as well') -#' log_error('ERROR') -#' invisible(42) -#' }, threshold = WARN) +#' log_trace("Logging everything!") +#' x <- with_log_threshold( +#' { +#' log_info("Now we are temporarily suppressing eg INFO messages") +#' log_warn("WARN") +#' log_debug("Debug messages are suppressed as well") +#' log_error("ERROR") +#' invisible(42) +#' }, +#' threshold = WARN +#' ) #' x -#' log_trace('DONE') +#' log_trace("DONE") #' } -with_log_threshold <- function(expression, threshold = ERROR, namespace = 'global', index = 1) { - old <- log_threshold(namespace = namespace, index = index) - on.exit({ - log_threshold(old, namespace = namespace, index = index) - }) - log_threshold(threshold, namespace = namespace, index = index) - eval(quote(expression)) +with_log_threshold <- function(expression, threshold = ERROR, namespace = "global", index = 1) { + old <- log_threshold(threshold, namespace = namespace, index = index) + on.exit(log_threshold(old, namespace = namespace, index = index)) + expression } diff --git a/R/try.R b/R/try.R index cb372eea..527e51ed 100644 --- a/R/try.R +++ b/R/try.R @@ -1,11 +1,13 @@ -#' Try to evaluate an expressions and evaluate another expression on exception +#' Try to evaluate an expressions and evaluate another expression on +#' exception #' @param try R expression #' @param except fallback R expression to be evaluated if `try` fails #' @export -#' @note Suppress log messages in the `except` namespace if you don't want to throw a `WARN` log message on the exception branch. +#' @note Suppress log messages in the `except` namespace if you don't +#' want to throw a `WARN` log message on the exception branch. #' @examples #' everything %except% 42 -#' everything <- '640kb' +#' everything <- "640kb" #' everything %except% 42 #' #' FunDoesNotExist(1:10) %except% sum(1:10) / length(1:10) @@ -14,23 +16,23 @@ #' FunDoesNotExist(1:10) %except% (MEAN(1:10) %except% mean(1:10)) `%except%` <- function(try, except) { - call <- sys.call(-1) - env <- parent.frame() - try <- substitute(try) - fallback <- substitute(except) + ## Need to capture these in the evaluation frame of `%except%` but only want + ## to do the work if there's an error + delayedAssign("call", sys.call(-1)) + delayedAssign("env", parent.frame()) + delayedAssign("except_text", deparse(substitute(except))) + delayedAssign("try_text", deparse(substitute(try))) - tryCatch( - eval(try, envir = env), - error = function(e) { - log_level( - WARN, - paste( - 'Running', shQuote(deparse(fallback)), 'as', - shQuote(deparse(try)), 'failed:', - shQuote(e$message)), - namespace = 'except', - .topcall = call, .topenv = env) - eval(fallback, envir = env) - }) + tryCatch(try, + error = function(e) { + log_level( + WARN, + paste0("Running '", except_text, "' as '", try_text, "' failed: '", e$message, "'"), + namespace = "except", + .topcall = call, + .topenv = env + ) + except + }) } diff --git a/R/utils.R b/R/utils.R index 62dc9292..8f6944ef 100644 --- a/R/utils.R +++ b/R/utils.R @@ -4,30 +4,36 @@ #' @export #' @importFrom utils packageVersion compareVersion #' @examples \dontrun{ -#' f <- function() fail_on_missing_package('foobar') +#' f <- function() fail_on_missing_package("foobar") #' f() -#' g <- function() fail_on_missing_package('stats') +#' g <- function() fail_on_missing_package("stats") #' g() #' } fail_on_missing_package <- function(pkg, min_version) { - pc <- sys.call(which = 1) - if (!requireNamespace(pkg, quietly = TRUE)) { - stop(sprintf( - 'Please install the %s package to use %s', - shQuote(pkg), - deparse(pc[[1]])), - call. = FALSE) - } - if (!missing(min_version)) { - if (compareVersion(min_version, as.character(packageVersion(pkg))) == 1) { - stop(sprintf( - 'Please install min. %s version of %s to use %s', - min_version, - pkg, - deparse(pc[[1]])), - call. = FALSE) - } + pc <- sys.call(which = 1) + if (!requireNamespace(pkg, quietly = TRUE)) { + stop( + sprintf( + "Please install the %s package to use %s", + shQuote(pkg), + deparse(pc[[1]]) + ), + call. = FALSE + ) + } + if (!missing(min_version)) { + if (compareVersion(min_version, as.character(packageVersion(pkg))) == 1) { + stop( + sprintf( + "Please install min. %s version of %s to use %s", + min_version, + pkg, + deparse(pc[[1]]) + ), + call. = FALSE + ) } + } } @@ -36,7 +42,7 @@ fail_on_missing_package <- function(pkg, min_version) { #' @keywords internal #' @param .topenv call environment top_env_name <- function(.topenv = parent.frame()) { - environmentName(topenv(.topenv)) + environmentName(topenv(.topenv)) } @@ -50,9 +56,10 @@ top_env_name <- function(.topenv = parent.frame()) { #' @return string #' @export deparse_to_one_line <- function(x) { - gsub('\\s+(?=(?:[^\\\'"]*[\\\'"][^\\\'"]*[\\\'"])*[^\\\'"]*$)', ' ', - paste(deparse(x), collapse = ' '), - perl = TRUE) + gsub('\\s+(?=(?:[^\\\'"]*[\\\'"][^\\\'"]*[\\\'"])*[^\\\'"]*$)', " ", + paste(deparse(x), collapse = " "), + perl = TRUE + ) } @@ -61,38 +68,36 @@ deparse_to_one_line <- function(x) { #' @keywords internal #' @param level see [log_levels()] #' @param namespace string -#' @examples +#' @examples #' \dontrun{ #' catch_base_log(INFO, NA_character_) -#' logger <- layout_glue_generator(format = '{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}') +#' logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") #' log_layout(logger) #' catch_base_log(INFO, NA_character_) #' fun <- function() catch_base_log(INFO, NA_character_) #' fun() -#' catch_base_log(INFO, NA_character_, .topcall = call('funLONG')) +#' catch_base_log(INFO, NA_character_, .topcall = call("funLONG")) #' } -catch_base_log <- function( - level, - namespace, - .topcall = sys.call(-1), - .topenv = parent.frame() - ) { - namespace <- fallback_namespace(namespace) - orginal_appender <- log_appender(namespace = namespace) - log_appender(appender_console, namespace = namespace) - # catch error, warning or message - res <- capture.output( - log_level(level = level, - "", - namespace = namespace, - .topcall = .topcall, - .topenv = .topenv), - type = 'message' - ) - log_appender(orginal_appender, namespace = namespace) - res +catch_base_log <- function(level, namespace, .topcall = sys.call(-1), .topenv = parent.frame()) { + namespace <- fallback_namespace(namespace) + + old <- log_appender(appender_console, namespace = namespace) + on.exit(log_appender(old, namespace = namespace)) + + # catch error, warning or message + capture.output( + log_level( + level = level, + "", + namespace = namespace, + .topcall = .topcall, + .topenv = .topenv + ), + type = "message" + ) + } in_pkgdown <- function() { - identical(Sys.getenv("IN_PKGDOWN"), "true") + identical(Sys.getenv("IN_PKGDOWN"), "true") } diff --git a/R/zzz.R b/R/zzz.R index 7d108136..5bfd3732 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,36 +1,47 @@ ## init storage for all logger settings -namespaces <- new.env() +namespaces <- new.env(parent = emptyenv()) .onLoad <- function(libname, pkgname) { + namespaces_reset() +} - ## default namespace's logger settings - namespaces$global <- list( - ## there can be multiple loggers for a namespace - default = list( - threshold = as.loglevel(Sys.getenv('LOGGER_LOG_LEVEL', unset = 'INFO')), - layout = layout_simple, - formatter = formatter_sprintf, - appender = if (in_pkgdown()) appender_stdout else appender_console)) - - if (requireNamespace('glue', quietly = TRUE)) { - log_formatter(formatter_glue, namespace = 'global', index = 1) - } - - ## internal namespace for debugging logger - namespaces$.logger <- list( - default = list( - threshold = ERROR, - layout = layout_simple, - formatter = formatter_sprintf, - appender = appender_console)) - +namespaces_reset <- function() { + rm(list = ls(namespaces), envir = namespaces) + + ## default namespace's logger settings + namespaces$global <- list( + ## there can be multiple loggers for a namespace + default = list( + threshold = as.loglevel(Sys.getenv("LOGGER_LOG_LEVEL", unset = "INFO")), + layout = layout_simple, + formatter = formatter_sprintf, + appender = if (in_pkgdown()) appender_stdout else appender_console + ) + ) + + if (requireNamespace("glue", quietly = TRUE)) { + log_formatter(formatter_glue, namespace = "global", index = 1) + } + + ## internal namespace for debugging logger + namespaces$.logger <- list( + default = list( + threshold = ERROR, + layout = layout_simple, + formatter = formatter_sprintf, + appender = appender_console + ) + ) } .onAttach <- function(libname, pkgname) { - - ## warn user about using sprintf instead of glue due to missing dependency - if (!requireNamespace('glue', quietly = TRUE)) { - packageStartupMessage('logger: As the "glue" R package is not installed, using "sprintf" as the default log message formatter instead of "glue".') - } - + ## warn user about using sprintf instead of glue due to missing dependency + if (!requireNamespace("glue", quietly = TRUE)) { + packageStartupMessage( + paste( + 'logger: As the "glue" R package is not installed,', + 'using "sprintf" as the default log message formatter instead of "glue".' + ) + ) + } } diff --git a/README.Rmd b/README.Rmd index 99880ea7..d58d42dd 100644 --- a/README.Rmd +++ b/README.Rmd @@ -27,7 +27,7 @@ A lightweight, modern and flexibly logging utility for R -- heavily inspired by ```{r} #| eval: false -install.packages('logger') +install.packages("logger") ``` The most recent, development version of `logger` can also be installed from GitHub: @@ -35,7 +35,7 @@ The most recent, development version of `logger` can also be installed from GitH ```{r} #| eval: false # install.packages("pak") -pak::pak('daroczig/logger') +pak::pak("daroczig/logger") ``` ## Quick example @@ -51,20 +51,20 @@ log_appender(appender_stdout) ```{r} library(logger) log_threshold(DEBUG) -log_info('Script starting up...') +log_info("Script starting up...") pkgs <- available.packages() -log_info('There are {nrow(pkgs)} R packages hosted on CRAN!') +log_info("There are {nrow(pkgs)} R packages hosted on CRAN!") for (letter in letters) { - lpkgs <- sum(grepl(letter, pkgs[, 'Package'], ignore.case = TRUE)) - log_level( + lpkgs <- sum(grepl(letter, pkgs[, "Package"], ignore.case = TRUE)) + log_level( if (lpkgs < 5000) TRACE else DEBUG, - '{lpkgs} R packages including the {shQuote(letter)} letter' + "{lpkgs} R packages including the {shQuote(letter)} letter" ) } -log_warn('There might be many, like {1:2} or more warnings!!!') +log_warn("There might be many, like {1:2} or more warnings!!!") ``` You can even use a custom log layout to render the log records with colors, as you can see in `layout_glue_colors()`: diff --git a/README.md b/README.md index 633663da..3c8fa1d7 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ inspired by the `futile.logger` R package and `logging` Python module. version](https://www.r-pkg.org/badges/version-ago/logger)](https://cran.r-project.org/package=logger) ``` r -install.packages('logger') +install.packages("logger") ``` The most recent, development version of `logger` can also be installed @@ -34,7 +34,7 @@ from GitHub: ``` r # install.packages("pak") -pak::pak('daroczig/logger') +pak::pak("daroczig/logger") ``` ## Quick example @@ -45,7 +45,7 @@ messages in ad-hoc and programmatic ways: ``` r library(logger) log_threshold(DEBUG) -log_info('Script starting up...') +log_info("Script starting up...") #> INFO [2024-08-05 16:05:22] Script starting up... pkgs <- available.packages() @@ -53,10 +53,10 @@ log_info('There are {nrow(pkgs)} R packages hosted on CRAN!') #> INFO [2024-08-05 16:05:23] There are 21132 R packages hosted on CRAN! for (letter in letters) { - lpkgs <- sum(grepl(letter, pkgs[, 'Package'], ignore.case = TRUE)) - log_level( + lpkgs <- sum(grepl(letter, pkgs[, "Package"], ignore.case = TRUE)) + log_level( if (lpkgs < 5000) TRACE else DEBUG, - '{lpkgs} R packages including the {shQuote(letter)} letter' + "{lpkgs} R packages including the {shQuote(letter)} letter" ) } #> DEBUG [2024-08-05 16:05:23] 10194 R packages including the 'a' letter @@ -73,7 +73,7 @@ for (letter in letters) { #> DEBUG [2024-08-05 16:05:23] 10296 R packages including the 's' letter #> DEBUG [2024-08-05 16:05:23] 9531 R packages including the 't' letter -log_warn('There might be many, like {1:2} or more warnings!!!') +log_warn("There might be many, like {1:2} or more warnings!!!") #> WARN [2024-08-05 16:05:23] There might be many, like 1 or more warnings!!! #> WARN [2024-08-05 16:05:23] There might be many, like 2 or more warnings!!! ``` diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..04c55859 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +comment: false + +coverage: + status: + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: auto + threshold: 1% + informational: true diff --git a/inst/demo-packages/logger-tester-package/DESCRIPTION b/inst/demo-packages/logger-tester-package/DESCRIPTION index 6cbd94b5..866a9c22 100644 --- a/inst/demo-packages/logger-tester-package/DESCRIPTION +++ b/inst/demo-packages/logger-tester-package/DESCRIPTION @@ -9,7 +9,7 @@ Description: Dummy package providing testing functions for logger Version: 0.1 Date: 2018-07-04 URL: https://github.com/daroczig/logger -RoxygenNote: 6.1.0 +RoxygenNote: 7.3.2 License: AGPL-3 Imports: logger diff --git a/inst/demo-packages/logger-tester-package/NAMESPACE b/inst/demo-packages/logger-tester-package/NAMESPACE index 48220e0f..7d1a2284 100644 --- a/inst/demo-packages/logger-tester-package/NAMESPACE +++ b/inst/demo-packages/logger-tester-package/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +export(logger_info_tester_function) export(logger_tester_function) -importFrom(logger,log_level) importFrom(logger,log_info) +importFrom(logger,log_level) diff --git a/inst/demo-packages/logger-tester-package/R/tester.R b/inst/demo-packages/logger-tester-package/R/tester.R index ee361054..dc4c99ee 100644 --- a/inst/demo-packages/logger-tester-package/R/tester.R +++ b/inst/demo-packages/logger-tester-package/R/tester.R @@ -4,8 +4,9 @@ #' @export #' @importFrom logger log_level logger_tester_function <- function(level, msg) { - x <- runif(1) - log_level(level, '{msg} {x}') + set.seed(1014) + x <- runif(1) + log_level(level, "{msg} {x}") } #' Testing logging INFO from package @@ -13,6 +14,6 @@ logger_tester_function <- function(level, msg) { #' @export #' @importFrom logger log_info logger_info_tester_function <- function(msg) { - everything <- 42 - log_info('{msg} {everything}') + everything <- 42 + log_info("{msg} {everything}") } diff --git a/inst/demo-packages/logger-tester-package/man/logger_info_tester_function.Rd b/inst/demo-packages/logger-tester-package/man/logger_info_tester_function.Rd new file mode 100644 index 00000000..27d9d80f --- /dev/null +++ b/inst/demo-packages/logger-tester-package/man/logger_info_tester_function.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tester.R +\name{logger_info_tester_function} +\alias{logger_info_tester_function} +\title{Testing logging INFO from package} +\usage{ +logger_info_tester_function(msg) +} +\arguments{ +\item{msg}{bar} +} +\description{ +Testing logging INFO from package +} diff --git a/inst/load-packages-in-background-process.R b/inst/load-packages-in-background-process.R index 350f4a21..53cb9ec1 100644 --- a/inst/load-packages-in-background-process.R +++ b/inst/load-packages-in-background-process.R @@ -1,4 +1,4 @@ ## this is to be called in the background process of appender_async ## because having library/require calls in that function throws false R CMD check alerts -require('logger') -require('txtq') +require("logger") +require("txtq") diff --git a/man/appender_async.Rd b/man/appender_async.Rd index 8e4ee115..fab61d2d 100644 --- a/man/appender_async.Rd +++ b/man/appender_async.Rd @@ -2,7 +2,8 @@ % Please edit documentation in R/appenders.R \name{appender_async} \alias{appender_async} -\title{Delays executing the actual appender function to the future in a background process to avoid blocking the main R session} +\title{Delays executing the actual appender function to the future in a +background process to avoid blocking the main R session} \usage{ appender_async( appender, @@ -12,22 +13,32 @@ appender_async( ) } \arguments{ -\item{appender}{a \code{\link[=log_appender]{log_appender()}} function with a \code{generator} attribute (TODO note not required, all fn will be passed if not)} +\item{appender}{a \code{\link[=log_appender]{log_appender()}} function with a \code{generator} +attribute (TODO note not required, all fn will be passed if +not)} \item{batch}{number of records to process from the queue at once} -\item{namespace}{\code{logger} namespace to use for logging messages on starting up the background process} +\item{namespace}{\code{logger} namespace to use for logging messages on +starting up the background process} -\item{init}{optional function to run in the background process that is useful to set up the environment required for logging, eg if the \code{appender} function requires some extra packages to be loaded or some environment variables to be set etc} +\item{init}{optional function to run in the background process that +is useful to set up the environment required for logging, eg if +the \code{appender} function requires some extra packages to be +loaded or some environment variables to be set etc} } \value{ function taking \code{lines} argument } \description{ -Delays executing the actual appender function to the future in a background process to avoid blocking the main R session +Delays executing the actual appender function to the future in a +background process to avoid blocking the main R session } \note{ -This functionality depends on the \pkg{txtq} and \pkg{callr} packages. The R session's temp folder is used for staging files (message queue and other forms of communication between the parent and child processes). +This functionality depends on the \pkg{txtq} and \pkg{callr} +packages. The R session's temp folder is used for staging files +(message queue and other forms of communication between the +parent and child processes). } \examples{ \dontrun{ @@ -35,22 +46,22 @@ appender_file_slow <- function(file) { force(file) function(lines) { Sys.sleep(1) - cat(lines, sep = '\n', file = file, append = TRUE) + cat(lines, sep = "\n", file = file, append = TRUE) } } ## log what's happening in the background -log_threshold(TRACE, namespace = 'async_logger') -log_appender(appender_console, namespace = 'async_logger') +log_threshold(TRACE, namespace = "async_logger") +log_appender(appender_console, namespace = "async_logger") ## start async appender t <- tempfile() -log_info('Logging in the background to {t}') +log_info("Logging in the background to {t}") my_appender <- appender_async(appender_file_slow(file = t)) ## use async appender log_appender(my_appender) -log_info('Was this slow?') +log_info("Was this slow?") system.time(for (i in 1:25) log_info(i)) readLines(t) @@ -58,18 +69,22 @@ Sys.sleep(10) readLines(t) ## check on the async appender (debugging, you will probably never need this) -attr(my_appender, 'async_writer_queue')$count() -attr(my_appender, 'async_writer_queue')$log() +attr(my_appender, "async_writer_queue")$count() +attr(my_appender, "async_writer_queue")$log() -attr(my_appender, 'async_writer_process')$get_pid() -attr(my_appender, 'async_writer_process')$get_state() -attr(my_appender, 'async_writer_process')$poll_process(1) -attr(my_appender, 'async_writer_process')$read() +attr(my_appender, "async_writer_process")$get_pid() +attr(my_appender, "async_writer_process")$get_state() +attr(my_appender, "async_writer_process")$poll_process(1) +attr(my_appender, "async_writer_process")$read() -attr(my_appender, 'async_writer_process')$is_alive() -attr(my_appender, 'async_writer_process')$read_error() +attr(my_appender, "async_writer_process")$is_alive() +attr(my_appender, "async_writer_process")$read_error() } } \seealso{ -This function is to be used with an actual \code{\link[=log_appender]{log_appender()}}, for example \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, \code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}} or \code{\link[=appender_kinesis]{appender_kinesis()}}. +This function is to be used with an actual +\code{\link[=log_appender]{log_appender()}}, for example \code{\link[=appender_console]{appender_console()}}, +\code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, +\code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}} or +\code{\link[=appender_kinesis]{appender_kinesis()}}. } diff --git a/man/appender_console.Rd b/man/appender_console.Rd index 766f2585..e5792a1f 100644 --- a/man/appender_console.Rd +++ b/man/appender_console.Rd @@ -16,5 +16,10 @@ appender_stderr(lines) Append log record to stderr } \seealso{ -This is a \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_stdout]{appender_stdout()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, \code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}}, \code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function in a background process. +This is a \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg +\code{\link[=appender_stdout]{appender_stdout()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, +\code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, +\code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}}, +\code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any +\code{\link[=log_appender]{log_appender()}} function in a background process. } diff --git a/man/appender_file.Rd b/man/appender_file.Rd index 6cd04af8..7df5e594 100644 --- a/man/appender_file.Rd +++ b/man/appender_file.Rd @@ -15,19 +15,29 @@ appender_file( \arguments{ \item{file}{path} -\item{append}{boolean passed to \code{cat} defining if the file should be overwritten with the most recent log message instead of appending} +\item{append}{boolean passed to \code{cat} defining if the file should +be overwritten with the most recent log message instead of +appending} -\item{max_lines}{numeric specifying the maximum number of lines allowed in a file before rotating} +\item{max_lines}{numeric specifying the maximum number of lines +allowed in a file before rotating} -\item{max_bytes}{numeric specifying the maximum number of bytes allowed in a file before rotating} +\item{max_bytes}{numeric specifying the maximum number of bytes +allowed in a file before rotating} -\item{max_files}{integer specifying the maximum number of files to be used in rotation} +\item{max_files}{integer specifying the maximum number of files to +be used in rotation} } \value{ function taking \code{lines} argument } \description{ -Log messages are written to a file with basic log rotation: when max number of lines or bytes is defined to be other than \code{Inf}, then the log file is renamed with a \code{.1} suffix and a new log file is created. The renaming happens recursively (eg \code{logfile.1} renamed to \code{logfile.2}) until the specified \code{max_files}, then the oldest file (\code{logfile.{max_files-1}}) is deleted. +Log messages are written to a file with basic log rotation: when +max number of lines or bytes is defined to be other than \code{Inf}, +then the log file is renamed with a \code{.1} suffix and a new log file +is created. The renaming happens recursively (eg \code{logfile.1} +renamed to \code{logfile.2}) until the specified \code{max_files}, then the +oldest file (\code{logfile.{max_files-1}}) is deleted. } \examples{ \dontrun{ @@ -43,8 +53,9 @@ readLines(t) ## rotated after every 3rd line up to max 5 files ## create a folder storing the log files -t <- tempfile(); dir.create(t) -f <- file.path(t, 'log') +t <- tempfile() +dir.create(t) +f <- file.path(t, "log") ## define the file logger with log rotation enabled log_appender(appender_file(f, max_lines = 3, max_files = 5L)) @@ -54,15 +65,20 @@ for (i in 1:25) log_info(i) ## see what was logged lapply(list.files(t, full.names = TRUE), function(t) { - cat('\n##', t, '\n') - cat(readLines(t), sep = '\n') + cat("\n##", t, "\n") + cat(readLines(t), sep = "\n") }) ## enable internal logging to see what's actually happening in the logrotate steps -log_threshold(TRACE, namespace = '.logger') +log_threshold(TRACE, namespace = ".logger") ## run the above commands again } } \seealso{ -This is generator function for \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, \code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}}, \code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function in a background process. +This is generator function for \code{\link[=log_appender]{log_appender()}}, for +alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_tee]{appender_tee()}}, +\code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, +\code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}}, +\code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any +\code{\link[=log_appender]{log_appender()}} function in a background process. } diff --git a/man/appender_kinesis.Rd b/man/appender_kinesis.Rd index e3eff62e..014f7403 100644 --- a/man/appender_kinesis.Rd +++ b/man/appender_kinesis.Rd @@ -10,7 +10,8 @@ appender_kinesis(stream) \item{stream}{name of the Kinesis stream} } \value{ -function taking \code{lines} and optional \code{partition_key} argument +function taking \code{lines} and optional \code{partition_key} +argument } \description{ Send log messages to a Amazon Kinesis stream @@ -19,5 +20,10 @@ Send log messages to a Amazon Kinesis stream This functionality depends on the \pkg{botor} package. } \seealso{ -This is generator function for \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, \code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function in a background process. +This is generator function for \code{\link[=log_appender]{log_appender()}}, for +alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, +\code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, +\code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}} and +\code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function +in a background process. } diff --git a/man/appender_pushbullet.Rd b/man/appender_pushbullet.Rd index e7413451..35a88cf8 100644 --- a/man/appender_pushbullet.Rd +++ b/man/appender_pushbullet.Rd @@ -7,7 +7,10 @@ appender_pushbullet(...) } \arguments{ -\item{...}{parameters passed to \code{pbPost}, such as \code{recipients} or \code{apikey}, although it's probably much better to set all these in the \verb{~/.rpushbullet.json} as per package docs at \url{http://dirk.eddelbuettel.com/code/rpushbullet.html}} +\item{...}{parameters passed to \code{pbPost}, such as \code{recipients} or +\code{apikey}, although it's probably much better to set all these +in the \verb{~/.rpushbullet.json} as per package docs at +\url{http://dirk.eddelbuettel.com/code/rpushbullet.html}} } \description{ Send log messages to Pushbullet @@ -16,5 +19,10 @@ Send log messages to Pushbullet This functionality depends on the \pkg{RPushbullet} package. } \seealso{ -This is generator function for \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}}, \code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function in a background process. +This is generator function for \code{\link[=log_appender]{log_appender()}}, for +alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, +\code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_telegram]{appender_telegram()}}, +\code{\link[=appender_syslog]{appender_syslog()}}, \code{\link[=appender_kinesis]{appender_kinesis()}} and +\code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function +in a background process. } diff --git a/man/appender_slack.Rd b/man/appender_slack.Rd index 78828c7a..e0885a18 100644 --- a/man/appender_slack.Rd +++ b/man/appender_slack.Rd @@ -13,7 +13,8 @@ appender_slack( ) } \arguments{ -\item{channel}{Slack channel name with a hashtag prefix for public channel and no prefix for private channels} +\item{channel}{Slack channel name with a hashtag prefix for public +channel and no prefix for private channels} \item{username}{Slack (bot) username} @@ -33,5 +34,10 @@ Send log messages to a Slack channel This functionality depends on the \pkg{slackr} package. } \seealso{ -This is generator function for \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, \code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}}, \code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function in a background process. +This is generator function for \code{\link[=log_appender]{log_appender()}}, for +alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, +\code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, +\code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}}, +\code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any +\code{\link[=log_appender]{log_appender()}} function in a background process. } diff --git a/man/appender_stdout.Rd b/man/appender_stdout.Rd index 241a14a5..e413b560 100644 --- a/man/appender_stdout.Rd +++ b/man/appender_stdout.Rd @@ -13,5 +13,6 @@ appender_stdout(lines) Append log record to stdout } \seealso{ -This is a \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}} +This is a \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, +\code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}} } diff --git a/man/appender_syslog.Rd b/man/appender_syslog.Rd index 3c8992cd..cf62fb18 100644 --- a/man/appender_syslog.Rd +++ b/man/appender_syslog.Rd @@ -29,5 +29,10 @@ if (requireNamespace("rsyslog", quietly = TRUE)) { } } \seealso{ -This is generator function for \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, \code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function in a background process. +This is generator function for \code{\link[=log_appender]{log_appender()}}, for +alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, +\code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, +\code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_kinesis]{appender_kinesis()}} and +\code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function +in a background process. } diff --git a/man/appender_syslognet.Rd b/man/appender_syslognet.Rd index 3844690f..37ce38af 100644 --- a/man/appender_syslognet.Rd +++ b/man/appender_syslognet.Rd @@ -25,7 +25,7 @@ This functionality depends on the \pkg{syslognet} package. \examples{ \dontrun{ if (requireNamespace("syslognet", quietly = TRUE)) { - log_appender(appender_syslognet("test_app", 'remoteserver')) + log_appender(appender_syslognet("test_app", "remoteserver")) log_info("Test message.") } } diff --git a/man/appender_tee.Rd b/man/appender_tee.Rd index 3ee0d5c4..f7ab7371 100644 --- a/man/appender_tee.Rd +++ b/man/appender_tee.Rd @@ -15,20 +15,31 @@ appender_tee( \arguments{ \item{file}{path} -\item{append}{boolean passed to \code{cat} defining if the file should be overwritten with the most recent log message instead of appending} +\item{append}{boolean passed to \code{cat} defining if the file should +be overwritten with the most recent log message instead of +appending} -\item{max_lines}{numeric specifying the maximum number of lines allowed in a file before rotating} +\item{max_lines}{numeric specifying the maximum number of lines +allowed in a file before rotating} -\item{max_bytes}{numeric specifying the maximum number of bytes allowed in a file before rotating} +\item{max_bytes}{numeric specifying the maximum number of bytes +allowed in a file before rotating} -\item{max_files}{integer specifying the maximum number of files to be used in rotation} +\item{max_files}{integer specifying the maximum number of files to +be used in rotation} } \value{ function taking \code{lines} argument } \description{ -This appends log messages to both console and a file. The same rotation options are available as in \code{\link[=appender_file]{appender_file()}}. +This appends log messages to both console and a file. The same +rotation options are available as in \code{\link[=appender_file]{appender_file()}}. } \seealso{ -This is generator function for \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, \code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}}, \code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function in a background process. +This is generator function for \code{\link[=log_appender]{log_appender()}}, for +alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, +\code{\link[=appender_slack]{appender_slack()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, +\code{\link[=appender_telegram]{appender_telegram()}}, \code{\link[=appender_syslog]{appender_syslog()}}, +\code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any +\code{\link[=log_appender]{log_appender()}} function in a background process. } diff --git a/man/appender_telegram.Rd b/man/appender_telegram.Rd index 607f64d0..9f0f8421 100644 --- a/man/appender_telegram.Rd +++ b/man/appender_telegram.Rd @@ -11,11 +11,13 @@ appender_telegram( ) } \arguments{ -\item{chat_id}{Unique identifier for the target chat or username of the target channel (in the format @channelusername)} +\item{chat_id}{Unique identifier for the target chat or username of +the target channel (in the format @channelusername)} \item{bot_token}{Telegram Authorization token} -\item{parse_mode}{Message parse mode. Allowed values: Markdown or HTML} +\item{parse_mode}{Message parse mode. Allowed values: Markdown or +HTML} } \value{ function taking \code{lines} argument @@ -27,5 +29,9 @@ Send log messages to a Telegram chat This functionality depends on the \pkg{telegram} package. } \seealso{ -This is generator function for \code{\link[=log_appender]{log_appender()}}, for alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, \code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, \code{\link[=appender_syslog]{appender_syslog()}}, \code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any \code{\link[=log_appender]{log_appender()}} function in a background process. +This is generator function for \code{\link[=log_appender]{log_appender()}}, for +alternatives, see eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}}, +\code{\link[=appender_tee]{appender_tee()}}, \code{\link[=appender_pushbullet]{appender_pushbullet()}}, \code{\link[=appender_syslog]{appender_syslog()}}, +\code{\link[=appender_kinesis]{appender_kinesis()}} and \code{\link[=appender_async]{appender_async()}} for evaluate any +\code{\link[=log_appender]{log_appender()}} function in a background process. } diff --git a/man/catch_base_log.Rd b/man/catch_base_log.Rd index ba1fbad5..b07ce97e 100644 --- a/man/catch_base_log.Rd +++ b/man/catch_base_log.Rd @@ -25,12 +25,12 @@ Catch the log header \examples{ \dontrun{ catch_base_log(INFO, NA_character_) -logger <- layout_glue_generator(format = '{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}') +logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") log_layout(logger) catch_base_log(INFO, NA_character_) fun <- function() catch_base_log(INFO, NA_character_) fun() -catch_base_log(INFO, NA_character_, .topcall = call('funLONG')) +catch_base_log(INFO, NA_character_, .topcall = call("funLONG")) } } \keyword{internal} diff --git a/man/colorize_by_log_level.Rd b/man/colorize_by_log_level.Rd index 727c9dff..54322ac0 100644 --- a/man/colorize_by_log_level.Rd +++ b/man/colorize_by_log_level.Rd @@ -24,20 +24,20 @@ the terminal. } \examples{ \dontshow{if (requireNamespace("crayon")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} -cat(colorize_by_log_level('foobar', FATAL), '\n') -cat(colorize_by_log_level('foobar', ERROR), '\n') -cat(colorize_by_log_level('foobar', WARN), '\n') -cat(colorize_by_log_level('foobar', SUCCESS), '\n') -cat(colorize_by_log_level('foobar', INFO), '\n') -cat(colorize_by_log_level('foobar', DEBUG), '\n') -cat(colorize_by_log_level('foobar', TRACE), '\n') +cat(colorize_by_log_level("foobar", FATAL), "\n") +cat(colorize_by_log_level("foobar", ERROR), "\n") +cat(colorize_by_log_level("foobar", WARN), "\n") +cat(colorize_by_log_level("foobar", SUCCESS), "\n") +cat(colorize_by_log_level("foobar", INFO), "\n") +cat(colorize_by_log_level("foobar", DEBUG), "\n") +cat(colorize_by_log_level("foobar", TRACE), "\n") -cat(grayscale_by_log_level('foobar', FATAL), '\n') -cat(grayscale_by_log_level('foobar', ERROR), '\n') -cat(grayscale_by_log_level('foobar', WARN), '\n') -cat(grayscale_by_log_level('foobar', SUCCESS), '\n') -cat(grayscale_by_log_level('foobar', INFO), '\n') -cat(grayscale_by_log_level('foobar', DEBUG), '\n') -cat(grayscale_by_log_level('foobar', TRACE), '\n') +cat(grayscale_by_log_level("foobar", FATAL), "\n") +cat(grayscale_by_log_level("foobar", ERROR), "\n") +cat(grayscale_by_log_level("foobar", WARN), "\n") +cat(grayscale_by_log_level("foobar", SUCCESS), "\n") +cat(grayscale_by_log_level("foobar", INFO), "\n") +cat(grayscale_by_log_level("foobar", DEBUG), "\n") +cat(grayscale_by_log_level("foobar", TRACE), "\n") \dontshow{\}) # examplesIf} } diff --git a/man/fail_on_missing_package.Rd b/man/fail_on_missing_package.Rd index 5e0b6dcf..c0299eb7 100644 --- a/man/fail_on_missing_package.Rd +++ b/man/fail_on_missing_package.Rd @@ -16,9 +16,9 @@ Check if R package can be loaded and fails loudly otherwise } \examples{ \dontrun{ -f <- function() fail_on_missing_package('foobar') +f <- function() fail_on_missing_package("foobar") f() -g <- function() fail_on_missing_package('stats') +g <- function() fail_on_missing_package("stats") g() } } diff --git a/man/formatter_glue.Rd b/man/formatter_glue.Rd index 2bdcf8e1..e21361ab 100644 --- a/man/formatter_glue.Rd +++ b/man/formatter_glue.Rd @@ -14,11 +14,17 @@ formatter_glue( \arguments{ \item{...}{passed to \code{glue} for the text interpolation} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector @@ -27,8 +33,15 @@ character vector Apply \code{glue} to convert R objects into a character vector } \note{ -Although this is the default log message formatter function, but when \pkg{glue} is not installed, \code{\link[=formatter_sprintf]{formatter_sprintf()}} will be used as a fallback. +Although this is the default log message formatter function, +but when \pkg{glue} is not installed, \code{\link[=formatter_sprintf]{formatter_sprintf()}} +will be used as a fallback. } \seealso{ -This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, \code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the formatter on it. +This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see +\code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, +\code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, \code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, +\code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} +and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the +formatter on it. } diff --git a/man/formatter_glue_or_sprintf.Rd b/man/formatter_glue_or_sprintf.Rd index 7e8474a2..224315b6 100644 --- a/man/formatter_glue_or_sprintf.Rd +++ b/man/formatter_glue_or_sprintf.Rd @@ -13,38 +13,53 @@ formatter_glue_or_sprintf( ) } \arguments{ -\item{msg}{passed to \code{sprintf} as \code{fmt} or handled as part of \code{...} in \code{glue}} +\item{msg}{passed to \code{sprintf} as \code{fmt} or handled as part of \code{...} +in \code{glue}} \item{...}{passed to \code{glue} for the text interpolation} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector } \description{ -The best of both words: using both formatter functions in your log messages, which can be useful eg if you are migrating from \code{sprintf} formatted log messages to \code{glue} or similar. +The best of both words: using both formatter functions in your log +messages, which can be useful eg if you are migrating from +\code{sprintf} formatted log messages to \code{glue} or similar. } \details{ -Note that this function tries to be smart when passing arguments to \code{glue} and \code{sprintf}, but might fail with some edge cases, and returns an unformatted string. +Note that this function tries to be smart when passing arguments to +\code{glue} and \code{sprintf}, but might fail with some edge cases, and +returns an unformatted string. } \examples{ \dontrun{ formatter_glue_or_sprintf("{a} + {b} = \%s", a = 2, b = 3, 5) -formatter_glue_or_sprintf("{pi} * {2} = \%s", pi*2) +formatter_glue_or_sprintf("{pi} * {2} = \%s", pi * 2) formatter_glue_or_sprintf("{pi} * {2} = {pi*2}") formatter_glue_or_sprintf("Hi ", "{c('foo', 'bar')}, did you know that 2*4={2*4}") formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4={2*4}") -formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4=\%s", 2*4) -formatter_glue_or_sprintf("Hi \%s, did you know that 2*4={2*4}", c('foo', 'bar')) -formatter_glue_or_sprintf("Hi \%s, did you know that 2*4=\%s", c('foo', 'bar'), 2*4) +formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4=\%s", 2 * 4) +formatter_glue_or_sprintf("Hi \%s, did you know that 2*4={2*4}", c("foo", "bar")) +formatter_glue_or_sprintf("Hi \%s, did you know that 2*4=\%s", c("foo", "bar"), 2 * 4) } } \seealso{ -This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the formatter on it. +This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see +\code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, +\code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_logging]{formatter_logging()}}, +\code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} and \code{\link[=skip_formatter]{skip_formatter()}} +for marking a string not to apply the formatter on it. } diff --git a/man/formatter_glue_safe.Rd b/man/formatter_glue_safe.Rd index f651dbc0..5971e25b 100644 --- a/man/formatter_glue_safe.Rd +++ b/man/formatter_glue_safe.Rd @@ -14,11 +14,17 @@ formatter_glue_safe( \arguments{ \item{...}{passed to \code{glue_safe} for the text interpolation} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector @@ -27,5 +33,10 @@ character vector Apply \code{glue_safe} to convert R objects into a character vector } \seealso{ -This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, \code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the formatter on it. +This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see +\code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, +\code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, +\code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} +and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the +formatter on it. } diff --git a/man/formatter_json.Rd b/man/formatter_json.Rd index ba6eaff7..d0f4c102 100644 --- a/man/formatter_json.Rd +++ b/man/formatter_json.Rd @@ -14,11 +14,17 @@ formatter_json( \arguments{ \item{...}{passed to \code{toJSON} wrapped into a \code{list}} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector @@ -38,5 +44,11 @@ log_info(mtcars = mtcars, species = iris$Species) } } \seealso{ -This is a \code{\link[=log_formatter]{log_formatter()}} potentially to be used with \code{\link[=layout_json_parser]{layout_json_parser()}}, for alternatives, see \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, \code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_pander]{formatter_pander()}} and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the formatter on it. +This is a \code{\link[=log_formatter]{log_formatter()}} potentially to be used with +\code{\link[=layout_json_parser]{layout_json_parser()}}, for alternatives, see +\code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, +\code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, +\code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_pander]{formatter_pander()}} and +\code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the +formatter on it. } diff --git a/man/formatter_logging.Rd b/man/formatter_logging.Rd index 53171dc3..712996ed 100644 --- a/man/formatter_logging.Rd +++ b/man/formatter_logging.Rd @@ -12,31 +12,46 @@ formatter_logging( ) } \arguments{ -\item{...}{string and further params passed to \code{sprintf} or R expressions to be evaluated} +\item{...}{string and further params passed to \code{sprintf} or R +expressions to be evaluated} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector } \description{ -The \pkg{logging} package uses a formatter that behaves differently when the input is a string or other R object. If the first argument is a string, then \code{\link[=sprintf]{sprintf()}} is being called -- otherwise it does something like \code{\link[=log_eval]{log_eval()}} and logs the R expression(s) and the result(s) as well. +The \pkg{logging} package uses a formatter that behaves differently +when the input is a string or other R object. If the first argument +is a string, then \code{\link[=sprintf]{sprintf()}} is being called -- otherwise it does +something like \code{\link[=log_eval]{log_eval()}} and logs the R expression(s) and the +result(s) as well. } \examples{ \dontrun{ log_formatter(formatter_logging) -log_info('42') +log_info("42") log_info(42) -log_info(4+2) -log_info('foo \%s', 'bar') -log_info('vector \%s', 1:3) -log_info(12, 1+1, 2 * 2) +log_info(4 + 2) +log_info("foo \%s", "bar") +log_info("vector \%s", 1:3) +log_info(12, 1 + 1, 2 * 2) } } \seealso{ -This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the formatter on it. +This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see +\code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_glue]{formatter_glue()}}, +\code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, +\code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} and \code{\link[=skip_formatter]{skip_formatter()}} +for marking a string not to apply the formatter on it. } diff --git a/man/formatter_pander.Rd b/man/formatter_pander.Rd index e564b3b7..a2ca8d44 100644 --- a/man/formatter_pander.Rd +++ b/man/formatter_pander.Rd @@ -17,11 +17,17 @@ formatter_pander( \item{...}{optional parameters passed to \code{pander}} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector @@ -35,14 +41,17 @@ This functionality depends on the \pkg{pander} package. \examples{ \dontrun{ log_formatter(formatter_pander) -log_info('42') +log_info("42") log_info(42) -log_info(4+2) +log_info(4 + 2) log_info(head(iris)) -log_info(head(iris), style = 'simple') +log_info(head(iris), style = "simple") log_info(lm(hp ~ wt, mtcars)) } } \seealso{ -This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, \code{\link[=formatter_logging]{formatter_logging()}} +This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see +\code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, +\code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, +\code{\link[=formatter_logging]{formatter_logging()}} } diff --git a/man/formatter_paste.Rd b/man/formatter_paste.Rd index 80723138..7dda8cac 100644 --- a/man/formatter_paste.Rd +++ b/man/formatter_paste.Rd @@ -14,11 +14,17 @@ formatter_paste( \arguments{ \item{...}{passed to \code{paste}} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector @@ -27,5 +33,10 @@ character vector Concatenate R objects into a character vector via \code{paste} } \seealso{ -This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, \code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the formatter on it. +This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see +\code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, +\code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, +\code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} +and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the +formatter on it. } diff --git a/man/formatter_sprintf.Rd b/man/formatter_sprintf.Rd index fbb7107e..b0729ee2 100644 --- a/man/formatter_sprintf.Rd +++ b/man/formatter_sprintf.Rd @@ -17,11 +17,17 @@ formatter_sprintf( \item{...}{passed to \code{sprintf}} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector @@ -30,5 +36,10 @@ character vector Apply \code{sprintf} to convert R objects into a character vector } \seealso{ -This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, \code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the formatter on it. +This is a \code{\link[=log_formatter]{log_formatter()}}, for alternatives, see +\code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_glue]{formatter_glue()}}, +\code{\link[=formatter_glue_safe]{formatter_glue_safe()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, +\code{\link[=formatter_logging]{formatter_logging()}}, \code{\link[=formatter_json]{formatter_json()}}, \code{\link[=formatter_pander]{formatter_pander()}} +and \code{\link[=skip_formatter]{skip_formatter()}} for marking a string not to apply the +formatter on it. } diff --git a/man/get_logger_definitions.Rd b/man/get_logger_definitions.Rd index 854f01d7..4539fa4f 100644 --- a/man/get_logger_definitions.Rd +++ b/man/get_logger_definitions.Rd @@ -2,17 +2,20 @@ % Please edit documentation in R/logger.R \name{get_logger_definitions} \alias{get_logger_definitions} -\title{Find the logger definition(s) specified for the current namespace with a fallback to the global namespace} +\title{Find the logger definition(s) specified for the current namespace +with a fallback to the global namespace} \usage{ get_logger_definitions(namespace = NA_character_, .topenv = parent.frame()) } \arguments{ -\item{namespace}{override the default / auto-picked namespace with a custom string} +\item{namespace}{override the default / auto-picked namespace with +a custom string} } \value{ list of function(s) } \description{ -Find the logger definition(s) specified for the current namespace with a fallback to the global namespace +Find the logger definition(s) specified for the current namespace +with a fallback to the global namespace } \keyword{internal} diff --git a/man/get_logger_meta_variables.Rd b/man/get_logger_meta_variables.Rd index 80a6dbe2..33af7a40 100644 --- a/man/get_logger_meta_variables.Rd +++ b/man/get_logger_meta_variables.Rd @@ -15,38 +15,53 @@ get_logger_meta_variables( \arguments{ \item{log_level}{log level as per \code{\link[=log_levels]{log_levels()}}} -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ list } \description{ Available variables to be used in the log formatter functions, eg in \code{\link[=layout_glue_generator]{layout_glue_generator()}}: +} +\details{ \itemize{ -\item levelr: log level as an R object, eg \code{\link[=INFO]{INFO()}} -\item level: log level as a string, eg \code{\link[=INFO]{INFO()}} -\item time: current time as \code{POSIXct} -\item node: name by which the machine is known on the network as reported by \code{Sys.info} -\item arch: machine type, typically the CPU architecture -\item os_name: Operating System's name -\item os_release: Operating System's release -\item os_version: Operating System's version -\item user: name of the real user id as reported by \code{Sys.info} -\item pid: the process identification number of the R session -\item node: name by which the machine is known on the network as reported by \code{Sys.info} -\item r_version: R's major and minor version as a string -\item ns: namespace usually defaults to \code{global} or the name of the holding R package of the calling the logging function -\item ns_pkg_version: the version of \code{ns} when it's a package -\item ans: same as \code{ns} if there's a defined \code{\link[=logger]{logger()}} for the namespace, otherwise a fallback namespace (eg usually \code{global}) -\item topenv: the name of the top environment from which the parent call was called (eg R package name or \code{GlobalEnv}) -\item call: parent call (if any) calling the logging function -\item fn: function's (if any) name calling the logging function +\item \code{levelr}: log level as an R object, eg \code{\link[=INFO]{INFO()}} +\item \code{level}: log level as a string, eg \code{\link[=INFO]{INFO()}} +\item \code{time}: current time as \code{POSIXct} +\item \code{node}: name by which the machine is known on the network as reported by \code{Sys.info} +\item \code{arch}: machine type, typically the CPU architecture +\item \code{os_name}: Operating System's name +\item \code{os_release}: Operating System's release +\item \code{os_version}: Operating System's version +\item \code{user}: name of the real user id as reported by \code{Sys.info} +\item \code{pid}: the process identification number of the R session +\item \code{node}: name by which the machine is known on the network as reported by \code{Sys.info} +\item \code{r_version}: R's major and minor version as a string +\item \code{ns}: namespace usually defaults to \code{global} or the name of the holding R package +of the calling the logging function +\item \code{ns_pkg_version}: the version of \code{ns} when it's a package +\item \code{ans}: same as \code{ns} if there's a defined \code{\link[=logger]{logger()}} for the namespace, +otherwise a fallback namespace (eg usually \code{global}) +\item \code{topenv}: the name of the top environment from which the parent call was called +(eg R package name or \code{GlobalEnv}) +\item \code{call}: parent call (if any) calling the logging function +\item \code{fn}: function's (if any) name calling the logging function } } \seealso{ diff --git a/man/grapes-except-grapes.Rd b/man/grapes-except-grapes.Rd index 5e7a5e7a..75d6fcb9 100644 --- a/man/grapes-except-grapes.Rd +++ b/man/grapes-except-grapes.Rd @@ -2,7 +2,8 @@ % Please edit documentation in R/try.R \name{\%except\%} \alias{\%except\%} -\title{Try to evaluate an expressions and evaluate another expression on exception} +\title{Try to evaluate an expressions and evaluate another expression on +exception} \usage{ try \%except\% except } @@ -12,14 +13,16 @@ try \%except\% except \item{except}{fallback R expression to be evaluated if \code{try} fails} } \description{ -Try to evaluate an expressions and evaluate another expression on exception +Try to evaluate an expressions and evaluate another expression on +exception } \note{ -Suppress log messages in the \code{except} namespace if you don't want to throw a \code{WARN} log message on the exception branch. +Suppress log messages in the \code{except} namespace if you don't +want to throw a \code{WARN} log message on the exception branch. } \examples{ everything \%except\% 42 -everything <- '640kb' +everything <- "640kb" everything \%except\% 42 FunDoesNotExist(1:10) \%except\% sum(1:10) / length(1:10) diff --git a/man/layout_blank.Rd b/man/layout_blank.Rd index da7d0783..0b69e4ff 100644 --- a/man/layout_blank.Rd +++ b/man/layout_blank.Rd @@ -2,7 +2,8 @@ % Please edit documentation in R/layouts.R \name{layout_blank} \alias{layout_blank} -\title{Format a log record by including the raw message without anything added or modified} +\title{Format a log record by including the raw message without anything +added or modified} \usage{ layout_blank( level, @@ -18,20 +19,33 @@ layout_blank( \item{msg}{string message} -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector } \description{ -Format a log record by including the raw message without anything added or modified +Format a log record by including the raw message without anything +added or modified } \seealso{ -This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json]{layout_json()}}, or generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} +This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see +\code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json]{layout_json()}}, or +generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} } diff --git a/man/layout_glue.Rd b/man/layout_glue.Rd index 5faead20..dd8e79de 100644 --- a/man/layout_glue.Rd +++ b/man/layout_glue.Rd @@ -18,20 +18,37 @@ layout_glue( \item{msg}{string message} -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector } \description{ -By default, this layout includes the log level of the log record as per \code{\link[=log_levels]{log_levels()}}, the current timestamp and the actual log message -- that you can override via calling \code{\link[=layout_glue_generator]{layout_glue_generator()}} directly. For colorized output, see \code{\link[=layout_glue_colors]{layout_glue_colors()}}. +By default, this layout includes the log level of the log record as +per \code{\link[=log_levels]{log_levels()}}, the current timestamp and the actual log +message -- that you can override via calling +\code{\link[=layout_glue_generator]{layout_glue_generator()}} directly. For colorized output, see +\code{\link[=layout_glue_colors]{layout_glue_colors()}}. } \seealso{ -This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see \code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json]{layout_json()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} +This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see +\code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, +\code{\link[=layout_json]{layout_json()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator functions +such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} } diff --git a/man/layout_glue_colors.Rd b/man/layout_glue_colors.Rd index 03307afc..9e9e00fc 100644 --- a/man/layout_glue_colors.Rd +++ b/man/layout_glue_colors.Rd @@ -18,13 +18,23 @@ layout_glue_colors( \item{msg}{string message} -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector @@ -39,18 +49,21 @@ This functionality depends on the \pkg{crayon} package. } \examples{ \dontshow{if (requireNamespace("crayon")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} -log_layout(layout_glue_colors) -log_threshold(TRACE) -log_info('Starting the script...') -log_debug('This is the second line') -log_trace('That is being placed right after the first one.') -log_warn('Some errors might come!') -log_error('This is a problem') -log_debug('Getting an error is usually bad') -log_error('This is another problem') -log_fatal('The last problem.') + log_layout(layout_glue_colors) + log_threshold(TRACE) + log_info("Starting the script...") + log_debug("This is the second line") + log_trace("That is being placed right after the first one.") + log_warn("Some errors might come!") + log_error("This is a problem") + log_debug("Getting an error is usually bad") + log_error("This is another problem") + log_fatal("The last problem.") \dontshow{\}) # examplesIf} } \seealso{ -This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see \code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue]{layout_glue()}}, \code{\link[=layout_json]{layout_json()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} +This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see +\code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue]{layout_glue()}}, +\code{\link[=layout_json]{layout_json()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator functions +such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} } diff --git a/man/layout_glue_generator.Rd b/man/layout_glue_generator.Rd index 74ab7523..175d8780 100644 --- a/man/layout_glue_generator.Rd +++ b/man/layout_glue_generator.Rd @@ -9,26 +9,29 @@ layout_glue_generator( ) } \arguments{ -\item{format}{\code{glue}-flavored layout of the message using the above variables} +\item{format}{\code{glue}-flavored layout of the message using the above +variables} } \value{ -function taking \code{level} and \code{msg} arguments - keeping the original call creating the generator in the \code{generator} attribute that is returned when calling \code{\link[=log_layout]{log_layout()}} for the currently used layout +function taking \code{level} and \code{msg} arguments - keeping the +original call creating the generator in the \code{generator} attribute +that is returned when calling \code{\link[=log_layout]{log_layout()}} for the currently +used layout } \description{ \code{format} is passed to \code{glue} with access to the below variables: -\itemize{ -\item msg: the actual log message -\item further variables set by \code{\link[=get_logger_meta_variables]{get_logger_meta_variables()}} -} +\itemize{ \item msg: the actual log message \item further variables +set by \code{\link[=get_logger_meta_variables]{get_logger_meta_variables()}} } } \examples{ \dontrun{ example_layout <- layout_glue_generator( - format = '{node}/{pid}/{ns}/{ans}/{topenv}/{fn} {time} {level}: {msg}') -example_layout(INFO, 'try {runif(1)}') + format = "{node}/{pid}/{ns}/{ans}/{topenv}/{fn} {time} {level}: {msg}" +) +example_layout(INFO, "try {runif(1)}") log_layout(example_layout) -log_info('try {runif(1)}') +log_info("try {runif(1)}") } } \seealso{ diff --git a/man/layout_json.Rd b/man/layout_json.Rd index 84a6e177..38d90ff4 100644 --- a/man/layout_json.Rd +++ b/man/layout_json.Rd @@ -10,7 +10,8 @@ layout_json( ) } \arguments{ -\item{fields}{character vector of field names to be included in the JSON} +\item{fields}{character vector of field names to be included in the +JSON} } \value{ character vector @@ -25,9 +26,12 @@ This functionality depends on the \pkg{jsonlite} package. \dontrun{ log_layout(layout_json()) log_info(42) -log_info('ok {1:3} + {1:3} = {2*(1:3)}') +log_info("ok {1:3} + {1:3} = {2*(1:3)}") } } \seealso{ -This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see \code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue]{layout_glue()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} +This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see +\code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue]{layout_glue()}}, +\code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator +functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} } diff --git a/man/layout_json_parser.Rd b/man/layout_json_parser.Rd index d35a30a4..60eebe99 100644 --- a/man/layout_json_parser.Rd +++ b/man/layout_json_parser.Rd @@ -2,7 +2,8 @@ % Please edit documentation in R/layouts.R \name{layout_json_parser} \alias{layout_json_parser} -\title{Generate log layout function rendering JSON after merging meta fields with parsed list from JSON message} +\title{Generate log layout function rendering JSON after merging meta +fields with parsed list from JSON message} \usage{ layout_json_parser( fields = c("time", "level", "ns", "ans", "topenv", "fn", "node", "arch", "os_name", @@ -10,10 +11,12 @@ layout_json_parser( ) } \arguments{ -\item{fields}{character vector of field names to be included in the JSON} +\item{fields}{character vector of field names to be included in the +JSON} } \description{ -Generate log layout function rendering JSON after merging meta fields with parsed list from JSON message +Generate log layout function rendering JSON after merging meta +fields with parsed list from JSON message } \note{ This functionality depends on the \pkg{jsonlite} package. @@ -24,10 +27,13 @@ log_formatter(formatter_json) log_info(everything = 42) log_layout(layout_json_parser()) log_info(everything = 42) -log_layout(layout_json_parser(fields = c('time', 'node'))) +log_layout(layout_json_parser(fields = c("time", "node"))) log_info(cars = row.names(mtcars), species = unique(iris$Species)) } } \seealso{ -This is a \code{\link[=log_layout]{log_layout()}} potentially to be used with \code{\link[=formatter_json]{formatter_json()}}, for alternatives, see \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue]{layout_glue()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json]{layout_json()}} or generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} +This is a \code{\link[=log_layout]{log_layout()}} potentially to be used with +\code{\link[=formatter_json]{formatter_json()}}, for alternatives, see \code{\link[=layout_simple]{layout_simple()}}, +\code{\link[=layout_glue]{layout_glue()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json]{layout_json()}} or +generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} } diff --git a/man/layout_logging.Rd b/man/layout_logging.Rd index 742cdb9d..253ad59a 100644 --- a/man/layout_logging.Rd +++ b/man/layout_logging.Rd @@ -18,13 +18,23 @@ layout_logging( \item{msg}{string message} -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector @@ -36,12 +46,15 @@ Format a log record as the logging package does by default \dontrun{ log_layout(layout_logging) log_info(42) -log_info(42, namespace = 'everything') +log_info(42, namespace = "everything") -devtools::load_all(system.file('demo-packages/logger-tester-package', package = 'logger')) +devtools::load_all(system.file("demo-packages/logger-tester-package", package = "logger")) logger_tester_function(INFO, 42) } } \seealso{ -This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see \code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_glue]{layout_glue()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json]{layout_json()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} +This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see +\code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_glue]{layout_glue()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, +\code{\link[=layout_json]{layout_json()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator functions +such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} } diff --git a/man/layout_simple.Rd b/man/layout_simple.Rd index fbf0cb6c..21f7e7df 100644 --- a/man/layout_simple.Rd +++ b/man/layout_simple.Rd @@ -2,7 +2,8 @@ % Please edit documentation in R/layouts.R \name{layout_simple} \alias{layout_simple} -\title{Format a log record by concatenating the log level, timestamp and message} +\title{Format a log record by concatenating the log level, timestamp and +message} \usage{ layout_simple( level, @@ -18,20 +19,34 @@ layout_simple( \item{msg}{string message} -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ character vector } \description{ -Format a log record by concatenating the log level, timestamp and message +Format a log record by concatenating the log level, timestamp and +message } \seealso{ -This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see \code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_glue]{layout_glue()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json]{layout_json()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} +This is a \code{\link[=log_layout]{log_layout()}}, for alternatives, see +\code{\link[=layout_blank]{layout_blank()}}, \code{\link[=layout_glue]{layout_glue()}}, \code{\link[=layout_glue_colors]{layout_glue_colors()}}, +\code{\link[=layout_json]{layout_json()}}, \code{\link[=layout_json_parser]{layout_json_parser()}}, or generator functions +such as \code{\link[=layout_glue_generator]{layout_glue_generator()}} } diff --git a/man/layout_syslognet.Rd b/man/layout_syslognet.Rd index c8b6624d..a8c940a8 100644 --- a/man/layout_syslognet.Rd +++ b/man/layout_syslognet.Rd @@ -18,13 +18,23 @@ layout_syslognet( \item{msg}{string message} -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ A character vector with a severity attribute. diff --git a/man/log_appender.Rd b/man/log_appender.Rd index 29f6fb2d..53ed3e68 100644 --- a/man/log_appender.Rd +++ b/man/log_appender.Rd @@ -7,7 +7,9 @@ log_appender(appender = NULL, namespace = "global", index = 1) } \arguments{ -\item{appender}{function delivering a log record to the destination, eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}} or \code{\link[=appender_tee]{appender_tee()}}, default NULL} +\item{appender}{function delivering a log record to the +destination, eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}} or +\code{\link[=appender_tee]{appender_tee()}}, default NULL} \item{namespace}{logger namespace} diff --git a/man/log_config_setter.Rd b/man/log_config_setter.Rd deleted file mode 100644 index 9bde4c96..00000000 --- a/man/log_config_setter.Rd +++ /dev/null @@ -1,24 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/logger.R -\name{log_config_setter} -\alias{log_config_setter} -\title{Base Logging Function} -\usage{ -log_config_setter(fun_name, arg, namespace, index) -} -\arguments{ -\item{fun_name}{string a full name of log function} - -\item{arg}{see \code{\link[=log_levels]{log_levels()}}} - -\item{namespace}{logger namespace} - -\item{index}{index of the logger within the namespace} -} -\value{ -currently set or return log function property -} -\description{ -Base Logging Function -} -\keyword{internal} diff --git a/man/log_errors.Rd b/man/log_errors.Rd index 9680ef3e..51dd5730 100644 --- a/man/log_errors.Rd +++ b/man/log_errors.Rd @@ -10,11 +10,13 @@ log_errors(muffle = getOption("logger_muffle_errors", FALSE)) \item{muffle}{if TRUE, the error is not thrown after being logged} } \description{ -This function uses \code{trace} to add a \code{log_error} function call when \code{stop} is called to log the error messages with the \code{logger} layout and appender. +This function uses \code{trace} to add a \code{log_error} function call when +\code{stop} is called to log the error messages with the \code{logger} layout +and appender. } \examples{ \dontrun{ log_errors() -stop('foobar') +stop("foobar") } } diff --git a/man/log_eval.Rd b/man/log_eval.Rd index 44070e81..bd957b7e 100644 --- a/man/log_eval.Rd +++ b/man/log_eval.Rd @@ -7,11 +7,16 @@ log_eval(expr, level = TRACE, multiline = FALSE) } \arguments{ -\item{expr}{R expression to be evaluated while logging the expression itself along with the result} +\item{expr}{R expression to be evaluated while logging the +expression itself along with the result} \item{level}{\code{\link[=log_levels]{log_levels()}}} -\item{multiline}{setting to \code{FALSE} will print both the expression (enforced to be on one line by removing line-breaks if any) and its result on a single line separated by \verb{=>}, while setting to \code{TRUE} will log the expression and the result in separate sections reserving line-breaks and rendering the printed results} +\item{multiline}{setting to \code{FALSE} will print both the expression +(enforced to be on one line by removing line-breaks if any) and +its result on a single line separated by \verb{=>}, while setting to +\code{TRUE} will log the expression and the result in separate +sections reserving line-breaks and rendering the printed results} } \description{ Evaluate an expression and log results @@ -45,6 +50,6 @@ log_eval(f <- log_eval) log_eval(f <- log_eval, multiline = TRUE) ## doing something computationally intensive -log_eval(system.time(for(i in 1:100) mad(runif(1000))), multiline = TRUE) +log_eval(system.time(for (i in 1:100) mad(runif(1000))), multiline = TRUE) } } diff --git a/man/log_failure.Rd b/man/log_failure.Rd index c45fb0cf..219583d7 100644 --- a/man/log_failure.Rd +++ b/man/log_failure.Rd @@ -14,7 +14,7 @@ Logs the error message to console before failing } \examples{ \dontrun{ -log_failure('foobar') +log_failure("foobar") log_failure(foobar) } } diff --git a/man/log_formatter.Rd b/man/log_formatter.Rd index 813a94f9..442c8786 100644 --- a/man/log_formatter.Rd +++ b/man/log_formatter.Rd @@ -7,7 +7,10 @@ log_formatter(formatter = NULL, namespace = "global", index = 1) } \arguments{ -\item{formatter}{function defining how R objects are converted into a single string, eg \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, \code{\link[=formatter_logging]{formatter_logging()}}, default NULL} +\item{formatter}{function defining how R objects are converted into +a single string, eg \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, +\code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, +\code{\link[=formatter_logging]{formatter_logging()}}, default NULL} \item{namespace}{logger namespace} @@ -17,5 +20,6 @@ log_formatter(formatter = NULL, namespace = "global", index = 1) Get or set log message formatter } \seealso{ -\code{\link[=logger]{logger()}}, \code{\link[=log_threshold]{log_threshold()}}, \code{\link[=log_appender]{log_appender()}} and \code{\link[=log_layout]{log_layout()}} +\code{\link[=logger]{logger()}}, \code{\link[=log_threshold]{log_threshold()}}, \code{\link[=log_appender]{log_appender()}} and +\code{\link[=log_layout]{log_layout()}} } diff --git a/man/log_layout.Rd b/man/log_layout.Rd index ef60d289..fd3781ed 100644 --- a/man/log_layout.Rd +++ b/man/log_layout.Rd @@ -7,7 +7,10 @@ log_layout(layout = NULL, namespace = "global", index = 1) } \arguments{ -\item{layout}{function defining the structure of a log record, eg \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue]{layout_glue()}} or \code{\link[=layout_glue_colors]{layout_glue_colors()}}, \code{\link[=layout_json]{layout_json()}}, or generator functions such as \code{\link[=layout_glue_generator]{layout_glue_generator()}}, default NULL} +\item{layout}{function defining the structure of a log record, eg +\code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue]{layout_glue()}} or \code{\link[=layout_glue_colors]{layout_glue_colors()}}, +\code{\link[=layout_json]{layout_json()}}, or generator functions such as +\code{\link[=layout_glue_generator]{layout_glue_generator()}}, default NULL} \item{namespace}{logger namespace} diff --git a/man/log_level.Rd b/man/log_level.Rd index 4022b01a..9937c0df 100644 --- a/man/log_level.Rd +++ b/man/log_level.Rd @@ -79,15 +79,26 @@ log_trace( \arguments{ \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} -\item{...}{R objects that can be converted to a character vector via the active message formatter function} - -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} - -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} - -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} - -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{...}{R objects that can be converted to a character vector +via the active message formatter function} + +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} + +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} + +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} + +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \value{ Invisible \code{list} of \code{logger} objects. See \code{\link[=logger]{logger()}} for more details on the format/ @@ -97,24 +108,24 @@ Log a message with given log level } \examples{ \dontrun{ -log_level(INFO, 'hi there') -log_info('hi there') +log_level(INFO, "hi there") +log_info("hi there") ## output omitted -log_debug('hi there') +log_debug("hi there") ## lower threshold and retry log_threshold(TRACE) -log_debug('hi there') +log_debug("hi there") ## multiple lines -log_info('ok {1:3} + {1:3} = {2*(1:3)}') +log_info("ok {1:3} + {1:3} = {2*(1:3)}") log_layout(layout_json()) -log_info('ok {1:3} + {1:3} = {2*(1:3)}') +log_info("ok {1:3} + {1:3} = {2*(1:3)}") ## note for the JSON output, glue is not automatically applied -log_info(glue::glue('ok {1:3} + {1:3} = {2*(1:3)}')) +log_info(glue::glue("ok {1:3} + {1:3} = {2*(1:3)}")) } } \seealso{ diff --git a/man/log_levels.Rd b/man/log_levels.Rd index 7b4efbb1..2a091f7e 100644 --- a/man/log_levels.Rd +++ b/man/log_levels.Rd @@ -30,7 +30,9 @@ DEBUG TRACE } \description{ -The standard Apache logj4 log levels plus a custom level for \code{SUCCESS}. For the full list of these log levels and suggested usage, check the below Details. +The standard Apache logj4 log levels plus a custom level for +\code{SUCCESS}. For the full list of these log levels and suggested +usage, check the below Details. } \details{ List of supported log levels: @@ -46,6 +48,7 @@ List of supported log levels: } } \references{ -\url{https://logging.apache.org/log4j/2.x/javadoc/log4j-api/org/apache/logging/log4j/Level.html}, \url{https://logging.apache.org/log4j/2.x/manual/customloglevels.html} +\url{https://logging.apache.org/log4j/2.x/javadoc/log4j-api/org/apache/logging/log4j/Level.html}, +\url{https://logging.apache.org/log4j/2.x/manual/customloglevels.html} } \keyword{datasets} diff --git a/man/log_messages.Rd b/man/log_messages.Rd index 8008328f..054387f1 100644 --- a/man/log_messages.Rd +++ b/man/log_messages.Rd @@ -7,11 +7,13 @@ log_messages() } \description{ -This function uses \code{trace} to add a \code{log_info} function call when \code{message} is called to log the informative messages with the \code{logger} layout and appender. +This function uses \code{trace} to add a \code{log_info} function call when +\code{message} is called to log the informative messages with the +\code{logger} layout and appender. } \examples{ \dontrun{ log_messages() -message('hi there') +message("hi there") } } diff --git a/man/log_separator.Rd b/man/log_separator.Rd index 10cfaa9b..5ac3d682 100644 --- a/man/log_separator.Rd +++ b/man/log_separator.Rd @@ -17,30 +17,40 @@ log_separator( \arguments{ \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} \item{separator}{character to be used as a separator} \item{width}{max width of message -- longer text will be wrapped into multiple lines} -\item{.logcall}{the logging call being evaluated (useful in formatters and layouts when you want to have access to the raw, unevaluated R expression)} +\item{.logcall}{the logging call being evaluated (useful in +formatters and layouts when you want to have access to the raw, +unevaluated R expression)} -\item{.topcall}{R expression from which the logging function was called (useful in formatters and layouts to extract the calling function's name or arguments)} +\item{.topcall}{R expression from which the logging function was +called (useful in formatters and layouts to extract the calling +function's name or arguments)} -\item{.topenv}{original frame of the \code{.topcall} calling function where the formatter function will be evaluated and that is used to look up the \code{namespace} as well via \code{logger:::top_env_name}} +\item{.topenv}{original frame of the \code{.topcall} calling function +where the formatter function will be evaluated and that is used +to look up the \code{namespace} as well via \code{logger:::top_env_name}} } \description{ Logs a long line to stand out from the console } \examples{ log_separator() -log_separator(ERROR, separator = '!', width = 60) -log_separator(ERROR, separator = '!', width = 100) -logger <- layout_glue_generator(format = '{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}') +log_separator(ERROR, separator = "!", width = 60) +log_separator(ERROR, separator = "!", width = 100) +logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") log_layout(logger) -log_separator(ERROR, separator = '!', width = 100) +log_separator(ERROR, separator = "!", width = 100) log_layout(layout_blank) -log_separator(ERROR, separator = '!', width = 80) +log_separator(ERROR, separator = "!", width = 80) } \seealso{ \code{\link[=log_with_separator]{log_with_separator()}} diff --git a/man/log_shiny_input_changes.Rd b/man/log_shiny_input_changes.Rd index f2509f1a..37058e3d 100644 --- a/man/log_shiny_input_changes.Rd +++ b/man/log_shiny_input_changes.Rd @@ -28,22 +28,20 @@ This is to be called in the \code{server} section of the Shiny app. library(shiny) ui <- bootstrapPage( - numericInput('mean', 'mean', 0), - numericInput('sd', 'sd', 1), - textInput('title', 'title', 'title'), - textInput('foo', 'This is not used at all, still gets logged', 'foo'), - passwordInput('password', 'Password not to be logged', 'secret'), - plotOutput('plot') + numericInput("mean", "mean", 0), + numericInput("sd", "sd", 1), + textInput("title", "title", "title"), + textInput("foo", "This is not used at all, still gets logged", "foo"), + passwordInput("password", "Password not to be logged", "secret"), + plotOutput("plot") ) server <- function(input, output) { + logger::log_shiny_input_changes(input, excluded_inputs = "password") - logger::log_shiny_input_changes(input, excluded_inputs = 'password') - - output$plot <- renderPlot({ - hist(rnorm(1e3, input$mean, input$sd), main = input$title) - }) - + output$plot <- renderPlot({ + hist(rnorm(1e3, input$mean, input$sd), main = input$title) + }) } shinyApp(ui = ui, server = server) diff --git a/man/log_threshold.Rd b/man/log_threshold.Rd index 2938ab2b..3385e945 100644 --- a/man/log_threshold.Rd +++ b/man/log_threshold.Rd @@ -35,7 +35,7 @@ log_info(1) log_warn(2) ## set the log level threshold in all namespaces to ERROR -log_threshold(ERROR, namespace = log_namespaces()) +log_threshold(ERROR, namespace = log_namespaces()) } } \seealso{ diff --git a/man/log_tictoc.Rd b/man/log_tictoc.Rd index 7b7c0789..4412aa55 100644 --- a/man/log_tictoc.Rd +++ b/man/log_tictoc.Rd @@ -18,13 +18,13 @@ Tic-toc logging } \examples{ \dontrun{ -log_tictoc('warming up') +log_tictoc("warming up") Sys.sleep(0.1) -log_tictoc('running') +log_tictoc("running") Sys.sleep(0.1) -log_tictoc('running') +log_tictoc("running") Sys.sleep(runif(1)) -log_tictoc('and running') +log_tictoc("and running") } } \author{ diff --git a/man/log_warnings.Rd b/man/log_warnings.Rd index 7a0e9895..ef6c8c44 100644 --- a/man/log_warnings.Rd +++ b/man/log_warnings.Rd @@ -10,11 +10,16 @@ log_warnings(muffle = getOption("logger_muffle_warnings", FALSE)) \item{muffle}{if TRUE, the warning is not shown after being logged} } \description{ -This function uses \code{trace} to add a \code{log_warn} function call when \code{warning} is called to log the warning messages with the \code{logger} layout and appender. +This function uses \code{trace} to add a \code{log_warn} function call when +\code{warning} is called to log the warning messages with the \code{logger} +layout and appender. } \examples{ \dontrun{ log_warnings() -for (i in 1:5) { Sys.sleep(runif(1)); warning(i) } +for (i in 1:5) { + Sys.sleep(runif(1)) + warning(i) +} } } diff --git a/man/log_with_separator.Rd b/man/log_with_separator.Rd index a77f429a..5d5c603e 100644 --- a/man/log_with_separator.Rd +++ b/man/log_with_separator.Rd @@ -13,11 +13,16 @@ log_with_separator( ) } \arguments{ -\item{...}{R objects that can be converted to a character vector via the active message formatter function} +\item{...}{R objects that can be converted to a character vector +via the active message formatter function} \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} -\item{namespace}{string referring to the \code{logger} environment / config to be used to override the target of the message record to be used instead of the default namespace, which is defined by the R package name from which the logger was called, and falls back to a common, global namespace.} +\item{namespace}{string referring to the \code{logger} environment / +config to be used to override the target of the message record to +be used instead of the default namespace, which is defined by the +R package name from which the logger was called, and falls back +to a common, global namespace.} \item{separator}{character to be used as a separator} @@ -27,22 +32,26 @@ log_with_separator( Logs a message in a very visible way } \examples{ -log_with_separator('An important message') -log_with_separator('Some critical KPI down!!!', separator = '$') -log_with_separator('This message is worth a {1e3} words') +log_with_separator("An important message") +log_with_separator("Some critical KPI down!!!", separator = "$") +log_with_separator("This message is worth a {1e3} words") log_with_separator(paste( - 'A very important message with a bunch of extra words that will', - 'eventually wrap into a multi-line message for our quite nice demo :wow:')) -log_with_separator(paste( - 'A very important message with a bunch of extra words that will', - 'eventually wrap into a multi-line message for our quite nice demo :wow:'), - width = 60) -log_with_separator('Boo!', level = FATAL) + "A very important message with a bunch of extra words that will", + "eventually wrap into a multi-line message for our quite nice demo :wow:" +)) +log_with_separator( + paste( + "A very important message with a bunch of extra words that will", + "eventually wrap into a multi-line message for our quite nice demo :wow:" + ), + width = 60 +) +log_with_separator("Boo!", level = FATAL) log_layout(layout_blank) -log_with_separator('Boo!', level = FATAL) -logger <- layout_glue_generator(format = '{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}') +log_with_separator("Boo!", level = FATAL) +logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") log_layout(logger) -log_with_separator('Boo!', level = FATAL, width = 120) +log_with_separator("Boo!", level = FATAL, width = 120) } \seealso{ \code{\link[=log_separator]{log_separator()}} diff --git a/man/logger.Rd b/man/logger.Rd index 64271ff1..1f87ce1b 100644 --- a/man/logger.Rd +++ b/man/logger.Rd @@ -9,38 +9,59 @@ logger(threshold, formatter, layout, appender) \arguments{ \item{threshold}{omit log messages below this \code{\link[=log_levels]{log_levels()}}} -\item{formatter}{function pre-processing the message of the log record when it's not wrapped in a \code{\link[=skip_formatter]{skip_formatter()}} call} +\item{formatter}{function pre-processing the message of the log +record when it's not wrapped in a \code{\link[=skip_formatter]{skip_formatter()}} call} -\item{layout}{function rendering the layout of the actual log record} +\item{layout}{function rendering the layout of the actual log +record} \item{appender}{function writing the log record} } \value{ -A function taking the log \code{level} to compare with the set threshold, all the \code{...} arguments passed to the formatter function, besides the standard \code{namespace}, \code{.logcall}, \code{.topcall} and \code{.topenv} arguments (see \code{\link[=log_level]{log_level()}} for more details). The function invisibly returns a list including the original \code{level}, \code{namespace}, all \code{...} transformed to a list as \code{params}, the log \code{message} (after calling the \code{formatter} function) and the log \code{record} (after calling the \code{layout} function), and a list of \code{handlers} with the \code{formatter}, \code{layout} and \code{appender} functions. +A function taking the log \code{level} to compare with the set +threshold, all the \code{...} arguments passed to the formatter +function, besides the standard \code{namespace}, \code{.logcall}, +\code{.topcall} and \code{.topenv} arguments (see \code{\link[=log_level]{log_level()}} for more +details). The function invisibly returns a list including the +original \code{level}, \code{namespace}, all \code{...} transformed to a list as +\code{params}, the log \code{message} (after calling the \code{formatter} +function) and the log \code{record} (after calling the \code{layout} +function), and a list of \code{handlers} with the \code{formatter}, +\code{layout} and \code{appender} functions. } \description{ -A logger consists of a log level \code{threshold}, a log message \code{formatter} function, a log record \code{layout} formatting function and the \code{appender} function deciding on the destination of the log record. For more details, see the package \code{README.md}. +A logger consists of a log level \code{threshold}, a log message +\code{formatter} function, a log record \code{layout} formatting function and +the \code{appender} function deciding on the destination of the log +record. For more details, see the package \code{README.md}. } \details{ By default, a general logger definition is created when loading the \code{logger} package, that uses \itemize{ \item \code{\link[=INFO]{INFO()}} (or as per the \code{LOGGER_LOG_LEVEL} environment variable override) as the log level threshold \item \code{\link[=layout_simple]{layout_simple()}} as the layout function showing the log level, timestamp and log message -\item \code{\link[=formatter_glue]{formatter_glue()}} (or \code{\link[=formatter_sprintf]{formatter_sprintf()}} if \pkg{glue} is not installed) as the default formatter function transforming the R objects to be logged to a character vector +\item \code{\link[=formatter_glue]{formatter_glue()}} (or \code{\link[=formatter_sprintf]{formatter_sprintf()}} if \pkg{glue} is not installed) as the +default formatter function transforming the R objects to be logged to a character vector \item \code{\link[=appender_console]{appender_console()}} as the default log record destination } } \note{ -It's quite unlikely that you need to call this function directly, but instead set the logger parameters and functions at \code{\link[=log_threshold]{log_threshold()}}, \code{\link[=log_formatter]{log_formatter()}}, \code{\link[=log_layout]{log_layout()}} and \code{\link[=log_appender]{log_appender()}} and then call \code{\link[=log_levels]{log_levels()}} and its derivatives, such as \code{\link[=log_info]{log_info()}} directly. +It's quite unlikely that you need to call this function +directly, but instead set the logger parameters and functions at +\code{\link[=log_threshold]{log_threshold()}}, \code{\link[=log_formatter]{log_formatter()}}, \code{\link[=log_layout]{log_layout()}} and +\code{\link[=log_appender]{log_appender()}} and then call \code{\link[=log_levels]{log_levels()}} and its +derivatives, such as \code{\link[=log_info]{log_info()}} directly. } \examples{ \dontrun{ do.call(logger, logger:::namespaces$global[[1]])(INFO, 42) -do.call(logger, logger:::namespaces$global[[1]])(INFO, '{pi}') +do.call(logger, logger:::namespaces$global[[1]])(INFO, "{pi}") x <- 42 -do.call(logger, logger:::namespaces$global[[1]])(INFO, '{x}^2 = {x^2}') +do.call(logger, logger:::namespaces$global[[1]])(INFO, "{x}^2 = {x^2}") } } \references{ -For more details, see the Anatomy of a Log Request vignette at \url{https://daroczig.github.io/logger/articles/anatomy.html}. +For more details, see the Anatomy of a Log Request +vignette at +\url{https://daroczig.github.io/logger/articles/anatomy.html}. } diff --git a/man/skip_formatter.Rd b/man/skip_formatter.Rd index 0b0cef69..aad7b4c7 100644 --- a/man/skip_formatter.Rd +++ b/man/skip_formatter.Rd @@ -7,13 +7,20 @@ skip_formatter(message, ...) } \arguments{ -\item{message}{character vector directly passed to the appender function in \code{\link[=logger]{logger()}}} +\item{message}{character vector directly passed to the appender +function in \code{\link[=logger]{logger()}}} \item{...}{should be never set} } \value{ -character vector with \code{skip_formatter} attribute set to \code{TRUE} +character vector with \code{skip_formatter} attribute set to +\code{TRUE} } \description{ -Adds the \code{skip_formatter} attribute to an object so that logger will skip calling the formatter function(s). This is useful if you want to preprocess the log message with a custom function instead of the active formatter function(s). Note that the \code{message} should be a string, and \code{skip_formatter} should be the only input for the logging function to make this work. +Adds the \code{skip_formatter} attribute to an object so that logger +will skip calling the formatter function(s). This is useful if you +want to preprocess the log message with a custom function instead +of the active formatter function(s). Note that the \code{message} should +be a string, and \code{skip_formatter} should be the only input for the +logging function to make this work. } diff --git a/man/with_log_threshold.Rd b/man/with_log_threshold.Rd index ef70902a..4bd2b597 100644 --- a/man/with_log_threshold.Rd +++ b/man/with_log_threshold.Rd @@ -26,15 +26,18 @@ Evaluate R expression with a temporarily updated log level threshold \examples{ \dontrun{ log_threshold(TRACE) -log_trace('Logging everything!') -x <- with_log_threshold({ - log_info('Now we are temporarily suppressing eg INFO messages') - log_warn('WARN') - log_debug('Debug messages are suppressed as well') - log_error('ERROR') - invisible(42) -}, threshold = WARN) +log_trace("Logging everything!") +x <- with_log_threshold( + { + log_info("Now we are temporarily suppressing eg INFO messages") + log_warn("WARN") + log_debug("Debug messages are suppressed as well") + log_error("ERROR") + invisible(42) + }, + threshold = WARN +) x -log_trace('DONE') +log_trace("DONE") } } diff --git a/tests/testthat/_snaps/CRANSKIP-helpers.md b/tests/testthat/_snaps/CRANSKIP-helpers.md new file mode 100644 index 00000000..d668e8ca --- /dev/null +++ b/tests/testthat/_snaps/CRANSKIP-helpers.md @@ -0,0 +1,15 @@ +# log with separator + + Code + log_with_separator(42) + Output + INFO =========================================================================== + INFO = 42 = + INFO =========================================================================== + Code + log_with_separator(42, separator = "|") + Output + INFO ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + INFO | 42 | + INFO ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + diff --git a/tests/testthat/_snaps/CRANSKIP-hooks.md b/tests/testthat/_snaps/CRANSKIP-hooks.md new file mode 100644 index 00000000..0eee2636 --- /dev/null +++ b/tests/testthat/_snaps/CRANSKIP-hooks.md @@ -0,0 +1,60 @@ +# log_messages + + Code + writeLines(eval_outside("log_messages()", "message(42)")) + Output + INFO 42 + +# log_warnings + + Code + writeLines(eval_outside("log_warnings(TRUE)", "warning(42)", "log(-1)")) + Output + WARN 42 + WARN NaNs produced + +# log_errors + + Code + writeLines(eval_outside("log_errors()", "stop(42)")) + Output + ERROR 42 + Code + writeLines(eval_outside("log_errors()", "foobar")) + Output + ERROR object 'foobar' not found + Code + writeLines(eval_outside("log_errors()", "f<-function(x) {42 * \"foobar\"}; f()")) + Output + ERROR non-numeric argument to binary operator + +# shiny input initialization is detected + + Code + writeLines(obs) + Output + INFO mock-session Default Shiny inputs initialized: {} + +# shiny input initialization is detected with different log-level + + Code + writeLines(obs) + Output + ERROR mock-session Default Shiny inputs initialized: {} + +# shiny input change is detected + + Code + writeLines(obs) + Output + INFO mock-session Default Shiny inputs initialized: {} + INFO mock-session Shiny input change detected in a: NULL -> 2 + +# shiny input change is logged with different level + + Code + writeLines(obs) + Output + ERROR mock-session Default Shiny inputs initialized: {} + ERROR mock-session Shiny input change detected in a: NULL -> 2 + diff --git a/tests/testthat/_snaps/CRANSKIP-logger-namespaces.md b/tests/testthat/_snaps/CRANSKIP-logger-namespaces.md new file mode 100644 index 00000000..223aaf98 --- /dev/null +++ b/tests/testthat/_snaps/CRANSKIP-logger-namespaces.md @@ -0,0 +1,26 @@ +# log_info() captures package info + + Code + logger_tester_function(INFO, "x = ") + Output + logger.tester INFO x = 0.0807501375675201 + Code + logger_info_tester_function("everything = ") + Output + logger.tester INFO everything = 42 + +# log_info() captures local info + + Code + log_info("foobar") + Output + logger / global / logger / eval / eval(expr, envir, enclos) + Code + f() + Output + logger / global / logger / f / f() + Code + g() + Output + logger / global / logger / f / f() + diff --git a/tests/testthat/_snaps/formatters.md b/tests/testthat/_snaps/formatters.md new file mode 100644 index 00000000..4f8811af --- /dev/null +++ b/tests/testthat/_snaps/formatters.md @@ -0,0 +1,16 @@ +# glue gives informative error if message contains curlies + + Code + log_info("hi{") + Condition + Error in `h()`: + ! `glue` failed in `formatter_glue` on: + + chr "hi{" + + Raw error message: + + Expecting '}' + + Please consider using another `log_formatter` or `skip_formatter` on strings with curly braces. + diff --git a/tests/testthat/_snaps/logger.md b/tests/testthat/_snaps/logger.md new file mode 100644 index 00000000..4607194d --- /dev/null +++ b/tests/testthat/_snaps/logger.md @@ -0,0 +1,23 @@ +# setters check inputs + + Code + log_appender(1) + Condition + Error in `log_appender()`: + ! `appender` must be a function + Code + log_formatter(1) + Condition + Error in `log_formatter()`: + ! `formatter` must be a function + Code + log_layout(1) + Condition + Error in `log_layout()`: + ! `layout` must be a function + Code + log_threshold("x") + Condition + Error in `validate_log_level()`: + ! Invalid log level + diff --git a/tests/testthat/_snaps/try.md b/tests/testthat/_snaps/try.md new file mode 100644 index 00000000..4790c13c --- /dev/null +++ b/tests/testthat/_snaps/try.md @@ -0,0 +1,8 @@ +# %except% logs errors and returns default value + + Code + out <- f() + Output + except / global / logger / f / f() + WARN Running '1' as 'FunDoesNotExist(1:10)' failed: 'could not find function "FunDoesNotExist"' + diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 0e600211..21f0b729 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -1,11 +1,9 @@ - local_test_logger <- function(threshold = INFO, - formatter = formatter_glue, - layout = layout_simple, - appender = appender_stdout, - namespace = "global", - frame = parent.frame()) { - + formatter = formatter_glue, + layout = layout_simple, + appender = appender_stdout, + namespace = "global", + frame = parent.frame()) { old <- namespaces[[namespace]] namespaces[[namespace]] <- list( diff --git a/tests/testthat/test-CRANSKIP-appenders.R b/tests/testthat/test-CRANSKIP-appenders.R index e37741f7..5d16ea0b 100644 --- a/tests/testthat/test-CRANSKIP-appenders.R +++ b/tests/testthat/test-CRANSKIP-appenders.R @@ -1,12 +1,12 @@ -test_that('async logging', { - t <- withr::local_tempfile() - local_test_logger( - layout = layout_blank, - appender = appender_async(appender_file(file = t)) - ) +test_that("async logging", { + t <- withr::local_tempfile() + local_test_logger( + layout = layout_blank, + appender = appender_async(appender_file(file = t)) + ) - for (i in 1:5) log_info(i) - Sys.sleep(0.25) - expect_equal(readLines(t)[1], '1') - expect_equal(length(readLines(t)), 5) + for (i in 1:5) log_info(i) + Sys.sleep(0.25) + expect_equal(readLines(t)[1], "1") + expect_equal(length(readLines(t)), 5) }) diff --git a/tests/testthat/test-CRANSKIP-helpers.R b/tests/testthat/test-CRANSKIP-helpers.R index fbe89b01..d727cf0d 100644 --- a/tests/testthat/test-CRANSKIP-helpers.R +++ b/tests/testthat/test-CRANSKIP-helpers.R @@ -1,22 +1,19 @@ library(logger) library(testthat) -test_that('tictoc', { - expect_match(capture.output(log_tictoc(), type = 'message'), 'timer tic 0 secs') - ## let time pass a bit - Sys.sleep(0.01) - expect_match(capture.output(log_tictoc(), type = 'message'), 'timer toc') - capture.output(expect_silent(log_tictoc()), type = 'message') +test_that("tictoc", { + expect_match(capture.output(log_tictoc(), type = "message"), "timer tic 0 secs") + ## let time pass a bit + Sys.sleep(0.01) + expect_match(capture.output(log_tictoc(), type = "message"), "timer toc") + capture.output(expect_silent(log_tictoc()), type = "message") }) -test_that('log with separator', { - expect_output( - cat(system("$R_HOME/bin/Rscript -e 'logger::log_with_separator(42)' 2>&1", intern = TRUE)), - '===') - expect_output( - cat(system("$R_HOME/bin/Rscript -e 'logger::log_with_separator(42)' 2>&1", intern = TRUE)), - '42') - expect_output( - cat(system("$R_HOME/bin/Rscript -e 'logger::log_with_separator(42, separator = \"|\")' 2>&1", intern = TRUE)), - '|||||') +test_that("log with separator", { + local_test_logger(layout = layout_glue_generator("{level} {msg}")) + + expect_snapshot({ + log_with_separator(42) + log_with_separator(42, separator = "|") + }) }) diff --git a/tests/testthat/test-CRANSKIP-hooks.R b/tests/testthat/test-CRANSKIP-hooks.R index cb13e26d..d2e66d31 100644 --- a/tests/testthat/test-CRANSKIP-hooks.R +++ b/tests/testthat/test-CRANSKIP-hooks.R @@ -1,71 +1,69 @@ -library(logger) -library(testthat) - -eval_outside <- function(expr) { - t <- tempfile() - on.exit(unlink(t)) - cat('library(logger); log_messages(); log_warnings(); log_errors();', file = t) - cat(expr, file = t, append = TRUE, sep = '\n') - paste( - suppressWarnings(system(paste('$R_HOME/bin/Rscript', t, '2>&1'), intern = TRUE)), - collapse = '\n') +eval_outside <- function(...) { + input <- normalizePath(withr::local_tempfile(lines = character()), winslash = "/") + output <- normalizePath(withr::local_tempfile(lines = character()), winslash = "/") + writeLines(con = input, c( + "library(logger)", + "log_layout(layout_glue_generator('{level} {msg}'))", + paste0("log_appender(appender_file('", output, "'))"), + ... + )) + path <- file.path(R.home("bin"), "Rscript") + if (Sys.info()[["sysname"]] == "Windows") { + path <- paste0(path, ".exe") + } + suppressWarnings(system2(path, input, stdout = TRUE, stderr = TRUE)) + readLines(output) } -test_that('log_messages', { - expect_match(eval_outside('message(42)'), 'INFO') - if (R.Version()$os == 'linux-gnu') { - expect_match(eval_outside('system("echo 42", invisible = TRUE)'), 'INFO') - } +test_that("log_messages", { + expect_snapshot({ + writeLines(eval_outside("log_messages()", "message(42)")) + }) }) -test_that('log_warnings', { - expect_match(eval_outside('warning(42)'), 'WARN') - if (R.Version()$major >= 4) { - expect_match(eval_outside('log(-1)'), 'WARN') - } +test_that("log_warnings", { + expect_snapshot({ + writeLines(eval_outside("log_warnings(TRUE)", "warning(42)", "log(-1)")) + }) }) -test_that('log_errors', { - expect_match(eval_outside('stop(42)'), 'ERROR') - if (R.Version()$major >= 4) { - expect_match(eval_outside('foobar'), 'ERROR') - expect_match(eval_outside('f<-function(x) {42 * "foobar"}; f()'), 'ERROR') - } +test_that("log_errors", { + expect_snapshot({ + writeLines(eval_outside("log_errors()", "stop(42)")) + writeLines(eval_outside("log_errors()", "foobar")) + writeLines(eval_outside("log_errors()", 'f<-function(x) {42 * "foobar"}; f()')) + }) }) -test_that('shiny input initialization is detected', { - obs <- - eval_outside(" +test_that("shiny input initialization is detected", { + obs <- + eval_outside(" .globals <- shiny:::.globals .globals$appState <- new.env(parent = emptyenv()) server <- function(input, output, session) { logger::log_shiny_input_changes(input) } shiny::testServer(server, {}) - " - ) - exp <- "INFO \\[[0-9: \\-]+\\] Default Shiny inputs initialized" - expect_match(obs, exp) + ") + expect_snapshot(writeLines(obs)) }) -test_that('shiny input initialization is detected with different log-level', { - obs <- - eval_outside(" +test_that("shiny input initialization is detected with different log-level", { + obs <- + eval_outside(" .globals <- shiny:::.globals .globals$appState <- new.env(parent = emptyenv()) server <- function(input, output, session) { logger::log_shiny_input_changes(input, level = logger::ERROR) } shiny::testServer(server, {}) - " - ) - exp <- "ERROR \\[[0-9: \\-]+\\] Default Shiny inputs initialized" - expect_match(obs, exp) + ") + expect_snapshot(writeLines(obs)) }) -test_that('shiny input change is detected', { - obs <- - eval_outside(" +test_that("shiny input change is detected", { + obs <- + eval_outside(" .globals <- shiny:::.globals .globals$appState <- new.env(parent = emptyenv()) server <- function(input, output, session) { @@ -75,15 +73,13 @@ test_that('shiny input change is detected', { shiny::testServer(server, { session$setInputs(a = 2) }) - " - ) - exp <- "INFO \\[[0-9: \\-]+\\] Shiny input change detected on a: NULL -> 2" - expect_match(obs, exp) + ") + expect_snapshot(writeLines(obs)) }) -test_that('shiny input change is logged with different level', { - obs <- - eval_outside(" +test_that("shiny input change is logged with different level", { + obs <- + eval_outside(" .globals <- shiny:::.globals .globals$appState <- new.env(parent = emptyenv()) server <- function(input, output, session) { @@ -93,8 +89,6 @@ test_that('shiny input change is logged with different level', { shiny::testServer(server, { session$setInputs(a = 2) }) - " - ) - exp <- "ERROR \\[[0-9: \\-]+\\] Shiny input change detected on a: NULL -> 2" - expect_match(obs, exp) + ") + expect_snapshot(writeLines(obs)) }) diff --git a/tests/testthat/test-CRANSKIP-logger-namespaces.R b/tests/testthat/test-CRANSKIP-logger-namespaces.R index 99a18e0f..d7fe14a3 100644 --- a/tests/testthat/test-CRANSKIP-logger-namespaces.R +++ b/tests/testthat/test-CRANSKIP-logger-namespaces.R @@ -1,95 +1,27 @@ -## save current settings so that we can reset later -layout <- log_layout() -appender <- log_appender() - -test_that('called from package', { - devtools::load_all(system.file('demo-packages/logger-tester-package', package = 'logger')) - local_test_logger(layout = layout_simple) - expect_output(logger_tester_function(INFO, 'x = '), 'INFO') - expect_output(logger_info_tester_function('everything = '), 'INFO') +test_that("log_info() captures package info", { + devtools::load_all( + system.file("demo-packages/logger-tester-package", package = "logger"), + quiet = TRUE + ) + withr::defer(devtools::unload("logger.tester")) + + local_test_logger(layout = layout_glue_generator("{ns} {level} {msg}")) + expect_snapshot({ + logger_tester_function(INFO, "x = ") + logger_info_tester_function("everything = ") + }) }) -rscript <- function(path) { - out <- system(paste('$R_HOME/bin/Rscript', path, '2>&1'), intern = TRUE) - setdiff(out, "NULL") -} - -test_that('namespace in a remote R session to avoid calling from testthat', { - - t <- tempfile() - cat(' - library(logger) - log_layout(layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}")) - log_info("foobar")', file = t) - expect_equal( - rscript(t), - 'global / global / R_GlobalEnv / NA / NA') - unlink(t) - - t <- tempfile() - cat(' - library(logger) - log_layout(layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}")) - f <- function() log_info("foobar") - f()', file = t) - expect_equal( - rscript(t), - 'global / global / R_GlobalEnv / f / f()') - unlink(t) - - t <- tempfile() - cat(' - library(logger) - log_layout(layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}")) - f <- function() log_info("foobar") - g <- function() f() - g()', file = t) - expect_equal( - rscript(t), - 'global / global / R_GlobalEnv / f / f()') - unlink(t) - - t <- tempfile() - cat(' - library(logger) - log_layout(layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}")) - f <- function() log_info("foobar") - g <- f - g()', file = t) - expect_equal( - rscript(t), - 'global / global / R_GlobalEnv / g / g()') - unlink(t) - - t <- tempfile() - cat(' - library(logger) - log_layout(layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}")) - log_appender(appender_stdout) - devtools::load_all(system.file("demo-packages/logger-tester-package", package = "logger"), quiet = TRUE) - logger_info_tester_function("foobar")', - file = t) - expect_equal( - rscript(t), - 'logger.tester / global / logger.tester / logger_info_tester_function / logger_info_tester_function("foobar")') - unlink(t) - - t <- tempfile() - cat(' - library(logger) - log_layout(layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}")) - devtools::load_all(system.file("demo-packages/logger-tester-package", package = "logger"), quiet = TRUE) - log_threshold(INFO, namespace = "logger.tester") - log_appender(appender_stdout, namespace = "logger.tester") - logger_info_tester_function("foobar")', - file = t) - expect_equal( - rscript(t), - 'logger.tester / logger.tester / logger.tester / logger_info_tester_function / logger_info_tester_function("foobar")') - unlink(t) - +test_that("log_info() captures local info", { + local_test_logger( + layout = layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}") + ) + f <- function() log_info("foobar") + g <- function() f() + + expect_snapshot({ + log_info("foobar") + f() + g() + }) }) - -## reset settings -log_layout(layout) -log_appender(appender) diff --git a/tests/testthat/test-appender.R b/tests/testthat/test-appender.R index 6037c311..99a3730c 100644 --- a/tests/testthat/test-appender.R +++ b/tests/testthat/test-appender.R @@ -1,62 +1,62 @@ -test_that('append to file', { - t <- withr::local_tempfile() - local_test_logger( - appender = appender_file(t), - layout = layout_glue_generator('{level} {msg}'), - threshold = TRACE - ) - log_info('foobar') - log_info('{1:2}') - expect_equal(length(readLines(t)), 3) - expect_equal(readLines(t)[1], 'INFO foobar') - expect_equal(readLines(t)[3], 'INFO 2') +test_that("append to file", { + t <- withr::local_tempfile() + local_test_logger( + appender = appender_file(t), + layout = layout_glue_generator("{level} {msg}"), + threshold = TRACE + ) + log_info("foobar") + log_info("{1:2}") + expect_equal(length(readLines(t)), 3) + expect_equal(readLines(t)[1], "INFO foobar") + expect_equal(readLines(t)[3], "INFO 2") }) -test_that('overwrite file', { - t <- withr::local_tempfile() - local_test_logger( - appender = appender_file(t, append = FALSE), - layout = layout_glue_generator('{level} {msg}'), - threshold = TRACE - ) - - log_info('foobar') - log_info('{1:2}') - expect_equal(length(readLines(t)), 2) - expect_equal(readLines(t), c('INFO 1', 'INFO 2')) - - log_info('42') - expect_equal(length(readLines(t)), 1) - expect_equal(readLines(t), 'INFO 42') +test_that("overwrite file", { + t <- withr::local_tempfile() + local_test_logger( + appender = appender_file(t, append = FALSE), + layout = layout_glue_generator("{level} {msg}"), + threshold = TRACE + ) + + log_info("foobar") + log_info("{1:2}") + expect_equal(length(readLines(t)), 2) + expect_equal(readLines(t), c("INFO 1", "INFO 2")) + + log_info("42") + expect_equal(length(readLines(t)), 1) + expect_equal(readLines(t), "INFO 42") }) -test_that('append to file + print to console', { - t <- withr::local_tempfile() - local_test_logger( - appender = appender_tee(t), - layout = layout_glue_generator('{level} {msg}'), - ) +test_that("append to file + print to console", { + t <- withr::local_tempfile() + local_test_logger( + appender = appender_tee(t), + layout = layout_glue_generator("{level} {msg}"), + ) - expect_equal(capture.output(log_info('foobar'), type = 'message'), 'INFO foobar') - capture.output(log_info('{1:2}'), type = 'message') - expect_equal(length(readLines(t)), 3) - expect_equal(readLines(t)[1], 'INFO foobar') + expect_equal(capture.output(log_info("foobar"), type = "message"), "INFO foobar") + capture.output(log_info("{1:2}"), type = "message") + expect_equal(length(readLines(t)), 3) + expect_equal(readLines(t)[1], "INFO foobar") }) -test_that('logrotate', { - t <- withr::local_tempdir() - f <- file.path(t, 'log') - local_test_logger( - appender = appender_file(f, max_lines = 2, max_files = 5L), - layout = layout_glue_generator('{msg}'), - threshold = TRACE - ) +test_that("logrotate", { + t <- withr::local_tempdir() + f <- file.path(t, "log") + local_test_logger( + appender = appender_file(f, max_lines = 2, max_files = 5L), + layout = layout_glue_generator("{msg}"), + threshold = TRACE + ) - for (i in 1:24) log_info(i) - expect_equal(length(readLines(f)), 2) - expect_equal(length(list.files(t)), 5) - expect_equal(readLines(f), c('23', '24')) - log_info('42') - expect_equal(length(readLines(f)), 1) - expect_equal(readLines(f), '42') + for (i in 1:24) log_info(i) + expect_equal(length(readLines(f)), 2) + expect_equal(length(list.files(t)), 5) + expect_equal(readLines(f), c("23", "24")) + log_info("42") + expect_equal(length(readLines(f)), 1) + expect_equal(readLines(f), "42") }) diff --git a/tests/testthat/test-eval.R b/tests/testthat/test-eval.R index 6f3e0461..8e101927 100644 --- a/tests/testthat/test-eval.R +++ b/tests/testthat/test-eval.R @@ -1,25 +1,27 @@ -test_that('single line', { - local_test_logger(layout = layout_glue_generator('{level} {msg}')) +test_that("single line", { + local_test_logger(layout = layout_glue_generator("{level} {msg}")) - expect_output(log_eval(4, INFO), sprintf("INFO %s => %s", shQuote(4), shQuote(4))) + expect_output(log_eval(4, INFO), sprintf("INFO %s => %s", shQuote(4), shQuote(4))) }) -test_that('multi line', { - local_test_logger(layout = layout_glue_generator('{level} {msg}')) +test_that("multi line", { + local_test_logger(layout = layout_glue_generator("{level} {msg}")) - expect_output(log_eval(4, INFO, multiline = TRUE), "Running expression") - expect_output(log_eval(4, INFO, multiline = TRUE), "Results:") - expect_output(log_eval(4, INFO, multiline = TRUE), "INFO 4") + expect_output(log_eval(4, INFO, multiline = TRUE), "Running expression") + expect_output(log_eval(4, INFO, multiline = TRUE), "Results:") + expect_output(log_eval(4, INFO, multiline = TRUE), "INFO 4") }) -test_that('invisible return', { - local_test_logger(layout = layout_glue_generator('{level} {msg}')) - expect_output(log_eval(require(logger), INFO), sprintf("INFO %s => %s", - shQuote('require\\(logger\\)'), - shQuote(TRUE))) +test_that("invisible return", { + local_test_logger(layout = layout_glue_generator("{level} {msg}")) + expect_output(log_eval(require(logger), INFO), sprintf( + "INFO %s => %s", + shQuote("require\\(logger\\)"), + shQuote(TRUE) + )) }) -test_that('lower log level', { - local_test_logger(TRACE, layout = layout_glue_generator('{level} {msg}')) - expect_output(log_eval(4), sprintf("TRACE %s => %s", shQuote(4), shQuote(4))) +test_that("lower log level", { + local_test_logger(TRACE, layout = layout_glue_generator("{level} {msg}")) + expect_output(log_eval(4), sprintf("TRACE %s => %s", shQuote(4), shQuote(4))) }) diff --git a/tests/testthat/test-formatters.R b/tests/testthat/test-formatters.R index cc95126a..929fbd67 100644 --- a/tests/testthat/test-formatters.R +++ b/tests/testthat/test-formatters.R @@ -1,164 +1,167 @@ everything <- 42 g <- function() { - log_info("Hi {everything}") + log_info("Hi {everything}") } f <- function() { - log_info("Hi %s", everything) + log_info("Hi %s", everything) } -test_that('glue works', { - local_test_logger(formatter = formatter_glue) - - expect_equal(formatter_glue("Hi"), "Hi") - expect_equal(formatter_glue(" Hi"), " Hi") - expect_equal(formatter_glue('1 + {1}'), '1 + 1') - expect_equal(formatter_glue('{1:2}'), as.character(1:2)) - expect_equal(formatter_glue('pi is {round(pi, 2)}'), 'pi is 3.14') - expect_equal(formatter_glue("Hi {42}"), "Hi 42") - expect_equal(formatter_glue("Hi {a}", a = 42), "Hi 42") - expect_equal(formatter_glue("Hi {everything}"), "Hi 42") - expect_equal(formatter_glue("Hi {1:2}"), paste("Hi", 1:2)) - - expect_output(do.call(logger, namespaces$global[[1]])(INFO, 42), '42') - expect_output(do.call(logger, namespaces$global[[1]])(INFO, "Hi {everything}"), '42') - - expect_output(log_info("Hi {everything}"), '42') - expect_output(log_warn("Hi {everything}"), '42') - expect_output(g(), '42') - - local_test_logger( - formatter = formatter_glue, - appender = appender_void, - ) - expect_error(formatter_glue('malformed {')) - expect_error(formatter_glue('malformed {{'), NA) - - ## disabled for https://github.com/atalv/azlogr/issues/35 - ## expect_warning(formatter_glue(NULL)) - ## expect_warning(log_info(NULL)) - ## expect_warning(log_info(a = 42, b = "foobar")) +test_that("glue works", { + local_test_logger(formatter = formatter_glue) + + expect_equal(formatter_glue("Hi"), "Hi") + expect_equal(formatter_glue(" Hi"), " Hi") + expect_equal(formatter_glue("1 + {1}"), "1 + 1") + expect_equal(formatter_glue("{1:2}"), as.character(1:2)) + expect_equal(formatter_glue("pi is {round(pi, 2)}"), "pi is 3.14") + expect_equal(formatter_glue("Hi {42}"), "Hi 42") + expect_equal(formatter_glue("Hi {a}", a = 42), "Hi 42") + expect_equal(formatter_glue("Hi {everything}"), "Hi 42") + expect_equal(formatter_glue("Hi {1:2}"), paste("Hi", 1:2)) + + expect_output(do.call(logger, namespaces$global[[1]])(INFO, 42), "42") + expect_output(do.call(logger, namespaces$global[[1]])(INFO, "Hi {everything}"), "42") + + expect_output(log_info("Hi {everything}"), "42") + expect_output(log_warn("Hi {everything}"), "42") + expect_output(g(), "42") + + local_test_logger( + formatter = formatter_glue, + appender = appender_void, + ) + expect_error(formatter_glue("malformed {")) + expect_error(formatter_glue("malformed {{"), NA) + + ## nolint start + ## disabled for https://github.com/atalv/azlogr/issues/35 + ## expect_warning(formatter_glue(NULL)) + ## expect_warning(log_info(NULL)) + ## expect_warning(log_info(a = 42, b = "foobar")) + ## nolint end }) -test_that('glue_safe works', { - local_test_logger(formatter = formatter_glue_safe) +test_that("glue gives informative error if message contains curlies", { + local_test_logger(formatter = formatter_glue) + expect_snapshot(log_info("hi{"), error = TRUE) +}) - expect_equal(formatter_glue_safe("Hi"), "Hi") - expect_equal(formatter_glue_safe(" Hi"), " Hi") - expect_equal(formatter_glue_safe("Hi {a}", a = 42), "Hi 42") - expect_equal(formatter_glue_safe("Hi {everything}"), "Hi 42") +test_that("glue_safe works", { + local_test_logger(formatter = formatter_glue_safe) - expect_output(log_info("Hi {everything}"), '42') - expect_output(log_warn("Hi {everything}"), '42') - expect_output(g(), '42') + expect_equal(formatter_glue_safe("Hi"), "Hi") + expect_equal(formatter_glue_safe(" Hi"), " Hi") + expect_equal(formatter_glue_safe("Hi {a}", a = 42), "Hi 42") + expect_equal(formatter_glue_safe("Hi {everything}"), "Hi 42") - expect_error(formatter_glue_safe("Hi {42}")) - expect_error(formatter_glue_safe('malformed {')) - expect_error(formatter_glue_safe('malformed {{'), NA) + expect_output(log_info("Hi {everything}"), "42") + expect_output(log_warn("Hi {everything}"), "42") + expect_output(g(), "42") + expect_error(formatter_glue_safe("Hi {42}")) + expect_error(formatter_glue_safe("malformed {")) + expect_error(formatter_glue_safe("malformed {{"), NA) }) -test_that('sprintf works', { - local_test_logger(formatter = formatter_sprintf) - - expect_equal(formatter_sprintf("Hi"), "Hi") - expect_equal(formatter_sprintf("Hi %s", 42), "Hi 42") - expect_equal(formatter_sprintf("Hi %s", everything), "Hi 42") - expect_equal(formatter_sprintf("Hi %s", 1:2), paste("Hi", 1:2)) - expect_equal(formatter_sprintf('1 + %s', 1), '1 + 1') - expect_equal(formatter_sprintf('=>%2i', 2), '=> 2') - expect_equal(formatter_sprintf('%s', 1:2), as.character(1:2)) - expect_equal(formatter_sprintf('pi is %s', round(pi, 2)), 'pi is 3.14') - expect_equal(formatter_sprintf('pi is %1.2f', pi), 'pi is 3.14') +test_that("sprintf works", { + local_test_logger(formatter = formatter_sprintf) - expect_error(formatter_sprintf('%s and %i', 1)) - expect_equal(formatter_sprintf('%s and %i', 1, 2), '1 and 2') + expect_equal(formatter_sprintf("Hi"), "Hi") + expect_equal(formatter_sprintf("Hi %s", 42), "Hi 42") + expect_equal(formatter_sprintf("Hi %s", everything), "Hi 42") + expect_equal(formatter_sprintf("Hi %s", 1:2), paste("Hi", 1:2)) + expect_equal(formatter_sprintf("1 + %s", 1), "1 + 1") + expect_equal(formatter_sprintf("=>%2i", 2), "=> 2") + expect_equal(formatter_sprintf("%s", 1:2), as.character(1:2)) + expect_equal(formatter_sprintf("pi is %s", round(pi, 2)), "pi is 3.14") + expect_equal(formatter_sprintf("pi is %1.2f", pi), "pi is 3.14") - expect_output(log_info("Hi %s", everything), '42') - expect_output(f(), '42') + expect_error(formatter_sprintf("%s and %i", 1)) + expect_equal(formatter_sprintf("%s and %i", 1, 2), "1 and 2") + expect_output(log_info("Hi %s", everything), "42") + expect_output(f(), "42") }) result <- c( - "Hi foo, did you know that 2*4=8?", - "Hi bar, did you know that 2*4=8?") - -test_that('glue+sprintf works', { - - expect_equal(formatter_glue_or_sprintf("Hi ", "{c('foo', 'bar')}, did you know that 2*4={2*4}?"), result) - expect_equal(formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4={2*4}?"), result) - expect_equal(formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4=%s?", 2*4), result) - expect_equal(formatter_glue_or_sprintf("Hi %s, did you know that 2*4={2*4}?", c('foo', 'bar')), result) - expect_equal(formatter_glue_or_sprintf("Hi %s, did you know that 2*4=%s?", c('foo', 'bar'), 2*4), result) - - expect_equal(formatter_glue_or_sprintf('%s and %i'), '%s and %i') - expect_equal(formatter_glue_or_sprintf('%s and %i', 1), '%s and %i') - expect_equal(formatter_glue_or_sprintf('fun{fun}'), 'fun{fun}') - - for (fn in c(formatter_sprintf, formatter_glue_or_sprintf)) { - local_test_logger(formatter = fn, appender = appender_void) - expect_error(log_info(character(0)), NA) - - local_test_logger(formatter = fn) - expect_output(log_info(character(0)), 'INFO') - } - + "Hi foo, did you know that 2*4=8?", + "Hi bar, did you know that 2*4=8?" +) + +test_that("glue+sprintf works", { + expect_equal(formatter_glue_or_sprintf("Hi ", "{c('foo', 'bar')}, did you know that 2*4={2*4}?"), result) + expect_equal(formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4={2*4}?"), result) + expect_equal(formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4=%s?", 2 * 4), result) + expect_equal(formatter_glue_or_sprintf("Hi %s, did you know that 2*4={2*4}?", c("foo", "bar")), result) + expect_equal(formatter_glue_or_sprintf("Hi %s, did you know that 2*4=%s?", c("foo", "bar"), 2 * 4), result) + + expect_equal(formatter_glue_or_sprintf("%s and %i"), "%s and %i") + expect_equal(formatter_glue_or_sprintf("%s and %i", 1), "%s and %i") + expect_equal(formatter_glue_or_sprintf("fun{fun}"), "fun{fun}") + + for (fn in c(formatter_sprintf, formatter_glue_or_sprintf)) { + local_test_logger(formatter = fn, appender = appender_void) + expect_error(log_info(character(0)), NA) + + local_test_logger(formatter = fn) + expect_output(log_info(character(0)), "INFO") + } }) -test_that('formatter_logging works', { - local_test_logger(formatter = formatter_logging) - - expect_output(log_info('42'), '42') - expect_output(log_info(42), '42') - expect_output(log_info(4+2), '4 \\+ 2') - expect_output(log_info(4+2), '6') - expect_output(log_info('foo %s', 'bar'), 'foo bar') - expect_output(log_info(12, 100+100, 2*2), '12') - expect_output(log_info(12, 100+100, 2*2), '100 \\+ 100') - expect_output(log_info(12, 100+100, 2*2), '200') - expect_output(log_info(12, 100+100, 2*2), '2 \\* 2') - expect_output(log_info(12, 100+100, 2*2), '4') - +test_that("formatter_logging works", { + local_test_logger(formatter = formatter_logging) + + expect_output(log_info("42"), "42") + expect_output(log_info(42), "42") + expect_output(log_info(4 + 2), "4 \\+ 2") + expect_output(log_info(4 + 2), "6") + expect_output(log_info("foo %s", "bar"), "foo bar") + expect_output(log_info(12, 100 + 100, 2 * 2), "12") + expect_output(log_info(12, 100 + 100, 2 * 2), "100 \\+ 100") + expect_output(log_info(12, 100 + 100, 2 * 2), "200") + expect_output(log_info(12, 100 + 100, 2 * 2), "2 \\* 2") + expect_output(log_info(12, 100 + 100, 2 * 2), "4") }) -test_that('special chars in the text work', { - expect_equal(formatter_glue('JSON: {jsonlite::toJSON(1:4)}'), 'JSON: [1,2,3,4]') - expect_equal(formatter_glue('JSON: {jsonlite::toJSON(iris[1:2, ], auto_unbox = TRUE)}'), 'JSON: [{"Sepal.Length":5.1,"Sepal.Width":3.5,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"},{"Sepal.Length":4.9,"Sepal.Width":3,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"}]') # nolint - +test_that("special chars in the text work", { + expect_equal(formatter_glue("JSON: {jsonlite::toJSON(1:4)}"), "JSON: [1,2,3,4]") + expect_equal(formatter_glue("JSON: {jsonlite::toJSON(iris[1:2, ], auto_unbox = TRUE)}"), 'JSON: [{"Sepal.Length":5.1,"Sepal.Width":3.5,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"},{"Sepal.Length":4.9,"Sepal.Width":3,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"}]') # nolint + local_test_logger() - expect_output(log_info('JSON: {jsonlite::toJSON(1:4)}'), '[1,2,3,4]') - expect_output(log_info('JSON: {jsonlite::toJSON(iris[1:2, ], auto_unbox = TRUE)}'), '[{"Sepal.Length":5.1,"Sepal.Width":3.5,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"},{"Sepal.Length":4.9,"Sepal.Width":3,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"}]') # nolint + expect_output(log_info("JSON: {jsonlite::toJSON(1:4)}"), "[1,2,3,4]") + expect_output(log_info("JSON: {jsonlite::toJSON(iris[1:2, ], auto_unbox = TRUE)}"), '[{"Sepal.Length":5.1,"Sepal.Width":3.5,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"},{"Sepal.Length":4.9,"Sepal.Width":3,"Petal.Length":1.4,"Petal.Width":0.2,"Species":"setosa"}]') # nolint }) -test_that('pander formatter', { - local_test_logger(formatter = formatter_pander) - # pander partially matches coef to coefficient - withr::local_options(warnPartialMatchDollar = FALSE) - - expect_output(log_info(42), '_42_') - expect_output(log_info('42'), '42') - expect_output(log_info(head(iris)), 'Sepal.Length') - expect_output(log_info(lm(hp ~ wt, mtcars)), 'Fitting linear model') +test_that("pander formatter", { + local_test_logger(formatter = formatter_pander) + # pander partially matches coef to coefficient + withr::local_options(warnPartialMatchDollar = FALSE) + + expect_output(log_info(42), "_42_") + expect_output(log_info("42"), "42") + expect_output(log_info(head(iris)), "Sepal.Length") + expect_output(log_info(lm(hp ~ wt, mtcars)), "Fitting linear model") }) ## cleanup rm(everything) rm(f) -test_that('paste formatter in actual logs', { - local_test_logger(formatter = formatter_paste) - expect_output(log_info('hi', 5), 'hi 5') +test_that("paste formatter in actual logs", { + local_test_logger(formatter = formatter_paste) + expect_output(log_info("hi", 5), "hi 5") }) -test_that('skip formatter', { - local_test_logger(formatter = formatter_glue) - expect_output(log_info(skip_formatter('hi {pi}')), 'hi \\{pi\\}') - expect_error(log_info(skip_formatter(mtcars))) - expect_error(log_info(skip_formatter('hi {x}', x = 4))) +test_that("skip formatter", { + local_test_logger(formatter = formatter_glue) + expect_output(log_info(skip_formatter("hi {pi}")), "hi \\{pi\\}") + expect_error(log_info(skip_formatter(mtcars))) + expect_error(log_info(skip_formatter("hi {x}", x = 4))) }) -test_that('skip formatter', { - local_test_logger(formatter = formatter_json) - expect_output(log_info(skip_formatter('hi {pi}')), 'hi \\{pi\\}') - expect_output(log_info(x = 1), '\\{"x":1\\}') +test_that("skip formatter", { + local_test_logger(formatter = formatter_json) + expect_output(log_info(skip_formatter("hi {pi}")), "hi \\{pi\\}") + expect_output(log_info(x = 1), '\\{"x":1\\}') }) diff --git a/tests/testthat/test-helpers.R b/tests/testthat/test-helpers.R index 06cd2e5c..59f88091 100644 --- a/tests/testthat/test-helpers.R +++ b/tests/testthat/test-helpers.R @@ -1,28 +1,28 @@ -test_that('separator', { - local_test_logger(layout = layout_blank) - expect_output(log_separator(), '={80,80}') - - local_test_logger() - expect_output(log_separator(separator = '-'), '---') - expect_output(log_separator(), 'INFO') - expect_output(log_separator(WARN), 'WARN') +test_that("separator", { + local_test_logger(layout = layout_blank) + expect_output(log_separator(), "={80,80}") + + local_test_logger() + expect_output(log_separator(separator = "-"), "---") + expect_output(log_separator(), "INFO") + expect_output(log_separator(WARN), "WARN") }) -test_that('tictoc', { - local_test_logger() - expect_output(log_tictoc(), 'timer') +test_that("tictoc", { + local_test_logger() + expect_output(log_tictoc(), "timer") }) -test_that('log with separator', { - local_test_logger() - expect_output(log_with_separator(42), '===') - expect_output(log_with_separator('Boo!', level = FATAL, width = 120), width = 120) +test_that("log with separator", { + local_test_logger() + expect_output(log_with_separator(42), "===") + expect_output(log_with_separator("Boo!", level = FATAL, width = 120), width = 120) }) -test_that('log failure', { - local_test_logger() - expect_output(log_failure("foobar"), NA) - expect_output(try(log_failure(foobar), silent = TRUE), 'ERROR.*foobar') - expect_error(log_failure('foobar'), NA) - expect_match(capture.output(expect_error(log_failure(foobar))), 'not found') +test_that("log failure", { + local_test_logger() + expect_output(log_failure("foobar"), NA) + expect_output(try(log_failure(foobar), silent = TRUE), "ERROR.*foobar") + expect_error(log_failure("foobar"), NA) + expect_match(capture.output(expect_error(log_failure(foobar))), "not found") }) diff --git a/tests/testthat/test-layout.R b/tests/testthat/test-layout.R index 1e799614..3089b188 100644 --- a/tests/testthat/test-layout.R +++ b/tests/testthat/test-layout.R @@ -1,58 +1,71 @@ -test_that('blank layout', { - local_test_logger(layout = layout_blank) - expect_output(log_info('foobar'), 'foobar') - expect_equal(capture.output(log_info('foobar')), 'foobar') +test_that("blank layout", { + local_test_logger(layout = layout_blank) + expect_output(log_info("foobar"), "foobar") + expect_equal(capture.output(log_info("foobar")), "foobar") }) -test_that('colorized layout', { - local_test_logger(layout = layout_glue_colors) - expect_output(log_info('foobar'), 'INFO') - expect_output(log_info('foobar'), 'foobar') - expect_output(log_error('foobar'), 'ERROR') - expect_output(log_error('foobar'), 'foobar') +test_that("colorized layout", { + local_test_logger(layout = layout_glue_colors) + expect_output(log_info("foobar"), "INFO") + expect_output(log_info("foobar"), "foobar") + expect_output(log_error("foobar"), "ERROR") + expect_output(log_error("foobar"), "foobar") }) -test_that('metavars', { - local_test_logger(layout = layout_glue_generator('{level} {ans} {fn} {msg}')) - expect_output((function(){log_info(42)})(), 'INFO') - expect_output((function(){log_warn(42)})(), 'WARN') - expect_output((function(){log_info(42)})(), 'log_info') - - local_test_logger(layout = layout_glue_generator('{fn}')) - expect_output({fun42<-function(){log_info(42)};fun42();rm(fun42)}, 'fun42') -}) +test_that("metavars", { + local_test_logger(layout = layout_glue_generator("{level} {ans} {fn} {msg}")) + expect_output((function() { + log_info(42) + })(), "INFO") + expect_output((function() { + log_warn(42) + })(), "WARN") + expect_output((function() { + log_info(42) + })(), "log_info") -test_that('JSON layout', { - local_test_logger(layout = layout_json()) - - expect_equal(jsonlite::fromJSON(capture.output(log_info('foobar')))$level, 'INFO') - expect_equal(jsonlite::fromJSON(capture.output(log_info('foobar')))$msg, 'foobar') + local_test_logger(layout = layout_glue_generator("{fn}")) + expect_output( + { + fun42 <- function() { + log_info(42) + } + fun42() + rm(fun42) + }, + "fun42" + ) }) -test_that('JSON parser layout', { - local_test_logger(layout = layout_json_parser(fields = c())) - expect_output(log_info(skip_formatter('{"x": 4}')), '\\{"x":4\\}') - expect_equal(capture.output(log_info(skip_formatter('{"x": 4}'))), '{"x":4}') -}) +test_that("JSON layout", { + local_test_logger(layout = layout_json()) -test_that('must throw errors', { + expect_equal(jsonlite::fromJSON(capture.output(log_info("foobar")))$level, "INFO") + expect_equal(jsonlite::fromJSON(capture.output(log_info("foobar")))$msg, "foobar") +}) - expect_error(layout_simple(FOOBAR)) - expect_error(layout_simple(42)) - expect_error(layout_simple(msg = 'foobar')) +test_that("JSON parser layout", { + local_test_logger(layout = layout_json_parser(fields = c())) + expect_output(log_info(skip_formatter('{"x": 4}')), '\\{"x":4\\}') + expect_equal(capture.output(log_info(skip_formatter('{"x": 4}'))), '{"x":4}') +}) - expect_error(layout_glue(FOOBAR)) - expect_error(layout_glue(42)) - expect_error(layout_glue(msg = 'foobar')) - expect_error(layout_glue(level = 53, msg = 'foobar')) +test_that("must throw errors", { + expect_error(layout_simple(FOOBAR)) + expect_error(layout_simple(42)) + expect_error(layout_simple(msg = "foobar")) + expect_error(layout_glue(FOOBAR)) + expect_error(layout_glue(42)) + expect_error(layout_glue(msg = "foobar")) + expect_error(layout_glue(level = 53, msg = "foobar")) }) -test_that('logging layout', { - local_test_logger(layout = layout_logging) - expect_output(log_level(INFO, 'foo', namespace = 'bar'), 'INFO:bar:foo') - expect_output(log_info('foobar'), 'INFO') - expect_output(log_info('foo', namespace = 'bar'), 'foo') - expect_output(log_info('foo', namespace = 'bar'), 'bar') - expect_output(log_info('foo', namespace = 'bar'), 'INFO:bar:foo') +test_that("logging layout", { + local_test_logger(layout = layout_logging) + expect_output(log_level(INFO, "foo", namespace = "bar"), "INFO:bar:foo") + expect_output(log_info("foobar"), "INFO") + expect_output(log_info("foo", namespace = "bar"), "foo") + expect_output(log_info("foo", namespace = "bar"), "bar") + expect_output(log_info("foo", namespace = "bar"), "INFO:bar:foo") }) diff --git a/tests/testthat/test-logger.R b/tests/testthat/test-logger.R index 6433046f..5e3a8a7e 100644 --- a/tests/testthat/test-logger.R +++ b/tests/testthat/test-logger.R @@ -1,149 +1,158 @@ -test_that('log levels', { - local_test_logger(WARN) - - expect_output(log_fatal('foo'), 'FATAL.*foo') - expect_output(log_error('foo'), 'ERROR.*foo') - expect_output(log_warn('foo'), 'WARN.*foo') - expect_output(log_success('foo'), NA) - expect_output(log_info('foo'), NA) - expect_output(log_debug('foo'), NA) - expect_output(log_trace('foo'), NA) - expect_output(log_level('ERROR', 'foo'), 'ERROR.*foo') - expect_output(log_level(ERROR, 'foo'), 'ERROR.*foo') - expect_output(log_level(as.loglevel(ERROR), 'foo'), 'ERROR.*foo') - expect_output(log_level(as.loglevel('ERROR'), 'foo'), 'ERROR.*foo') - expect_output(log_level(as.loglevel(200L), 'foo'), 'ERROR.*foo') - expect_output(log_level('TRACE', 'foo'), NA) - expect_output(log_level(TRACE, 'foo'), NA) - expect_output(log_level(as.loglevel(TRACE), 'foo'), NA) - expect_output(log_level(as.loglevel('TRACE'), 'foo'), NA) - expect_output(log_level(as.loglevel(600L), 'foo'), NA) +test_that("log levels", { + local_test_logger(WARN) + + expect_output(log_fatal("foo"), "FATAL.*foo") + expect_output(log_error("foo"), "ERROR.*foo") + expect_output(log_warn("foo"), "WARN.*foo") + expect_output(log_success("foo"), NA) + expect_output(log_info("foo"), NA) + expect_output(log_debug("foo"), NA) + expect_output(log_trace("foo"), NA) + expect_output(log_level("ERROR", "foo"), "ERROR.*foo") + expect_output(log_level(ERROR, "foo"), "ERROR.*foo") + expect_output(log_level(as.loglevel(ERROR), "foo"), "ERROR.*foo") + expect_output(log_level(as.loglevel("ERROR"), "foo"), "ERROR.*foo") + expect_output(log_level(as.loglevel(200L), "foo"), "ERROR.*foo") + expect_output(log_level("TRACE", "foo"), NA) + expect_output(log_level(TRACE, "foo"), NA) + expect_output(log_level(as.loglevel(TRACE), "foo"), NA) + expect_output(log_level(as.loglevel("TRACE"), "foo"), NA) + expect_output(log_level(as.loglevel(600L), "foo"), NA) }) -test_that('log levels - OFF', { - local_test_logger(OFF) - expect_output(log_fatal('foo'), NA) - expect_output(log_error('foo'), NA) - expect_output(log_warn('foo'), NA) - expect_output(log_success('foo'), NA) - expect_output(log_info('foo'), NA) - expect_output(log_debug('foo'), NA) - expect_output(log_trace('foo'), NA) +test_that("log levels - OFF", { + local_test_logger(OFF) + expect_output(log_fatal("foo"), NA) + expect_output(log_error("foo"), NA) + expect_output(log_warn("foo"), NA) + expect_output(log_success("foo"), NA) + expect_output(log_info("foo"), NA) + expect_output(log_debug("foo"), NA) + expect_output(log_trace("foo"), NA) }) -test_that('log thresholds', { - local_test_logger(TRACE) - expect_output(log_fatal('foo'), 'FATAL.*foo') - expect_output(log_error('foo'), 'ERROR.*foo') - expect_output(log_warn('foo'), 'WARN.*foo') - expect_output(log_success('foo'), 'SUCCESS.*foo') - expect_output(log_info('foo'), 'INFO.*foo') - expect_output(log_debug('foo'), 'DEBUG.*foo') - expect_output(log_trace('foo'), 'TRACE.*foo') +test_that("log thresholds", { + local_test_logger(TRACE) + expect_output(log_fatal("foo"), "FATAL.*foo") + expect_output(log_error("foo"), "ERROR.*foo") + expect_output(log_warn("foo"), "WARN.*foo") + expect_output(log_success("foo"), "SUCCESS.*foo") + expect_output(log_info("foo"), "INFO.*foo") + expect_output(log_debug("foo"), "DEBUG.*foo") + expect_output(log_trace("foo"), "TRACE.*foo") }) -test_that('with log thresholds', { - local_test_logger(WARN) - expect_output(with_log_threshold(log_fatal('foo'), threshold = TRACE), 'FATAL.*foo') - expect_output(with_log_threshold(log_error('foo'), threshold = TRACE), 'ERROR.*foo') - expect_output(with_log_threshold(log_error('foo'), threshold = FATAL), NA) - expect_output(with_log_threshold(log_error('foo'), threshold = INFO), 'ERROR.*foo') - expect_output(with_log_threshold(log_debug('foo'), threshold = INFO), NA) +test_that("with log thresholds", { + local_test_logger(WARN) + expect_output(with_log_threshold(log_fatal("foo"), threshold = TRACE), "FATAL.*foo") + expect_output(with_log_threshold(log_error("foo"), threshold = TRACE), "ERROR.*foo") + expect_output(with_log_threshold(log_error("foo"), threshold = FATAL), NA) + expect_output(with_log_threshold(log_error("foo"), threshold = INFO), "ERROR.*foo") + expect_output(with_log_threshold(log_debug("foo"), threshold = INFO), NA) }) -test_that('simple glue layout with no threshold', { - local_test_logger(TRACE, layout = layout_glue_generator('{level} {msg}')) +test_that("simple glue layout with no threshold", { + local_test_logger(TRACE, layout = layout_glue_generator("{level} {msg}")) - expect_equal(capture.output(log_fatal('foobar')), 'FATAL foobar') - expect_equal(capture.output(log_error('foobar')), 'ERROR foobar') - expect_equal(capture.output(log_warn('foobar')), 'WARN foobar') - expect_equal(capture.output(log_info('foobar')), 'INFO foobar') - expect_equal(capture.output(log_debug('foobar')), 'DEBUG foobar') - expect_equal(capture.output(log_trace('foobar')), 'TRACE foobar') + expect_equal(capture.output(log_fatal("foobar")), "FATAL foobar") + expect_equal(capture.output(log_error("foobar")), "ERROR foobar") + expect_equal(capture.output(log_warn("foobar")), "WARN foobar") + expect_equal(capture.output(log_info("foobar")), "INFO foobar") + expect_equal(capture.output(log_debug("foobar")), "DEBUG foobar") + expect_equal(capture.output(log_trace("foobar")), "TRACE foobar") }) -test_that('simple glue layout with threshold', { - local_test_logger(INFO, layout = layout_glue_generator('{level} {msg}')) - expect_equal(capture.output(log_fatal('foobar')), 'FATAL foobar') - expect_equal(capture.output(log_error('foobar')), 'ERROR foobar') - expect_equal(capture.output(log_warn('foobar')), 'WARN foobar') - expect_equal(capture.output(log_info('foobar')), 'INFO foobar') - expect_equal(capture.output(log_debug('foobar')), character()) - expect_equal(capture.output(log_trace('foobar')), character()) +test_that("simple glue layout with threshold", { + local_test_logger(INFO, layout = layout_glue_generator("{level} {msg}")) + expect_equal(capture.output(log_fatal("foobar")), "FATAL foobar") + expect_equal(capture.output(log_error("foobar")), "ERROR foobar") + expect_equal(capture.output(log_warn("foobar")), "WARN foobar") + expect_equal(capture.output(log_info("foobar")), "INFO foobar") + expect_equal(capture.output(log_debug("foobar")), character()) + expect_equal(capture.output(log_trace("foobar")), character()) }) -test_that('namespaces', { - local_test_logger(ERROR, namespace = 'custom', layout = layout_glue_generator('{level} {msg}')) - expect_output(log_fatal('foobar', namespace = 'custom'), 'FATAL foobar') - expect_output(log_error('foobar', namespace = 'custom'), 'ERROR foobar') - expect_output(log_info('foobar', namespace = 'custom'), NA) - expect_output(log_debug('foobar', namespace = 'custom'), NA) - - local_test_logger(INFO, namespace = 'custom', layout = layout_glue_generator('{level} {msg}')) - expect_output(log_info('foobar', namespace = 'custom'), 'INFO foobar') - expect_output(log_debug('foobar', namespace = 'custom'), NA) - - log_threshold(TRACE, namespace = log_namespaces()) - expect_output(log_debug('foobar', namespace = 'custom'), 'DEBUG foobar') +test_that("namespaces", { + local_test_logger(ERROR, namespace = "custom", layout = layout_glue_generator("{level} {msg}")) + expect_output(log_fatal("foobar", namespace = "custom"), "FATAL foobar") + expect_output(log_error("foobar", namespace = "custom"), "ERROR foobar") + expect_output(log_info("foobar", namespace = "custom"), NA) + expect_output(log_debug("foobar", namespace = "custom"), NA) + + local_test_logger(INFO, namespace = "custom", layout = layout_glue_generator("{level} {msg}")) + expect_output(log_info("foobar", namespace = "custom"), "INFO foobar") + expect_output(log_debug("foobar", namespace = "custom"), NA) + + log_threshold(TRACE, namespace = log_namespaces()) + expect_output(log_debug("foobar", namespace = "custom"), "DEBUG foobar") }) -test_that('simple glue layout with threshold directly calling log', { - local_test_logger(layout = layout_glue_generator('{level} {msg}')) - expect_equal(capture.output(log_level(FATAL, 'foobar')), 'FATAL foobar') - expect_equal(capture.output(log_level(ERROR, 'foobar')), 'ERROR foobar') - expect_equal(capture.output(log_level(WARN, 'foobar')), 'WARN foobar') - expect_equal(capture.output(log_level(INFO, 'foobar')), 'INFO foobar') - expect_equal(capture.output(log_level(DEBUG, 'foobar')), character()) - expect_equal(capture.output(log_level(TRACE, 'foobar')), character()) +test_that("simple glue layout with threshold directly calling log", { + local_test_logger(layout = layout_glue_generator("{level} {msg}")) + expect_equal(capture.output(log_level(FATAL, "foobar")), "FATAL foobar") + expect_equal(capture.output(log_level(ERROR, "foobar")), "ERROR foobar") + expect_equal(capture.output(log_level(WARN, "foobar")), "WARN foobar") + expect_equal(capture.output(log_level(INFO, "foobar")), "INFO foobar") + expect_equal(capture.output(log_level(DEBUG, "foobar")), character()) + expect_equal(capture.output(log_level(TRACE, "foobar")), character()) }) -test_that('built in variables: pid', { - local_test_logger(layout = layout_glue_generator('{pid}')) - expect_equal(capture.output(log_info('foobar')), as.character(Sys.getpid())) +test_that("built in variables: pid", { + local_test_logger(layout = layout_glue_generator("{pid}")) + expect_equal(capture.output(log_info("foobar")), as.character(Sys.getpid())) }) -test_that('built in variables: fn and call', { - local_test_logger(layout = layout_glue_generator('{fn} / {call}')) - f <- function() log_info('foobar') - expect_output(f(), 'f / f()') - g <- function() f() - expect_output(g(), 'f / f()') - g <- f - expect_output(g(), 'g / g()') +test_that("built in variables: fn and call", { + local_test_logger(layout = layout_glue_generator("{fn} / {call}")) + f <- function() log_info("foobar") + expect_output(f(), "f / f()") + g <- function() f() + expect_output(g(), "f / f()") + g <- f + expect_output(g(), "g / g()") +}) + +test_that("built in variables: namespace", { + local_test_logger(layout = layout_glue_generator("{ns}")) + expect_output(log_info("bar", namespace = "foo"), "foo") + + local_test_logger(layout = layout_glue_generator("{ans}")) + expect_output(log_info("bar", namespace = "foo"), "global") }) -test_that('built in variables: namespace', { - local_test_logger(layout = layout_glue_generator('{ns}')) - expect_output(log_info('bar', namespace = 'foo'), 'foo') - - local_test_logger(layout = layout_glue_generator('{ans}')) - expect_output(log_info('bar', namespace = 'foo'), 'global') +test_that("print.level", { + expect_equal(capture.output(print(INFO)), "Log level: INFO") }) -test_that('print.level', { - expect_equal(capture.output(print(INFO)), 'Log level: INFO') +test_that("config setter called from do.call", { + local_test_logger() + + t <- tempfile() + expect_error(do.call(log_appender, list(appender_file(t))), NA) + log_info(42) + expect_length(readLines(t), 1) + expect_error(do.call(log_threshold, list(ERROR)), NA) + log_info(42) + expect_length(readLines(t), 1) + expect_error(do.call(log_threshold, list(INFO)), NA) + log_info(42) + expect_length(readLines(t), 2) + expect_error(do.call(log_layout, list(formatter_paste)), NA) + log_info(42) + expect_length(readLines(t), 3) + unlink(t) }) -test_that('config setter called from do.call', { - local_test_logger() - - t <- tempfile() - expect_error(do.call(log_appender, list(appender_file(t))), NA) - log_info(42) - expect_length(readLines(t), 1) - expect_error(do.call(log_threshold, list(ERROR)), NA) - log_info(42) - expect_length(readLines(t), 1) - expect_error(do.call(log_threshold, list(INFO)), NA) - log_info(42) - expect_length(readLines(t), 2) - expect_error(do.call(log_layout, list(formatter_paste)), NA) - log_info(42) - expect_length(readLines(t), 3) - unlink(t) +test_that("providing log_level() args to wrappers diretly is OK", { + local_test_logger(WARN) + expect_silent(log_info("{Sepal.Length}", .topenv = iris)) }) -test_that('providing log_level() args to wrappers diretly is OK', { - local_test_logger(WARN) - expect_silent(log_info('{Sepal.Length}', .topenv = iris)) +test_that("setters check inputs", { + expect_snapshot(error = TRUE, { + log_appender(1) + log_formatter(1) + log_layout(1) + log_threshold("x") + }) }) diff --git a/tests/testthat/test-return.R b/tests/testthat/test-return.R index b596c17a..75082af8 100644 --- a/tests/testthat/test-return.R +++ b/tests/testthat/test-return.R @@ -1,14 +1,15 @@ glue_or_sprintf_result <- c( - "Hi foo, did you know that 2*4=8?", - "Hi bar, did you know that 2*4=8?") + "Hi foo, did you know that 2*4=8?", + "Hi bar, did you know that 2*4=8?" +) test_that("return value is formatted string", { local_test_logger(appender = appender_file(withr::local_tempfile())) log_formatter(formatter_glue) - expect_equal(log_info('pi is {round(pi, 2)}')[[1]]$message, 'pi is 3.14') - expect_match(log_info('pi is {round(pi, 2)}')[[1]]$record, 'INFO [[0-9: -]*] pi is 3.14') + expect_equal(log_info("pi is {round(pi, 2)}")[[1]]$message, "pi is 3.14") + expect_match(log_info("pi is {round(pi, 2)}")[[1]]$record, "INFO [[0-9: -]*] pi is 3.14") log_formatter(formatter_paste, index = 2) - expect_equal(log_info('pi is {round(pi, 2)}')[[1]]$message, 'pi is 3.14') - expect_equal(log_info('pi is {round(pi, 2)}')[[2]]$message, 'pi is {round(pi, 2)}') + expect_equal(log_info("pi is {round(pi, 2)}")[[1]]$message, "pi is 3.14") + expect_equal(log_info("pi is {round(pi, 2)}")[[2]]$message, "pi is {round(pi, 2)}") }) diff --git a/tests/testthat/test-try.R b/tests/testthat/test-try.R new file mode 100644 index 00000000..818c677c --- /dev/null +++ b/tests/testthat/test-try.R @@ -0,0 +1,14 @@ +test_that("%except% logs errors and returns default value", { + local_test_logger(layout = layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}\n{level} {msg}")) + + f <- function() { + FunDoesNotExist(1:10) %except% 1 + } + + expect_snapshot(out <- f()) + expect_equal(out, 1) +}) + +test_that("%except% returns value when no error", { + expect_equal(5 %except% 1, 5) +}) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 17357748..eedb76ad 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1,31 +1,25 @@ -test_that('fail_on_missing_package', { - expect_error(fail_on_missing_package('logger'), NA) - expect_error(fail_on_missing_package('logger', '9.9.9')) - expect_error(fail_on_missing_package('an.R.package-that-doesNotExists')) +test_that("fail_on_missing_package", { + expect_error(fail_on_missing_package("logger"), NA) + expect_error(fail_on_missing_package("logger", "9.9.9")) + expect_error(fail_on_missing_package("an.R.package-that-doesNotExists")) }) -test_that('except helper', { - local_test_logger() - expect_equal(FunDoesNotExist(1:10) %except% sum(1:10) / length(1:10), 5.5) - expect_output(FunDoesNotExist(1:10) %except% sum(1:10) / length(1:10), 'WARN') +test_that("validate_log_level", { + expect_equal(validate_log_level(ERROR), ERROR) + expect_equal(validate_log_level("ERROR"), ERROR) + expect_error(validate_log_level("FOOBAR"), "log level") }) -test_that('validate_log_level', { - expect_equal(validate_log_level(ERROR), ERROR) - expect_equal(validate_log_level('ERROR'), ERROR) - expect_error(validate_log_level('FOOBAR'), 'log level') -}) - -test_that('catch_base_log', { - expect_true(nchar(catch_base_log(ERROR, NA_character_)) == 28) - expect_true(nchar(catch_base_log(INFO, NA_character_)) == 27) - local_test_logger(layout = layout_blank) - expect_true(nchar(catch_base_log(INFO, NA_character_)) == 0) +test_that("catch_base_log", { + expect_true(nchar(catch_base_log(ERROR, NA_character_)) == 28) + expect_true(nchar(catch_base_log(INFO, NA_character_)) == 27) + local_test_logger(layout = layout_blank) + expect_true(nchar(catch_base_log(INFO, NA_character_)) == 0) - local_test_logger( - layout = layout_glue_generator(format = '{namespace}/{fn} {level}: {msg}'), - namespace = "TEMP" - ) - expect_true(nchar(catch_base_log(INFO, 'TEMP', .topcall = NA)) == 14) - expect_true(nchar(catch_base_log(INFO, 'TEMP', .topcall = call('5char'))) == 17) + local_test_logger( + layout = layout_glue_generator(format = "{namespace}/{fn} {level}: {msg}"), + namespace = "TEMP" + ) + expect_true(nchar(catch_base_log(INFO, "TEMP", .topcall = NA)) == 14) + expect_true(nchar(catch_base_log(INFO, "TEMP", .topcall = call("5char"))) == 17) }) diff --git a/vignettes/Intro.Rmd b/vignettes/Intro.Rmd index 39889db0..d75e05cb 100644 --- a/vignettes/Intro.Rmd +++ b/vignettes/Intro.Rmd @@ -14,13 +14,6 @@ knitr::opts_chunk$set( ) library(logger) -## backup settings -oldconf <- list( - threshold = log_threshold(), - layout = log_layout(), - formatter = log_formatter(), - appender = log_appender()) -## knitr not picking up stderr log_appender(appender_stdout) ``` @@ -30,12 +23,12 @@ For most cases, it's enough to load the package and use the functions with the ` ```{r} library(logger) -log_info('Loading data') +log_info("Loading data") data(mtcars) -log_info('The dataset includes {nrow(mtcars)} rows') +log_info("The dataset includes {nrow(mtcars)} rows") if (max(mtcars$hp) < 1000) { - log_warn('Oh, no! There are no cars with more than 1K horsepower in the dataset :/') - log_debug('The most powerful car is {rownames(mtcars)[which.max(mtcars$hp)]} with {max(mtcars$hp)} hp') + log_warn("Oh, no! There are no cars with more than 1K horsepower in the dataset :/") + log_debug("The most powerful car is {rownames(mtcars)[which.max(mtcars$hp)]} with {max(mtcars$hp)} hp") } ``` @@ -54,12 +47,12 @@ log_threshold(TRACE) The rerunning the above code chunk: ```{r} -log_info('Loading data') +log_info("Loading data") data(mtcars) -log_info('The dataset includes {nrow(mtcars)} rows') +log_info("The dataset includes {nrow(mtcars)} rows") if (max(mtcars$hp) < 1000) { - log_warn('Oh, no! There are no cars with more than 1K horsepower in the dataset :/') - log_debug('The most powerful car is {rownames(mtcars)[which.max(mtcars$hp)]} with {max(mtcars$hp)} hp') + log_warn("Oh, no! There are no cars with more than 1K horsepower in the dataset :/") + log_debug("The most powerful car is {rownames(mtcars)[which.max(mtcars$hp)]} with {max(mtcars$hp)} hp") } ``` @@ -77,10 +70,10 @@ str(y) Sometimes, it may be reasonable to log R objects as markdown, e.g. a smallish `data.frame` or `data.table`, e.g. `mtcars` or `iris`. Calling the formatter using `pander` instead of `glue` can help: ```{r knitr-pander-setup, include = FALSE} -ppo1 <- pander::panderOptions('knitr.auto.asis') -ppo2 <- pander::panderOptions('table.style') -pander::panderOptions('knitr.auto.asis', FALSE) -pander::panderOptions('table.style', 'simple') +ppo1 <- pander::panderOptions("knitr.auto.asis") +ppo2 <- pander::panderOptions("table.style") +pander::panderOptions("knitr.auto.asis", FALSE) +pander::panderOptions("table.style", "simple") ``` ```{r} @@ -89,16 +82,12 @@ log_info(head(iris)) ``` ```{r knitr-pander-revert, include = FALSE} -pander::panderOptions('knitr.auto.asis', ppo1) -pander::panderOptions('table.style', ppo2) +pander::panderOptions("knitr.auto.asis", ppo1) +pander::panderOptions("table.style", ppo2) ``` For more details, check the [function reference in the manual](https://daroczig.github.io/logger/reference/index.html), or start with the [The Anatomy of a Log Request](https://daroczig.github.io/logger/articles/anatomy.html) and [Customizing the Format and the Destination of a Log Record](https://daroczig.github.io/logger/articles/customize_logger.html) vignettes. ```{r cleanup, include = FALSE} -## restore settings -log_threshold(oldconf$threshold) -log_layout(oldconf$layout) -log_formatter(oldconf$formatter) -log_appender(oldconf$appender) +logger:::namespaces_reset() ``` diff --git a/vignettes/anatomy.Rmd b/vignettes/anatomy.Rmd index 3396ab73..92e0ad76 100644 --- a/vignettes/anatomy.Rmd +++ b/vignettes/anatomy.Rmd @@ -11,17 +11,10 @@ resource_files: ```{r setup, include = FALSE} knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>" + collapse = TRUE, + comment = "#>" ) library(logger) -## backup settings -oldconf <- list( - threshold = log_threshold(), - layout = log_layout(), - formatter = log_formatter(), - appender = log_appender()) -## knitr not picking up stderr log_appender(appender_stdout) ``` @@ -44,8 +37,8 @@ To make a successful log record, `logger` requires the below components: ```{r} - f <- function() get_logger_meta_variables(log_level = INFO) - f() +f <- function() get_logger_meta_variables(log_level = INFO) +f() ``` - a **logger definition** to process the log request, including @@ -53,17 +46,17 @@ To make a successful log record, `logger` requires the below components: - log level `threshold`, eg `INFO`, which defines the minimum log level required for actual logging -- all log requests with lower log level will be thrown away ```{r} - log_threshold() - ERROR <= INFO - log_error('Oops') +log_threshold() +ERROR <= INFO +log_error("Oops") ``` - `formatter` function, which takes R objects and converts those into actual log message(s) to be then passed to the `layout` function for the log record rendering -- such as `paste`, `sprintf`, `glue` or eg the below custom example: ```{r} - formatter <- function(...) paste(..., collapse = ' ', sep = ' ') - formatter(1:3, c('foo', 'bar')) +formatter <- function(...) paste(..., collapse = " ", sep = " ") +formatter(1:3, c("foo", "bar")) ``` - `layout` function, which takes log message(s) and further information on the log request (such as timestamp, hostname, username, calling function etc) to render the actual log records eg human-readable text, JSON etc @@ -79,8 +72,8 @@ To make a successful log record, `logger` requires the below components: - `appender` function, which takes fully-rendered log record(s) and delivers to somewhere, eg `stdout`, a file or a streaming service, eg ```{r} - appender <- function(line) cat(line, '\n') - appender('INFO [now] I am a log message') +appender <- function(line) cat(line, "\n") +appender("INFO [now] I am a log message") ``` Putting all these together (by explicitly setting the default config in the `global` namespace): @@ -89,17 +82,13 @@ Putting all these together (by explicitly setting the default config in the `glo log_threshold(INFO) log_formatter(formatter_glue) log_layout(layout_simple) -log_appender(appender_console) -log_debug('I am a low level log message that will not be printed with a high log level threshold') -log_warn('I am a higher level log message that is very likely to be printed') +log_appender(appender_stdout) +log_debug("I am a low level log message that will not be printed with a high log level threshold") +log_warn("I am a higher level log message that is very likely to be printed") ``` Note, that all `logger` definitions and requests are tied to a logging namespace, and one log request might trigger multiple `logger` definitions as well (stacking). Find more information on these in the [Customizing the format and destination of log records](https://daroczig.github.io/logger/articles/customize_logger.html) vignette. ```{r cleanup, include = FALSE} -## restore settings -log_threshold(oldconf$threshold) -log_layout(oldconf$layout) -log_formatter(oldconf$formatter) -log_appender(oldconf$appender) +logger:::namespaces_reset() ``` diff --git a/vignettes/customize_logger.Rmd b/vignettes/customize_logger.Rmd index bfd93b50..9eb7cd98 100644 --- a/vignettes/customize_logger.Rmd +++ b/vignettes/customize_logger.Rmd @@ -9,11 +9,11 @@ vignette: > ```{r pkgchecks, echo = FALSE} ## check if other logger packages are available and exit if not -for (pkg in c('devtools', 'parallel')) { - if (!requireNamespace(pkg, quietly = TRUE)) { - warning(paste(pkg, 'package not available, so cannot build this vignette')) - knitr::knit_exit() - } +for (pkg in c("devtools", "parallel")) { + if (!requireNamespace(pkg, quietly = TRUE)) { + warning(paste(pkg, "package not available, so cannot build this vignette")) + knitr::knit_exit() + } } ``` @@ -24,13 +24,6 @@ knitr::opts_chunk$set( ) library(logger) -## backup settings -oldconf <- list( - threshold = log_threshold(), - layout = log_layout(), - formatter = log_formatter(), - appender = log_appender()) -## knitr not picking up stderr log_appender(appender_stdout) ``` @@ -41,11 +34,11 @@ In this vignette I suppose that you are already familiar with [The Anatomy of a `logger` mostly relies on and uses the default `log4j` log levels and supports suppressing log messages with a lower log level compared to the currently set threshold in the logging namespace: ```{r} -log_info('Hi, there!') -log_debug('How are you doing today?') +log_info("Hi, there!") +log_debug("How are you doing today?") log_threshold() log_threshold(TRACE) -log_debug('How are you doing today?') +log_debug("How are you doing today?") ``` So the `?log_threshold` function can both get and set the log level threshold for all future log requests. @@ -55,33 +48,36 @@ For the full list of all supported log levels and so thus the possible log level If you want to define the log level in a programmatic way, check out `?log_level`, eg ```{r} -log_level(INFO, 'Hi, there!') +log_level(INFO, "Hi, there!") ``` To temporarily update the log level threshold, you may also find the `?with_log_threshold` function useful: ```{r} log_threshold(INFO) -log_debug('pst, can you hear me?') -log_info('no') - -with_log_threshold(log_debug('pst, can you hear me?'), threshold = TRACE) -log_info('yes') - -with_log_threshold({ - log_debug('pst, can you hear me?') - log_info('yes') -}, threshold = TRACE) +log_debug("pst, can you hear me?") +log_info("no") + +with_log_threshold(log_debug("pst, can you hear me?"), threshold = TRACE) +log_info("yes") + +with_log_threshold( + { + log_debug("pst, can you hear me?") + log_info("yes") + }, + threshold = TRACE +) ``` You can also define your own log level(s) if needed, for example introducing an extra level between `DEBUG` and `INFO`: ```{r} -FYI <- structure(450L, level = 'FYI', class = c('loglevel', 'integer')) +FYI <- structure(450L, level = "FYI", class = c("loglevel", "integer")) log_threshold(FYI) -log_debug('ping') -log_level(FYI, 'ping') -log_info('pong') +log_debug("ping") +log_level(FYI, "ping") +log_info("pong") ``` ## Log namespaces @@ -92,18 +88,18 @@ If you specify an unknown `namespace` in a log request, it will fall back to the ```{r} log_threshold(INFO) -log_trace('Hi, there!', namespace = 'kitchensink') -log_info('Hi, there!', namespace = 'kitchensink') +log_trace("Hi, there!", namespace = "kitchensink") +log_info("Hi, there!", namespace = "kitchensink") ``` But once you start customizing that namespace, it gets forked from the global settings and live on its own without modifying the original namespace: ```{r} -log_threshold(TRACE, namespace = 'kitchensink') -log_trace('Hi, there!', namespace = 'kitchensink') -log_info('Hi, there!', namespace = 'kitchensink') -log_trace('Hi, there!') +log_threshold(TRACE, namespace = "kitchensink") +log_trace("Hi, there!", namespace = "kitchensink") +log_info("Hi, there!", namespace = "kitchensink") +log_trace("Hi, there!") ``` ## Log message formatter functions @@ -114,8 +110,8 @@ By default, `logger` uses `glue` in the background: ```{r} log_formatter(formatter_glue) -log_info('There are {nrow(mtcars)} cars in the mtcars dataset') -log_info('2 + 2 = {2+2}') +log_info("There are {nrow(mtcars)} cars in the mtcars dataset") +log_info("2 + 2 = {2+2}") ``` If you don't like this syntax, or want to save a dependency, you can use other formatter functions as well, such as `?formatter_sprintf` (being the default in eg the [`logging` and `futile.logger` packages](https://daroczig.github.io/logger/articles/migration.html)) or `?formatter_paste`, or [write your own formatter function](https://daroczig.github.io/logger/articles/write_custom_extensions.html) converting R objects into string. @@ -126,8 +122,8 @@ By default, `?log_level` and its derivative functions (eg `?log_info`) will simp ```{r} log_info(42) -log_info('The answer is {42}') -log_info('The answers are {1:5}') +log_info("The answer is {42}") +log_info("The answers are {1:5}") ``` In the above example, first, `42` was converted to a string by the `?formatter_glue` message formatter, then the message was passed to the `?layout_simple` layout function to generate the actual log record. @@ -137,8 +133,8 @@ An example of another layout function writing the same log messages in JSON: ```{r} log_layout(layout_json()) log_info(42) -log_info('The answer is {42}') -log_info('The answers are {1:5}') +log_info("The answer is {42}") +log_info("The answers are {1:5}") ``` If you need colorized logs highlighting the important log messages, check out `?layout_glue_colors`, and for other formatter and layout functions, see the manual of the above mentioned functions that have references to all the other functions and generator functions bundled with the package. @@ -154,38 +150,38 @@ A quick example: * define custom logger: ```{r} - logger <- layout_glue_generator(format = '{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}') - log_layout(logger) +logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") +log_layout(logger) ``` * check what's being logged when called from the global environment: ```{r} - log_info('foo') +log_info("foo") ``` * check what's being logged when called from a custom function: ```{r} - f <- function() log_info('foo') - f() +f <- function() log_info("foo") +f() ``` * check what's being logged when called from a package: ```{r} - devtools::load_all(system.file('demo-packages/logger-tester-package', package = 'logger')) - logger_tester_function(INFO, 'hi from tester package') +devtools::load_all(system.file("demo-packages/logger-tester-package", package = "logger")) +logger_tester_function(INFO, "hi from tester package") ``` * suppress messages in a namespace: ```{r} - log_threshold(namespace = 'logger.tester') - log_threshold(WARN, namespace = 'logger.tester') - logger_tester_function(INFO, 'hi from tester package') - logger_tester_function(WARN, 'hi from tester package') - log_info('I am still working in the global namespace') +log_threshold(namespace = "logger.tester") +log_threshold(WARN, namespace = "logger.tester") +logger_tester_function(INFO, "hi from tester package") +logger_tester_function(WARN, "hi from tester package") +log_info("I am still working in the global namespace") ``` Another example of making use of the generator function is to update the layout to include the Process ID that might be very useful eg when forking, see for example the below code chunk still using the above defined log layout: @@ -246,7 +242,7 @@ To write to a logfile instead, use the `?appender_file` generator function, that ```{r} t <- tempfile() log_appender(appender_file(t)) -log_info('where is this message going?') +log_info("where is this message going?") log_appender() readLines(t) unlink(t) @@ -272,7 +268,7 @@ You may find `?appender_tee` also useful, that writes the log messages to both ` ```{r} ## reset appender -log_appender(appender_console) +log_appender(appender_stdout) ``` And the are many other appender functions bundled with `logger` as well, eg some writing to Syslog, Telegram, Pushbullet, a database table or an Amazon Kinesis stream -- even doing that asynchronously via `appender_async` -- see [Simple Benchmarks on Performance](https://daroczig.github.io/logger/articles/performance.html) for more details. @@ -296,17 +292,13 @@ t <- tempfile() log_appender(appender_file(t), index = 2) ## test both loggers -log_info('info msg') -log_debug('info msg') +log_info("info msg") +log_debug("info msg") readLines(t) unlink(t) ``` ```{r cleanup, include = FALSE} -## restore settings -log_threshold(oldconf$threshold) -log_layout(oldconf$layout) -log_formatter(oldconf$formatter) -log_appender(oldconf$appender) +logger:::namespaces_reset() ``` diff --git a/vignettes/migration.Rmd b/vignettes/migration.Rmd index dd5c47bb..325c04c9 100644 --- a/vignettes/migration.Rmd +++ b/vignettes/migration.Rmd @@ -23,11 +23,11 @@ div#tocnav ul.nav li ul { ```{r pkgchecks, echo = FALSE} ## check if other logger packages are available and exit if not -for (pkg in c('futile.logger', 'logging', 'log4r')) { - if (!requireNamespace(pkg, quietly = TRUE)) { - warning(paste(pkg, 'package not available, so cannot build this vignette')) - knitr::knit_exit() - } +for (pkg in c("futile.logger", "logging", "log4r")) { + if (!requireNamespace(pkg, quietly = TRUE)) { + warning(paste(pkg, "package not available, so cannot build this vignette")) + knitr::knit_exit() + } } ``` @@ -39,13 +39,6 @@ knitr::opts_chunk$set( ## load the main package first library(logger) -## backup settings -oldconf <- list( - threshold = log_threshold(), - layout = log_layout(), - formatter = log_formatter(), - appender = log_appender()) -## knitr not picking up stderr log_appender(appender_stdout) ``` @@ -76,14 +69,14 @@ The most important change is that function names are by snake_case in `logger`,
futile.logger ```{r} -flog.info('hi there') -flog.warn('watch out') +flog.info("hi there") +flog.warn("watch out") ```
logger ```{r} -log_info('hi there') -log_warn('watch out') +log_info("hi there") +log_warn("watch out") ```
@@ -100,14 +93,14 @@ Changing layouts is easy in both package, as you simply pass a layout function:
futile.logger ```{r} flog.layout(layout.json) -flog.info('hi again') +flog.info("hi again") ```
logger ```{r} log_layout(layout_json()) -log_info('hi again') +log_info("hi again") ```
@@ -124,20 +117,20 @@ By default, `futile.logger` uses an `sprintf` formatter, while `logger` passes t
futile.logger ```{r} -flog.info('hi') -flog.info('hi %s', 84/2) -flog.info(paste('hi', 84/2)) -flog.info(glue::glue('hi {84/2}')) +flog.info("hi") +flog.info("hi %s", 84 / 2) +flog.info(paste("hi", 84 / 2)) +flog.info(glue::glue("hi {84/2}")) ```
logger ```{r} -log_info('hi') -log_info('hi {84/2}') +log_info("hi") +log_info("hi {84/2}") log_formatter(formatter_sprintf) -log_info('hi %s', 84/2) +log_info("hi %s", 84 / 2) log_formatter(formatter_paste) -log_info('hi', 84/2) +log_info("hi", 84 / 2) ```
@@ -170,7 +163,7 @@ log_appender(appender_tee(t)) ```{r echo=FALSE, results='hide'} flog.appender(appender.console) -log_appender(appender_console) +log_appender(appender_stdout) ``` ### Hierarchical logging and performance @@ -190,8 +183,8 @@ flog.info <- log_info flog.warn <- log_warn flog.error <- log_error -flog.info('Hello from logger in a futile.logger theme ...') -flog.warn('... where the default log message formatter is %s', 'sprintf') +flog.info("Hello from logger in a futile.logger theme ...") +flog.warn("... where the default log message formatter is %s", "sprintf") ``` ## logging @@ -211,7 +204,6 @@ basicConfig()
logger ```{r} library(logger) - ```
@@ -224,14 +216,14 @@ After initializing the logging engine, actual logging works similarly in the two
logging ```{r} -loginfo('hi there') -logwarn('watch out') +loginfo("hi there") +logwarn("watch out") ```
logger ```{r} -log_info('hi there') -log_warn('watch out') +log_info("hi there") +log_warn("watch out") ```
@@ -251,11 +243,13 @@ str(as.list(loglevels))
logger ```{r} -levels <- mget(ls( - envir = environment(logger), pattern = '^[A-Z]'), - envir = environment(logger)) +levels <- mget( + ls( + envir = environment(logger), pattern = "^[A-Z]" + ), + envir = environment(logger) +) str(levels[order(-as.numeric(levels))], give.attr = FALSE) - ```
@@ -269,7 +263,7 @@ Getting and setting the layout of the log record should happen up-front in both
logging ```{r} -getLogger()[['handlers']]$basic.stdout$formatter +getLogger()[["handlers"]]$basic.stdout$formatter ```
logger @@ -286,20 +280,20 @@ If you want to pass dynamic log messages to the log engines, you can do that via
logging ```{r} -loginfo('hi') -loginfo('hi %s', 84/2) -loginfo(paste('hi', 84/2)) -loginfo(glue::glue('hi {84/2}')) +loginfo("hi") +loginfo("hi %s", 84 / 2) +loginfo(paste("hi", 84 / 2)) +loginfo(glue::glue("hi {84/2}")) ```
logger ```{r} -log_info('hi') -log_info('hi {84/2}') +log_info("hi") +log_info("hi {84/2}") log_formatter(formatter_sprintf) -log_info('hi %s', 84/2) +log_info("hi %s", 84 / 2) log_formatter(formatter_paste) -log_info('hi', 84/2) +log_info("hi", 84 / 2) ```
@@ -307,11 +301,11 @@ For even better compatibility, there's also `?formatter_logging` that not only r ```{r} log_formatter(formatter_logging) -log_info('42') +log_info("42") log_info(42) -log_info(4+2) -log_info('foo %s', 'bar') -log_info(12, 1+1, 2 * 2) +log_info(4 + 2) +log_info("foo %s", "bar") +log_info(12, 1 + 1, 2 * 2) ``` ```{r echo=FALSE, results='hide'} @@ -357,8 +351,8 @@ loginfo <- log_info logwarn <- log_warn logerror <- log_error -loginfo('Hello from logger in a logging theme ...') -logwarn('... where the default log message formatter is %s', 'sprintf', namespace = 'foobar') +loginfo("Hello from logger in a logging theme ...") +logwarn("... where the default log message formatter is %s", "sprintf", namespace = "foobar") ``` ## log4r @@ -389,14 +383,14 @@ While `logger` has a `log_` prefix for all logging functions, `log4r` has lowerc
log4r ```{r} -info(logger, 'hi there') -warn(logger, 'watch out') +info(logger, "hi there") +warn(logger, "watch out") ```
logger ```{r} -log_info('hi there') -log_warn('watch out') +log_info("hi there") +log_warn("watch out") ```
@@ -425,15 +419,8 @@ Creating objects is the `log4r` way of handling multiple log environments, while Sorry, no direct replacement for [`loggit`](https://cran.r-project.org/package=loggit) -- capturing `message`, `warning` and `stop` function messages, but it's on the [roadmap](https://github.com/daroczig/logger/issues/6) to provide helper functions to be used as message hooks feed `logger`. ```{r cleanup, include = FALSE} -## restore settings -log_threshold(oldconf$threshold) -log_layout(oldconf$layout) -log_formatter(oldconf$formatter) -log_appender(oldconf$appender) -``` - -```{r echo=FALSE, results='hide'} -detach('package:logger', unload = TRUE) -detach('package:futile.logger', unload = TRUE) -detach('package:logging', unload = TRUE) +logger:::namespaces_reset() +detach("package:logger", unload = TRUE) +detach("package:futile.logger", unload = TRUE) +detach("package:logging", unload = TRUE) ``` diff --git a/vignettes/migration.html b/vignettes/migration.html new file mode 100644 index 00000000..de52b543 --- /dev/null +++ b/vignettes/migration.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + +Migration Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +

In this vignette I suppose that you are already familiar with at +least one of the similar +logging R packages and you are looking for suggestions on how to +switch to logger. Before moving forward, please make sure +that you have read the Introduction +to logger, The +Anatomy of a Log Request and Customizing +the Format and the Destination of a Log Record vignettes for a +decent background on logger, and use this vignette as a +quick-reference sheet to help you migrate from another package.

+
+

futile.logger

+

The logger package has been very heavily inspired by futile.logger +and have been using it for many years, also opened multiple pull +requests to extend futile.logger before I decided to revamp +my ideas into a new R package – but there are still many common things +between futile.logger and logger.

+
+

Initialize

+

Both packages comes with a default log engine / config, so it’s +enough to load the packages and those are ready to be used right +away:

+
+

futile.logger

+
library(futile.logger)
+#> 
+#> Attaching package: 'futile.logger'
+#> The following objects are masked from 'package:logger':
+#> 
+#>     DEBUG, ERROR, FATAL, INFO, TRACE, WARN
+
+
+

logger

+
library(logger)
+
+
+
+

Logging functions

+

The most important change is that function names are by snake_case in +logger, while futile.logger uses dot.separated +expressions, and futile.logger prefixes function names by +flog while logger uses log for +that:

+
+

futile.logger

+
flog.info('hi there')
+#> INFO [2024-08-06 14:28:37] hi there
+flog.warn('watch out')
+#> WARN [2024-08-06 14:28:37] watch out
+
+
+

logger

+
log_info('hi there')
+#> INFO [2024-08-06 14:28:37] hi there
+log_warn('watch out')
+#> WARN [2024-08-06 14:28:37] watch out
+
+

As you can see above, the default layout of the messages is exactly +the same.

+
+
+

Log levels

+

Regarding log levels, futile.logger bundles the default +log4j levels (TRACE, DEBUG, +INFO, WARN, ERROR and +FATAL) that is extended by SUCCESS in +logger as sometimes it’s worth logging with a higher than +INFO level that something succeeded.

+
+
+

Log record layout

+

Changing layouts is easy in both package, as you simply pass a layout +function:

+
+

futile.logger

+
flog.layout(layout.json)
+#> NULL
+flog.info('hi again')
+#> {"level":"INFO","timestamp":"2024-08-06 14:28:37 -0500","message":"hi again","func":"rmarkdown::render"}
+
+
+

logger

+
log_layout(layout_json())
+
+log_info('hi again')
+#> {"time":"2024-08-06 14:28:37","level":"INFO","ns":"global","ans":"global","topenv":"R_GlobalEnv","fn":"eval","node":"Hadleys-MBP.attlocal.net","arch":"arm64","os_name":"Darwin","os_release":"23.5.0","os_version":"Darwin Kernel Version 23.5.0: Wed May  1 20:12:58 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T6000","pid":86631,"user":"hadleywickham","msg":"hi again"}
+
+

As you can see, logger provided a bit more information +about the log request compared to futile.logger, but it’s +easy to change the list of fields to be used in the JSON – see +?get_logger_meta_variables for a complete list of variable +names to be passed to ?layout_json. logger +also ships a lot more layouts, eg ?layout_glue_colors or +roll out your own via the ?layout_glue_generator factory +function.

+
+
+

Log message formatting

+

By default, futile.logger uses an sprintf +formatter, while logger passes the objects to be logged to +glue:

+
+

futile.logger

+
flog.info('hi')
+#> INFO [2024-08-06 14:28:37] hi
+flog.info('hi %s', 84/2)
+#> INFO [2024-08-06 14:28:37] hi 42
+flog.info(paste('hi', 84/2))
+#> INFO [2024-08-06 14:28:37] hi 42
+flog.info(glue::glue('hi {84/2}'))
+#> INFO [2024-08-06 14:28:37] hi 42
+
+
+

logger

+
log_info('hi')
+#> INFO [2024-08-06 14:28:37] hi
+log_info('hi {84/2}')
+#> INFO [2024-08-06 14:28:37] hi 42
+log_formatter(formatter_sprintf)
+log_info('hi %s', 84/2)
+#> INFO [2024-08-06 14:28:37] hi 42
+log_formatter(formatter_paste)
+log_info('hi', 84/2)
+#> INFO [2024-08-06 14:28:37] hi 42
+
+

It’s easy to change this default formatter in both packages: use +flog.layout handles this as well in +futile.logger, while the formatter is separated from the +layout function in logger, so check +?log_formatter instead. logger ships with a +bit more formatter functions, eg the default +?formatter_glue and ?formatter_glue_or_sprintf +that tries to combine the best from both words.

+
+
+

Log record destination

+

Setting the destination of the log records works similarly in both +packages, although he logger packages bundles a lot more +options:

+
+

logging

+
t <- tempfile()
+flog.appender(appender.file(t))
+#> NULL
+flog.appender(appender.tee(t))
+#> NULL
+
+
+

logger

+
t <- tempfile()
+log_appender(appender_file(t))
+log_appender(appender_tee(t))
+
+
+
+

Hierarchical logging and performance

+

Both packages support using different logging namespaces and stacking +loggers within the same namespace. Performance-wise, there’s +logger seems to be faster than futile.logger, +but for more details, check the Simple +Benchmarks on Performance vignette.

+
+
+

Using logger as a drop-in-replacement of +futile.logger

+

logger has no hard requirements, so it’s a very +lightweight alternative of futile.logger. Although the +function names are a bit different, and the message formatter also +differs, but with some simple tweaks, logger can become an +almost perfect drop-in-replacement of futile.logger:

+
library(logger)
+log_formatter(formatter_sprintf)
+flog.trace <- log_trace
+flog.debug <- log_debug
+flog.info <- log_info
+flog.warn <- log_warn
+flog.error <- log_error
+
+flog.info('Hello from logger in a futile.logger theme ...')
+flog.warn('... where the default log message formatter is %s', 'sprintf')
+
+
+
+

logging

+

The logging +package behaves very similarly to the Python logging module and so thus +being pretty Pythonic, while logger tries to accommodate +native R users’ expectations – so there are some minor nuances between +the usage of the two packages.

+
+

Initialize

+

In logging, you have to initialize a logger first via +addHandler or simply by calling basicConfig, +which is not required in logger as it already comes with a +default log config:

+
+

logging

+
library(logging)
+basicConfig()
+
+
+

logger

+
library(logger)
+
+
+
+

Logging functions

+

After initializing the logging engine, actual logging works similarly +in the two packages – with a bit different function names:

+
    +
  • although logging uses mostly camelCase function names +(eg basicConfig), but the logging functions are all +lowercase without any separator, such as loginfo or +logwarn
  • +
  • logger uses snake_case for the function names, such as +log_info and log_warn
  • +
+
+

logging

+
loginfo('hi there')
+#> 2024-08-06 14:28:37.693914 INFO::hi there
+logwarn('watch out')
+#> 2024-08-06 14:28:37.698499 WARNING::watch out
+
+
+

logger

+
log_info('hi there')
+log_warn('watch out')
+
+

As you can see above, the default layout of the log messages is +somewhat different:

+
    +
  • logging starts with the timestamp that is followed by +the log level, optional namespace and the message separated by +colons
  • +
  • logger starts with the log level, followed by the +timestamp between brackets and then the message
  • +
+
+
+

Log levels

+

For the available log levels in logging, check +?loglevels, and ?log_levels for the same in +logger:

+
+

logging

+
str(as.list(loglevels))
+#> List of 11
+#>  $ NOTSET  : num 0
+#>  $ FINEST  : num 1
+#>  $ FINER   : num 4
+#>  $ FINE    : num 7
+#>  $ DEBUG   : num 10
+#>  $ INFO    : num 20
+#>  $ WARNING : num 30
+#>  $ WARN    : num 30
+#>  $ ERROR   : num 40
+#>  $ CRITICAL: num 50
+#>  $ FATAL   : num 50
+
+
+

logger

+
levels <- mget(ls(
+    envir = environment(logger), pattern = '^[A-Z]'),
+    envir = environment(logger))
+str(levels[order(-as.numeric(levels))], give.attr = FALSE)
+#> List of 8
+#>  $ TRACE  : 'loglevel' int 600
+#>  $ DEBUG  : 'loglevel' int 500
+#>  $ INFO   : 'loglevel' int 400
+#>  $ SUCCESS: 'loglevel' int 350
+#>  $ WARN   : 'loglevel' int 300
+#>  $ ERROR  : 'loglevel' int 200
+#>  $ FATAL  : 'loglevel' int 100
+#>  $ OFF    : 'loglevel' int 0
+
+
+
+

Performance

+

Performance-wise, there’s no big difference between the two packages, +but for more details, check the Simple +Benchmarks on Performance vignette.

+
+
+

Log record layout

+

Getting and setting the layout of the log record should happen +up-front in both packages:

+
+

logging

+
getLogger()[['handlers']]$basic.stdout$formatter
+#> function (record) 
+#> {
+#>     msg <- trimws(record$msg)
+#>     text <- paste(record$timestamp, paste(record$levelname, record$logger, 
+#>         msg, sep = ":"))
+#>     return(text)
+#> }
+#> <bytecode: 0x1375de6b8>
+#> <environment: namespace:logging>
+
+
+

logger

+
log_layout()
+#> layout_simple
+
+

logger provides multiple configurable layouts to fit the +user’s need, eg easily show the calling function of the lof request, the +pid of the R process, name of the machine etc. or colorized +outputs. See Customizing +the Format and the Destination of a Log Record vignette for more +details.

+
+
+

Log message formatting

+

If you want to pass dynamic log messages to the log engines, you can +do that via the hard-coded sprintf in the +logging package, while you can set that on a namespaces +basis in logger, which is by default using +glue:

+
+

logging

+
loginfo('hi')
+#> 2024-08-06 14:28:37.726038 INFO::hi
+loginfo('hi %s', 84/2)
+#> 2024-08-06 14:28:37.726619 INFO::hi 42
+loginfo(paste('hi', 84/2))
+#> 2024-08-06 14:28:37.727083 INFO::hi 42
+loginfo(glue::glue('hi {84/2}'))
+#> 2024-08-06 14:28:37.727625 INFO::hi 42
+
+
+

logger

+
log_info('hi')
+log_info('hi {84/2}')
+log_formatter(formatter_sprintf)
+log_info('hi %s', 84/2)
+log_formatter(formatter_paste)
+log_info('hi', 84/2)
+
+

For even better compatibility, there’s also +?formatter_logging that not only relies on +sprintf when the first argument is a string, but will log +the call and the result as well when the log object is an R +expression:

+
log_formatter(formatter_logging)
+log_info('42')
+log_info(42)
+log_info(4+2)
+log_info('foo %s', 'bar')
+log_info(12, 1+1, 2 * 2)
+
+
+

Log record destination

+

Setting the destination of the log records works similarly in both +packages, although he logger packages bundles a lot more +options:

+
+

logging

+
?addHandler
+?writeToConsole
+?writeToFile
+
+
+

logger

+
?log_appender
+?appender_console
+?appender_file
+?appender_tee
+?appender_slack
+?appender_pushbullet
+
+
+
+

Hierarchical logging

+

Both packages support using different logging namespaces and stacking +loggers within the same namespace.

+
+
+

Using logger as a drop-in-replacement of +logging

+

logger has no hard requirements, so it’s an adequate +alternative of logging. Although the function names are a +bit different, and the message formatter also differs, but with some +simple tweaks, logger can become an almost perfect +drop-in-replacement of logging – although not all log +levels (eg and ) are supported:

+
library(logger)
+log_formatter(formatter_logging)
+log_layout(layout_logging)
+logdebug <- log_debug
+loginfo <- log_info
+logwarn <- log_warn
+logerror <- log_error
+
+loginfo('Hello from logger in a logging theme ...')
+logwarn('... where the default log message formatter is %s', 'sprintf', namespace = 'foobar')
+
+
+
+

log4r

+

The log4r +package provides an object-oriented approach for logging in R, so the +logger object is to be passed to the log calls – unlike in the +logger package.

+
+

Initialize

+

So thus it’s important to create a logging object in +log4r before being able to log messages, while that’s +automatically done in `logger:

+
+

log4r

+
library(log4r)
+#> 
+#> Attaching package: 'log4r'
+#> The following object is masked from 'package:logging':
+#> 
+#>     levellog
+#> The following objects are masked from 'package:logger':
+#> 
+#>     as.loglevel, logger
+#> The following object is masked from 'package:base':
+#> 
+#>     debug
+logger <- create.logger(logfile = stdout(), level = "INFO")
+
+
+

logger

+
library(logger)
+
+

Please note that in the background, logger does have a +concept of logger objects, but that’s behind the scene and the user does +not have to specify / reference it. On the other hand, if you wish, you +can do that via the namespace concept of +logger – more on that later.

+
+
+

Logging functions

+

While logger has a log_ prefix for all +logging functions, log4r has lowercase functions names +referring to the log level, which takes a logging object and the log +message:

+
+

log4r

+
info(logger, 'hi there')
+#> INFO  [2024-08-06 14:28:37] hi there
+warn(logger, 'watch out')
+#> WARN  [2024-08-06 14:28:37] watch out
+
+
+

logger

+
log_info('hi there')
+log_warn('watch out')
+
+

As you can see the default layout of the messages is a bit different +in the two packages.

+
+
+

Log levels

+

Both packages are based on log4j, and log4r +provides DEBUG, INFO, WARN, +ERROR and FATAL, while logger +also adds TRACE and SUCCESS on the top of +these.

+

To change the log level threshold, use the level +function on the logging object in log4r, while it’s +log_level in logger.

+
+
+

Log record layout and formatter functions

+

The log4r provides a logformat argument in +create.logger that can be used to override the default +formatting, while logger provides formatter and layout +functions for a flexible log record design.

+
+
+

Log record destination

+

By default, log4r logs to a file that can be set to +stoud to write to the console, while logger +writes to the console by default, but logging to files via the +appender_file functions is also possible – besides a number +of other log record destinations as well.

+
+
+

Hierarchical logging and performance

+

Creating objects is the log4r way of handling multiple +log environments, while logger handles that via +namespaces.

+
+
+
+

loggit

+

Sorry, no direct replacement for loggit +– capturing message, warning and +stop function messages, but it’s on the roadmap to +provide helper functions to be used as message hooks feed +logger.

+
+ + + + +
+ + + + + + + + + + + + + + + diff --git a/vignettes/performance.Rmd b/vignettes/performance.Rmd index 85bac3c4..9d453b41 100644 --- a/vignettes/performance.Rmd +++ b/vignettes/performance.Rmd @@ -107,3 +107,7 @@ microbenchmark(async(), times = 1e3) ``` Please note that although this ~0.6 ms is significantly higher than the ~0.15 ms we achieved above with the `sprintf` formatter, but this time we are calling an appender that would take 1 full second to deliver the log message (and not just printing to the console), so bringing that down to less than 1 millisecond is not too bad. If you need even higher throughput, then a custom `appender_async` without checking on the background process and potentially a faster message queue can bring this even below to 200 µs. + +```{r cleanup, include = FALSE} +logger:::namespaces_reset() +``` diff --git a/vignettes/r_packages.Rmd b/vignettes/r_packages.Rmd index 47d37dfc..bfeca803 100644 --- a/vignettes/r_packages.Rmd +++ b/vignettes/r_packages.Rmd @@ -9,11 +9,11 @@ vignette: > ```{r pkgchecks, echo = FALSE} ## check if other logger packages are available and exit if not -for (pkg in c('devtools')) { - if (!requireNamespace(pkg, quietly = TRUE)) { - warning(paste(pkg, 'package not available, so cannot build this vignette')) - knitr::knit_exit() - } +for (pkg in c("devtools")) { + if (!requireNamespace(pkg, quietly = TRUE)) { + warning(paste(pkg, "package not available, so cannot build this vignette")) + knitr::knit_exit() + } } ``` @@ -30,10 +30,14 @@ So that your R package's users can suppress (or render with custom layout) the l ```{r} library(logger) -devtools::load_all(system.file('demo-packages/logger-tester-package', package = 'logger')) -logger_tester_function(INFO, 'hi from tester package') +devtools::load_all(system.file("demo-packages/logger-tester-package", package = "logger")) +logger_tester_function(INFO, "hi from tester package") ``` But if auto-guessing is not your style, then feel free to set your custom namespace (eg the name of your package) in all `?log_info` etc function calls and let your users know about how to suppress / reformat / redirect your log messages via `?log_threshold`, `?log_layout`, `?log_appender`. Please note that setting the formatter function via `?log_formatter` should not be left to the R package end-users, as the log message formatter is specific to your logging calls, so that should be decided by the R package author. Feel free to pick any formatter function (eg `glue`, `sprintf`, `paste` or something else), and set that via `?log_formatter` when your R package is loaded. All other parameters of your `logger` will inherit from the `global` namespace -- set by your R package's end user. + +```{r cleanup, include = FALSE} +logger:::namespaces_reset() +``` diff --git a/vignettes/write_custom_extensions.Rmd b/vignettes/write_custom_extensions.Rmd index 67f45fc0..922c85fc 100644 --- a/vignettes/write_custom_extensions.Rmd +++ b/vignettes/write_custom_extensions.Rmd @@ -70,3 +70,7 @@ log_appender(function(lines) { table = 'logs', db = con) }) ``` + +```{r cleanup, include = FALSE} +logger:::namespaces_reset() +```