├── .Rbuildignore ├── .covrignore ├── .github ├── .gitignore ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── R-CMD-check.yaml │ ├── lint.yaml │ ├── pkgdown.yaml │ ├── recheck.yaml │ └── test-coverage.yaml ├── .gitignore ├── .lintr ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── appenders.R ├── color.R ├── formatters.R ├── helpers.R ├── hooks.R ├── layouts.R ├── levels.R ├── logger-meta.R ├── logger-package.R ├── logger.R ├── try.R ├── utils.R └── zzz.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── inst └── demo-packages │ └── logger-tester-package │ ├── DESCRIPTION │ ├── LICENSE │ ├── NAMESPACE │ ├── R │ └── tester.R │ └── man │ ├── logger_info_tester_function.Rd │ └── logger_tester_function.Rd ├── man ├── appender_async.Rd ├── appender_console.Rd ├── appender_file.Rd ├── appender_kinesis.Rd ├── appender_pushbullet.Rd ├── appender_slack.Rd ├── appender_stdout.Rd ├── appender_syslog.Rd ├── appender_syslognet.Rd ├── appender_tee.Rd ├── appender_telegram.Rd ├── appender_void.Rd ├── as.loglevel.Rd ├── colorize_by_log_level.Rd ├── delete_logger_index.Rd ├── deparse_to_one_line.Rd ├── fail_on_missing_package.Rd ├── figures │ ├── colors.png │ └── logo.png ├── formatter_cli.Rd ├── formatter_glue.Rd ├── formatter_glue_or_sprintf.Rd ├── formatter_glue_safe.Rd ├── formatter_json.Rd ├── formatter_logging.Rd ├── formatter_pander.Rd ├── formatter_paste.Rd ├── formatter_sprintf.Rd ├── get_logger_meta_variables.Rd ├── grapes-except-grapes.Rd ├── layout_blank.Rd ├── layout_glue.Rd ├── layout_glue_colors.Rd ├── layout_glue_generator.Rd ├── layout_json.Rd ├── layout_json_parser.Rd ├── layout_logging.Rd ├── layout_simple.Rd ├── layout_syslognet.Rd ├── log_appender.Rd ├── log_chunk_time.Rd ├── log_elapsed.Rd ├── log_errors.Rd ├── log_eval.Rd ├── log_failure.Rd ├── log_formatter.Rd ├── log_indices.Rd ├── log_layout.Rd ├── log_level.Rd ├── log_levels.Rd ├── log_messages.Rd ├── log_namespaces.Rd ├── log_separator.Rd ├── log_shiny_input_changes.Rd ├── log_threshold.Rd ├── log_tictoc.Rd ├── log_warnings.Rd ├── log_with_separator.Rd ├── logger-package.Rd ├── logger.Rd ├── skip_formatter.Rd └── with_log_threshold.Rd ├── pkgdown └── favicon │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── tests ├── testthat.R └── testthat │ ├── _snaps │ ├── formatters.md │ ├── helpers.md │ ├── hooks.md │ ├── layouts.md │ ├── logger.md │ ├── try.md │ └── utils.md │ ├── helper.R │ ├── test-appenders.R │ ├── test-eval.R │ ├── test-formatters.R │ ├── test-helpers.R │ ├── test-hooks.R │ ├── test-layouts.R │ ├── test-logger-meta.R │ ├── test-logger.R │ ├── test-return.R │ ├── test-try.R │ └── test-utils.R └── vignettes ├── Intro.Rmd ├── anatomy.Rmd ├── customize_logger.Rmd ├── logger_structure.svg ├── migration.Rmd ├── migration.html ├── performance.Rmd ├── r_packages.Rmd └── write_custom_extensions.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | _pkgdown.yml 3 | .editorconfig 4 | .gitignore 5 | .lintr 6 | .*.sh$ 7 | .backup 8 | .*.graphml$ 9 | logo.R 10 | cran-comments.md 11 | .covrignore 12 | dev-resources 13 | ^doc$ 14 | ^Meta$ 15 | ^_pkgdown\.yml$ 16 | ^docs$ 17 | ^pkgdown$ 18 | ^\.github$ 19 | revdep 20 | ^codecov\.yml$ 21 | ^README\.Rmd$ 22 | ^LICENSE\.md$ 23 | -------------------------------------------------------------------------------- /.covrignore: -------------------------------------------------------------------------------- 1 | R/zzz.R -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: File a bug report with a reproducible example 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | Please be as clear and thorough as possible. 13 | 14 | **Reproducible example** 15 | 16 | Share a short code chunk reproducing the bug, ideally using `reprex`. To make sure that the log output is visible, either switch the appender to `stdout`, or use the `std_out_err` option of `reprex`. For example, you could copy one of the below code chunks and run `reprex` to get a markdown chunk that you can paste in the bug report: 17 | 18 | 1. For using `reprex::reprex()` (updating the appender function as part of the example): 19 | 20 | ```r 21 | library(logger) 22 | log_appender(appender_stdout) 23 | log_info(42) 24 | ``` 25 | 26 | 2. For using `reprex::reprex(std_out_err = TRUE)` (without updating the appender function): 27 | 28 | ```r 29 | logger::log_info(42) 30 | ``` 31 | 32 | **Session info** 33 | 34 |
35 | ``` r 36 | # Paste here the output of `sessionInfo()` 37 | ``` 38 |
39 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | branches: [main, master] 12 | 13 | name: R-CMD-check.yaml 14 | 15 | permissions: read-all 16 | 17 | jobs: 18 | R-CMD-check: 19 | runs-on: ${{ matrix.config.os }} 20 | 21 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | config: 27 | - {os: macos-latest, r: 'release'} 28 | 29 | - {os: windows-latest, r: 'release'} 30 | # use 4.0 or 4.1 to check with rtools40's older compiler 31 | - {os: windows-latest, r: 'oldrel-4'} 32 | 33 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 34 | - {os: ubuntu-latest, r: 'release'} 35 | - {os: ubuntu-latest, r: 'oldrel-1'} 36 | - {os: ubuntu-latest, r: 'oldrel-2'} 37 | - {os: ubuntu-latest, r: 'oldrel-3'} 38 | - {os: ubuntu-latest, r: 'oldrel-4'} 39 | 40 | env: 41 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 42 | R_KEEP_PKG_SOURCE: yes 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | 47 | - uses: r-lib/actions/setup-pandoc@v2 48 | 49 | - uses: r-lib/actions/setup-r@v2 50 | with: 51 | r-version: ${{ matrix.config.r }} 52 | http-user-agent: ${{ matrix.config.http-user-agent }} 53 | use-public-rspm: true 54 | 55 | - uses: r-lib/actions/setup-r-dependencies@v2 56 | with: 57 | extra-packages: any::rcmdcheck 58 | needs: check 59 | pak-version: devel # can remove once pak 0.7.3 is released 60 | 61 | - uses: r-lib/actions/check-r-package@v2 62 | with: 63 | upload-snapshots: true 64 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 65 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: lint.yaml 10 | 11 | permissions: read-all 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::lintr, local::. 28 | needs: lint 29 | 30 | - name: Lint 31 | run: lintr::lint_package() 32 | shell: Rscript {0} 33 | env: 34 | LINTR_ERROR_ON_LINT: true 35 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown.yaml 13 | 14 | permissions: read-all 15 | 16 | jobs: 17 | pkgdown: 18 | runs-on: ubuntu-latest 19 | # Only restrict concurrency for non-PR jobs 20 | concurrency: 21 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 22 | env: 23 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 24 | permissions: 25 | contents: write 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - uses: r-lib/actions/setup-pandoc@v2 30 | 31 | - uses: r-lib/actions/setup-r@v2 32 | with: 33 | use-public-rspm: true 34 | 35 | - uses: r-lib/actions/setup-r-dependencies@v2 36 | with: 37 | extra-packages: any::pkgdown, local::. 38 | needs: website 39 | 40 | - name: Build site 41 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 42 | shell: Rscript {0} 43 | 44 | - name: Deploy to GitHub pages 🚀 45 | if: github.event_name != 'pull_request' 46 | uses: JamesIves/github-pages-deploy-action@v4.5.0 47 | with: 48 | clean: false 49 | branch: gh-pages 50 | folder: docs 51 | -------------------------------------------------------------------------------- /.github/workflows/recheck.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | which: 5 | type: choice 6 | description: Which dependents to check 7 | options: 8 | - strong 9 | - most 10 | 11 | name: Reverse dependency check 12 | 13 | jobs: 14 | revdep_check: 15 | name: Reverse check ${{ inputs.which }} dependents 16 | uses: r-devel/recheck/.github/workflows/recheck.yml@v1 17 | with: 18 | which: ${{ inputs.which }} 19 | subdirectory: '' 20 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage.yaml 10 | 11 | permissions: read-all 12 | 13 | jobs: 14 | test-coverage: 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: r-lib/actions/setup-r@v2 23 | with: 24 | use-public-rspm: true 25 | 26 | - uses: r-lib/actions/setup-r-dependencies@v2 27 | with: 28 | extra-packages: any::covr, any::xml2 29 | needs: coverage 30 | 31 | - name: Test coverage 32 | run: | 33 | cov <- covr::package_coverage( 34 | quiet = FALSE, 35 | clean = FALSE, 36 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 37 | ) 38 | covr::to_cobertura(cov) 39 | shell: Rscript {0} 40 | 41 | - uses: codecov/codecov-action@v4 42 | with: 43 | fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} 44 | file: ./cobertura.xml 45 | plugin: noop 46 | disable_search: true 47 | token: ${{ secrets.CODECOV_TOKEN }} 48 | 49 | - name: Show testthat output 50 | if: always() 51 | run: | 52 | ## -------------------------------------------------------------------- 53 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 54 | shell: bash 55 | 56 | - name: Upload test results 57 | if: failure() 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: coverage-test-failures 61 | path: ${{ runner.temp }}/package 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | README.html 3 | 4 | /.quarto/ 5 | -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults( 2 | line_length_linter(120), 3 | object_usage_linter(interpret_glue = TRUE), 4 | object_name_linter(styles = c("snake_case", "symbols", "UPPERCASE")) 5 | ) 6 | exclude: '# nolint' 7 | exclude_start: '# nolint start' 8 | exclude_end: '# nolint end' 9 | exclusions: list( 10 | "inst/demo-packages/logger-tester-package/R/tester.R", 11 | "vignettes/migration.Rmd" 12 | ) 13 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Type: Package 2 | Package: logger 3 | Title: A Lightweight, Modern and Flexible Logging Utility 4 | Version: 0.4.0.9000 5 | Date: 2024-10-19 6 | Authors@R: c( 7 | person("Gergely", "Daróczi", , "daroczig@rapporter.net", role = c("aut", "cre"), 8 | comment = c(ORCID = "0000-0003-3149-8537")), 9 | person("Hadley", "Wickham", , "hadley@posit.co", role = "aut", 10 | comment = c(ORCID = "0000-0003-4757-117X")), 11 | person("System1", role = "fnd") 12 | ) 13 | Description: Inspired by the the 'futile.logger' R package and 'logging' 14 | Python module, this utility provides a flexible and extensible way of 15 | formatting and delivering log messages with low overhead. 16 | License: MIT + file LICENSE 17 | URL: https://daroczig.github.io/logger/ 18 | BugReports: https://github.com/daroczig/logger/issues 19 | Depends: 20 | R (>= 4.0.0) 21 | Imports: 22 | utils 23 | Suggests: 24 | botor, 25 | cli, 26 | covr, 27 | crayon, 28 | devtools, 29 | glue, 30 | jsonlite, 31 | knitr, 32 | mirai (>= 1.3.0), 33 | pander, 34 | parallel, 35 | R.utils, 36 | rmarkdown, 37 | roxygen2, 38 | RPushbullet, 39 | rsyslog, 40 | shiny, 41 | slackr (>= 1.4.1), 42 | syslognet, 43 | telegram, 44 | testthat (>= 3.0.0), 45 | withr 46 | Enhances: 47 | futile.logger, 48 | log4r, 49 | logging 50 | VignetteBuilder: 51 | knitr 52 | Config/testthat/edition: 3 53 | Config/testthat/parallel: TRUE 54 | Encoding: UTF-8 55 | Roxygen: list(markdown = TRUE) 56 | RoxygenNote: 7.3.2 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2024 2 | COPYRIGHT HOLDER: logger authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 logger authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(as.loglevel,character) 4 | S3method(as.loglevel,default) 5 | S3method(as.loglevel,integer) 6 | S3method(as.loglevel,numeric) 7 | S3method(print,loglevel) 8 | export("%except%") 9 | export(DEBUG) 10 | export(ERROR) 11 | export(FATAL) 12 | export(INFO) 13 | export(OFF) 14 | export(SUCCESS) 15 | export(TRACE) 16 | export(WARN) 17 | export(appender_async) 18 | export(appender_console) 19 | export(appender_file) 20 | export(appender_kinesis) 21 | export(appender_pushbullet) 22 | export(appender_slack) 23 | export(appender_stderr) 24 | export(appender_stdout) 25 | export(appender_syslog) 26 | export(appender_syslognet) 27 | export(appender_tee) 28 | export(appender_telegram) 29 | export(appender_void) 30 | export(as.loglevel) 31 | export(colorize_by_log_level) 32 | export(delete_logger_index) 33 | export(deparse_to_one_line) 34 | export(fail_on_missing_package) 35 | export(formatter_cli) 36 | export(formatter_glue) 37 | export(formatter_glue_or_sprintf) 38 | export(formatter_glue_safe) 39 | export(formatter_json) 40 | export(formatter_logging) 41 | export(formatter_pander) 42 | export(formatter_paste) 43 | export(formatter_sprintf) 44 | export(get_logger_meta_variables) 45 | export(grayscale_by_log_level) 46 | export(layout_blank) 47 | export(layout_glue) 48 | export(layout_glue_colors) 49 | export(layout_glue_generator) 50 | export(layout_json) 51 | export(layout_json_parser) 52 | export(layout_logging) 53 | export(layout_simple) 54 | export(layout_syslognet) 55 | export(log_appender) 56 | export(log_chunk_time) 57 | export(log_debug) 58 | export(log_elapsed) 59 | export(log_elapsed_start) 60 | export(log_error) 61 | export(log_errors) 62 | export(log_eval) 63 | export(log_failure) 64 | export(log_fatal) 65 | export(log_formatter) 66 | export(log_indices) 67 | export(log_info) 68 | export(log_layout) 69 | export(log_level) 70 | export(log_messages) 71 | export(log_namespaces) 72 | export(log_separator) 73 | export(log_shiny_input_changes) 74 | export(log_success) 75 | export(log_threshold) 76 | export(log_tictoc) 77 | export(log_trace) 78 | export(log_warn) 79 | export(log_warnings) 80 | export(log_with_separator) 81 | export(logger) 82 | export(skip_formatter) 83 | export(with_log_threshold) 84 | importFrom(utils,assignInMyNamespace) 85 | importFrom(utils,assignInNamespace) 86 | importFrom(utils,capture.output) 87 | importFrom(utils,compareVersion) 88 | importFrom(utils,getFromNamespace) 89 | importFrom(utils,packageVersion) 90 | importFrom(utils,str) 91 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # logger (development version) 2 | 3 | * Support renaming meta fields (#217, @atusy) 4 | * Added `log_elapsed()` to show cumulative elapsed running time (#221, @thomasp85) 5 | * `log_errors()` gains a `traceback` argument that toggles whether the error traceback should be logged along with the message (fix #86 via #223, @thomasp85) 6 | * File and line location of the log call is now available to the layouts (fix #110 via #224, @thomasp85) 7 | * New `formatter_cli()` allows you to use the syntax from the cli package to create log messages (fix #210 via #225, @thomasp85) 8 | * New `log_chunk_time()` helper function to automatically log the execution time of knitr code chunks (fix #222 via #227, @thomasp85) 9 | * Allow user to overwrite the timestamp during logging if needed (fix #230, @thomasp85) 10 | 11 | # logger 0.4.0 (2024-10-19) 12 | 13 | A lot of internal code quality improvements and standardization, 14 | improved documentations, modernized tests, performance speedups. 15 | 16 | ## New features 17 | 18 | * logo 😻 (#196, @hadley) 19 | * computing metadata lazily, so various expensive computations are only performed if you actually add them to the log (#105, @hadley) 20 | * `log_appender()`, `log_layout()` and `log_formatter()` now check that you are calling them with a function, and return the previously set value (#170, @hadley) 21 | * new function to return number of log indices (#194, @WurmPeter) 22 | * `appender_async` is now using `mirai` instead of a custom background process and queue system (#214, @hadley @shikokuchuo) 23 | 24 | ## Fixes 25 | 26 | * `eval` scoping and lazy eval (#178, @hadley) 27 | 28 | ## Housekeeping 29 | 30 | * update `pkgdown` site to Bootstrap 5 and related revamp, e.g. reference index and run/show examples (#159 #165 #193, @hadley) 31 | * roxygen updated to use markdown, general cleanup (#160 #161 #191 #201, @hadley) 32 | * testing improvements, e.g. move to `testthat` v3 and snapshots, syntactic sugar (#163 #167 #168 #169 #171 #192, @hadley) 33 | * README tweaks (#162 #176, @hadley) 34 | * modernize GitHub Actions (#171, @hadley) 35 | * drop support for R versions below 4.0.0 (#177, @hadley) 36 | * internal function tweaks (#181 #187 #197, @hadley) 37 | * restyle sources (#185 #186 #191 #199, @daroczig and @hadley) 38 | 39 | # logger 0.3.0 (2024-03-03) 40 | 41 | Many unrelated small features, fixes and documentation updates collected over 2+ years. 42 | 43 | ## New features 44 | 45 | * update `log_*` functions to invisibly return the formatted log message and record (#26, @r2evans) 46 | * add `namespace` argument to `log_shiny_input_changes` (#93, @kpagacz) 47 | * optionally suppress messages in `globalCallingHandlers` after being logged (#100, @DanChaltiel) 48 | * `as.loglevel` helper to convert string/number to loglevel (requested by @tentacles-from-outer-space) 49 | * new formatter function: `formatter_glue_safe` (#126, @terashim) 50 | * support `OFF` log level (#138, @pawelru) 51 | * override default `INFO` log level via env var (#145, requested by sellorm) 52 | 53 | ## Fixes 54 | 55 | * handle zero-length messages in `formatter_glue_or_sprintf` (#74, @deeenes) 56 | * generalize `log_separator` to work with all layout functions (#96, @Polkas) 57 | * support log levels in `log_shiny_input_changes` (#103, @taekeharkema) 58 | * fix fn name lookup/reference with nested calls (#120, reported by @averissimo) 59 | * force the `file` argument of `appender_tee` (#124, reported by @dbontemps) 60 | * don't allow stacking logger hooks on messages/warnings/errors (reported by @jkeuskamp) 61 | * improve fragile test case when `Hmisc` loaded (#131, @r2evans) 62 | * pass `index`, `namespace` etc from `log_` functions down to `log_level` (#143, @MichaelChirico) 63 | * refer to the caller function in global message logger hooks (#146, reported by @gabesolomon10) 64 | 65 | # logger 0.2.2 (2021-10-10) 66 | 67 | Maintenance release: 68 | 69 | * fix unbalanced code chunk delimiters in vignette (yihui/knitr#2057) 70 | 71 | # logger 0.2.1 (2021-07-06) 72 | 73 | Maintenance release: 74 | 75 | * update `appender_slack` to use `slackr_msg` instead of `text_slackr` 76 | 77 | # logger 0.2.0 (2021-03-03) 78 | 79 | ## Breaking changes 80 | 81 | * `appender_console` writes to `stderr` by default instead of `stdout` (#28) 82 | 83 | ## Fixes 84 | 85 | * default date format in `glue` layouts (#44, @burgikukac) 86 | * `fn` reference in loggers will not to a Cartesian join on the log lines and message, but merge (and clean up) the `fn` even for large anonymous functions (#20) 87 | 88 | ## New features 89 | 90 | * allow defining the log level threshold as a string (#13, @artemklevtsov) 91 | * allow updating the log level threshold, formatter, layout and appender in all namespaces with a single call (#50) 92 | * new argument to `appender_file` to optionally truncate before appending (#24, @eddelbuettel) 93 | * new arguments to `appender_file` to optionally rotate the log files after appending (#42) 94 | * new meta variables for logging in custom layouts: R version and calling package's version 95 | * improved performance by not evaluating arguments when the log record does not meet the log level threshold (#27, @jozefhajnala) 96 | * `logger` in now part of the Mikata Project: https://mikata.dev 97 | 98 | ## New helper functions 99 | 100 | * `%except%`: evaluate an expression with fallback 101 | * `log_separator`: logging with separator lines (#16) 102 | * `log_tictoc`: tic-toc logging (#16, @nfultz) 103 | * `log_failure`: log error before failing (#19, @amy17519) 104 | * `log_messages`, `log_warnings`, `log_errors`: optionally auto-log messages, warnings and errors using `globalCallingHandlers` on R 4.0.0 and above, and injecting `logger` calls to `message`, `warnings` and `stop` below R 4.0.0 105 | * `log_shiny_input_changes`: auto-log input changes in Shiny apps (#25) 106 | 107 | ## New formatter functions 108 | 109 | * `layout_pander`: transform R objects into markdown before logging (#22) 110 | 111 | ## New layout functions 112 | 113 | * `layout_blank`: blank log messages without any modification 114 | * `layout_json_parser`: render the layout as a JSON blob after merging with requested meta fields 115 | 116 | ## New appender functions 117 | 118 | * `appender_telegram`: deliver log records to Telegram (#14, @artemklevtsov) 119 | * `appender_syslog`: deliver log records to syslog (#30, @atheriel) 120 | * `appender_kinesis`: deliver log records to Amazon Kinesis (#35) 121 | * `appender_async`: wrapper function for other appender functions to deliver log records in a background process asynchronously without blocking the master process (#35) 122 | 123 | # logger 0.1 (2018-12-20) 124 | 125 | Initial CRAN release after collecting feedback for a month on Twitter at `https://twitter.com/daroczig/status/1067461632677330944`: 126 | 127 | * finalized design of a log request defined by 128 | 129 | * a log level `threshold`, 130 | * a `formatter` function preparing the log message, 131 | * a `layout` function rendering the actual log records and 132 | * an `appender` function delivering to the log destination 133 | 134 | * detailed documentation with 7 vignettes and a lot of examples, even some benchmarks 135 | * ~75% code coverage for unit tests 136 | * 5 `formatter` functions mostly using `paste`, `sprintf` and `glue` 137 | * 6 `layout` functions with convenient wrappers to let users define custom layouts via `glue` or `JSON`, including colorized output 138 | * 5 `appender` functions delivering log records to the console, files, Pushbullet and Slack 139 | * helper function to evaluate an expressions with auto-logging both the expression and its result 140 | * helper function to temporarily update the log level threshold 141 | * helper function to skip running the formatter function on a log message 142 | * mostly backward compatibly with the `logging` and `futile.logger` packages 143 | -------------------------------------------------------------------------------- /R/color.R: -------------------------------------------------------------------------------- 1 | #' Color string by the related log level 2 | #' 3 | #' Color log messages according to their severity with either a rainbow 4 | #' or grayscale color scheme. The greyscale theme assumes a dark background on 5 | #' the terminal. 6 | #' 7 | #' @param msg String to color. 8 | #' @param level see [log_levels()] 9 | #' @return A string with ANSI escape codes. 10 | #' @export 11 | #' @examplesIf requireNamespace("crayon") 12 | #' cat(colorize_by_log_level("foobar", FATAL), "\n") 13 | #' cat(colorize_by_log_level("foobar", ERROR), "\n") 14 | #' cat(colorize_by_log_level("foobar", WARN), "\n") 15 | #' cat(colorize_by_log_level("foobar", SUCCESS), "\n") 16 | #' cat(colorize_by_log_level("foobar", INFO), "\n") 17 | #' cat(colorize_by_log_level("foobar", DEBUG), "\n") 18 | #' cat(colorize_by_log_level("foobar", TRACE), "\n") 19 | #' 20 | #' cat(grayscale_by_log_level("foobar", FATAL), "\n") 21 | #' cat(grayscale_by_log_level("foobar", ERROR), "\n") 22 | #' cat(grayscale_by_log_level("foobar", WARN), "\n") 23 | #' cat(grayscale_by_log_level("foobar", SUCCESS), "\n") 24 | #' cat(grayscale_by_log_level("foobar", INFO), "\n") 25 | #' cat(grayscale_by_log_level("foobar", DEBUG), "\n") 26 | #' cat(grayscale_by_log_level("foobar", TRACE), "\n") 27 | colorize_by_log_level <- function(msg, level) { 28 | fail_on_missing_package("crayon") 29 | 30 | color <- switch(attr(level, "level"), 31 | "FATAL" = crayon::combine_styles(crayon::bold, crayon::make_style("red1")), 32 | "ERROR" = crayon::make_style("red4"), 33 | "WARN" = crayon::make_style("darkorange"), 34 | "SUCCESS" = crayon::combine_styles(crayon::bold, crayon::make_style("green4")), 35 | "INFO" = crayon::reset, 36 | "DEBUG" = crayon::make_style("deepskyblue4"), 37 | "TRACE" = crayon::make_style("dodgerblue4"), 38 | stop("Unknown log level") 39 | ) 40 | 41 | paste0(color(msg), crayon::reset("")) 42 | } 43 | 44 | #' @export 45 | #' @rdname colorize_by_log_level 46 | grayscale_by_log_level <- function(msg, level) { 47 | fail_on_missing_package("crayon") 48 | 49 | color <- switch(attr(level, "level"), 50 | "FATAL" = crayon::make_style("gray100"), 51 | "ERROR" = crayon::make_style("gray90"), 52 | "WARN" = crayon::make_style("gray80"), 53 | "SUCCESS" = crayon::make_style("gray70"), 54 | "INFO" = crayon::make_style("gray60"), 55 | "DEBUG" = crayon::make_style("gray50"), 56 | "TRACE" = crayon::make_style("gray40"), 57 | stop("Unknown log level") 58 | ) 59 | 60 | paste0(color(msg), crayon::reset("")) 61 | } 62 | -------------------------------------------------------------------------------- /R/hooks.R: -------------------------------------------------------------------------------- 1 | #' Injects a logger call to standard messages 2 | #' 3 | #' This function uses [trace()] to add a [log_info()] function call when 4 | #' `message` is called to log the informative messages with the 5 | #' `logger` layout and appender. 6 | #' @export 7 | #' @examples \dontrun{ 8 | #' log_messages() 9 | #' message("hi there") 10 | #' } 11 | log_messages <- function() { 12 | if (any(sapply( 13 | globalCallingHandlers()[names(globalCallingHandlers()) == "message"], 14 | attr, 15 | which = "implements" 16 | ) == "log_messages")) { 17 | warning("Ignoring this call to log_messages as it was registered previously.") 18 | } else { 19 | globalCallingHandlers( 20 | message = structure(function(m) { 21 | logger::log_level(logger::INFO, m$message, .topcall = m$call) 22 | }, implements = "log_messages") 23 | ) 24 | } 25 | } 26 | 27 | 28 | #' Injects a logger call to standard warnings 29 | #' 30 | #' This function uses `trace` to add a `log_warn` function call when 31 | #' `warning` is called to log the warning messages with the `logger` 32 | #' layout and appender. 33 | #' @param muffle if TRUE, the warning is not shown after being logged 34 | #' @export 35 | #' @examples \dontrun{ 36 | #' log_warnings() 37 | #' for (i in 1:5) { 38 | #' Sys.sleep(runif(1)) 39 | #' warning(i) 40 | #' } 41 | #' } 42 | log_warnings <- function(muffle = getOption("logger_muffle_warnings", FALSE)) { 43 | if (any(sapply( 44 | globalCallingHandlers()[names(globalCallingHandlers()) == "warning"], 45 | attr, 46 | which = "implements" 47 | ) == "log_warnings")) { 48 | warning("Ignoring this call to log_warnings as it was registered previously.") 49 | } else { 50 | globalCallingHandlers( 51 | warning = structure(function(m) { 52 | logger::log_level(logger::WARN, m$message, .topcall = m$call) 53 | if (isTRUE(muffle)) { 54 | invokeRestart("muffleWarning") 55 | } 56 | }, implements = "log_warnings") 57 | ) 58 | } 59 | } 60 | 61 | #' Injects a logger call to standard errors 62 | #' 63 | #' This function uses [trace()] to add a [log_error()] function call when 64 | #' [stop()] is called to log the error messages with the `logger` layout 65 | #' and appender. 66 | #' @param muffle if TRUE, the error is not thrown after being logged 67 | #' @param traceback if TRUE the error traceback is logged along with the error 68 | #' message 69 | #' @export 70 | #' @examples \dontrun{ 71 | #' log_errors() 72 | #' stop("foobar") 73 | #' } 74 | log_errors <- function(muffle = getOption("logger_muffle_errors", FALSE), traceback = FALSE) { 75 | if (any(sapply( 76 | globalCallingHandlers()[names(globalCallingHandlers()) == "error"], 77 | attr, 78 | which = "implements" 79 | ) == "log_errors")) { 80 | warning("Ignoring this call to log_errors as it was registered previously.") 81 | } else { 82 | globalCallingHandlers( 83 | error = structure(function(m) { 84 | logger::log_level(logger::ERROR, m$message, .topcall = m$call) 85 | if (traceback) { 86 | bt <- .traceback(3L) 87 | logger::log_level(logger::ERROR, "Traceback:", .topcall = m$call) 88 | for (i in seq_along(bt)) { 89 | msg <- paste0(length(bt) - i + 1L, ": ", bt[[i]]) 90 | ref <- attr(bt[[i]], "srcref") 91 | file <- attr(ref, "srcfile") 92 | if (inherits(file, "srcfile")) { 93 | file <- basename(file$filename) 94 | line <- paste(unique(c(ref[1L], ref[3L])), collapse = "-") 95 | msg <- paste0(msg, " at ", file, " #", line) 96 | } 97 | logger::log_level(logger::ERROR, skip_formatter(msg), .topcall = m$call) 98 | } 99 | } 100 | if (isTRUE(muffle)) { 101 | invokeRestart("abort") 102 | } 103 | }, implements = "log_errors") 104 | ) 105 | } 106 | } 107 | 108 | 109 | #' Auto logging input changes in Shiny app 110 | #' 111 | #' This is to be called in the `server` section of the Shiny app. 112 | #' @export 113 | #' @param input passed from Shiny's `server` 114 | #' @param level log level 115 | #' @param excluded_inputs character vector of input names to exclude from logging 116 | #' @param namespace the name of the namespace 117 | #' @importFrom utils assignInMyNamespace assignInNamespace 118 | #' @examples \dontrun{ 119 | #' library(shiny) 120 | #' 121 | #' ui <- bootstrapPage( 122 | #' numericInput("mean", "mean", 0), 123 | #' numericInput("sd", "sd", 1), 124 | #' textInput("title", "title", "title"), 125 | #' textInput("foo", "This is not used at all, still gets logged", "foo"), 126 | #' passwordInput("password", "Password not to be logged", "secret"), 127 | #' plotOutput("plot") 128 | #' ) 129 | #' 130 | #' server <- function(input, output) { 131 | #' logger::log_shiny_input_changes(input, excluded_inputs = "password") 132 | #' 133 | #' output$plot <- renderPlot({ 134 | #' hist(rnorm(1e3, input$mean, input$sd), main = input$title) 135 | #' }) 136 | #' } 137 | #' 138 | #' shinyApp(ui = ui, server = server) 139 | #' } 140 | log_shiny_input_changes <- function(input, 141 | level = INFO, 142 | namespace = NA_character_, 143 | excluded_inputs = character()) { 144 | fail_on_missing_package("shiny") 145 | fail_on_missing_package("jsonlite") 146 | 147 | session <- shiny::getDefaultReactiveDomain() 148 | ns <- ifelse(!is.null(session), session$ns(character(0)), "") 149 | 150 | if (!(shiny::isRunning() || inherits(session, "MockShinySession") || inherits(session, "session_proxy"))) { 151 | stop("No Shiny app running, it makes no sense to call this function outside of a Shiny app") 152 | } 153 | 154 | input_values <- shiny::isolate(shiny::reactiveValuesToList(input)) 155 | assignInMyNamespace("shiny_input_values", input_values) 156 | log_level(level, skip_formatter(trimws(paste( 157 | ns, 158 | "Default Shiny inputs initialized:", 159 | as.character(jsonlite::toJSON(input_values, auto_unbox = TRUE)) 160 | ))), namespace = namespace) 161 | 162 | shiny::observe({ 163 | old_input_values <- shiny_input_values 164 | new_input_values <- shiny::reactiveValuesToList(input) 165 | names <- unique(c(names(old_input_values), names(new_input_values))) 166 | names <- setdiff(names, excluded_inputs) 167 | for (name in names) { 168 | old <- old_input_values[name] 169 | new <- new_input_values[name] 170 | if (!identical(old, new)) { 171 | message <- trimws("{ns} Shiny input change detected in {name}: {old} -> {new}") 172 | log_level(level, message, namespace = namespace) 173 | } 174 | } 175 | assignInNamespace("shiny_input_values", new_input_values, ns = "logger") 176 | }) 177 | } 178 | shiny_input_values <- NULL 179 | -------------------------------------------------------------------------------- /R/levels.R: -------------------------------------------------------------------------------- 1 | log_levels_supported <- c("OFF", "FATAL", "ERROR", "WARN", "SUCCESS", "INFO", "DEBUG", "TRACE") 2 | 3 | #' Log levels 4 | #' 5 | #' The standard Apache logj4 log levels plus a custom level for 6 | #' `SUCCESS`. For the full list of these log levels and suggested 7 | #' usage, check the below Details. 8 | #' 9 | #' List of supported log levels: 10 | #' 11 | #' * `OFF` No events will be logged 12 | #' * `FATAL` Severe error that will prevent the application from continuing 13 | #' * `ERROR` An error in the application, possibly recoverable 14 | #' * `WARN` An event that might possible lead to an error 15 | #' * `SUCCESS` An explicit success event above the INFO level that you want to log 16 | #' * `INFO` An event for informational purposes 17 | #' * `DEBUG` A general debugging event 18 | #' * `TRACE` A fine-grained debug message, typically capturing the flow through the application. 19 | #' @references , 20 | #' 21 | #' @name log_levels 22 | NULL 23 | 24 | #' @rdname log_levels 25 | #' @export 26 | #' @format NULL 27 | OFF <- structure(0L, level = "OFF", class = c("loglevel", "integer")) 28 | #' @export 29 | #' @rdname log_levels 30 | #' @format NULL 31 | FATAL <- structure(100L, level = "FATAL", class = c("loglevel", "integer")) 32 | #' @export 33 | #' @rdname log_levels 34 | #' @format NULL 35 | ERROR <- structure(200L, level = "ERROR", class = c("loglevel", "integer")) 36 | #' @export 37 | #' @rdname log_levels 38 | #' @format NULL 39 | WARN <- structure(300L, level = "WARN", class = c("loglevel", "integer")) 40 | #' @export 41 | #' @rdname log_levels 42 | #' @format NULL 43 | SUCCESS <- structure(350L, level = "SUCCESS", class = c("loglevel", "integer")) 44 | #' @export 45 | #' @rdname log_levels 46 | #' @format NULL 47 | INFO <- structure(400L, level = "INFO", class = c("loglevel", "integer")) 48 | #' @export 49 | #' @rdname log_levels 50 | #' @format NULL 51 | DEBUG <- structure(500L, level = "DEBUG", class = c("loglevel", "integer")) 52 | #' @export 53 | #' @rdname log_levels 54 | #' @format NULL 55 | TRACE <- structure(600L, level = "TRACE", class = c("loglevel", "integer")) 56 | 57 | #' @export 58 | print.loglevel <- function(x, ...) { 59 | cat("Log level: ", attr(x, "level"), "\n", sep = "") 60 | } 61 | 62 | 63 | #' Convert R object into a logger log-level 64 | #' @param x string or integer 65 | #' @return pander log-level, e.g. `INFO` 66 | #' @export 67 | #' @examples 68 | #' as.loglevel(INFO) 69 | #' as.loglevel(400L) 70 | #' as.loglevel(400) 71 | as.loglevel <- function(x) { # nolint 72 | UseMethod("as.loglevel", x) 73 | } 74 | 75 | 76 | #' @export 77 | as.loglevel.default <- function(x) { 78 | stop(paste( 79 | "Do not know how to convert", 80 | shQuote(class(x)[1]), 81 | "to a logger log-level." 82 | )) 83 | } 84 | 85 | 86 | #' @export 87 | as.loglevel.character <- function(x) { 88 | stopifnot( 89 | length(x) == 1, 90 | x %in% log_levels_supported 91 | ) 92 | getFromNamespace(x, "logger") 93 | } 94 | 95 | 96 | #' @export 97 | as.loglevel.integer <- function(x) { 98 | loglevels <- mget(log_levels_supported, envir = asNamespace("logger")) 99 | stopifnot( 100 | length(x) == 1, 101 | x %in% as.integer(loglevels) 102 | ) 103 | loglevels[[which(loglevels == x)]] 104 | } 105 | 106 | 107 | #' @export 108 | as.loglevel.numeric <- as.loglevel.integer 109 | -------------------------------------------------------------------------------- /R/logger-meta.R: -------------------------------------------------------------------------------- 1 | logger_meta_env <- function(log_level = NULL, 2 | namespace = NA_character_, 3 | .logcall = sys.call(), 4 | .topcall = sys.call(-1), 5 | .topenv = parent.frame(), 6 | .timestamp = Sys.time(), 7 | parent = emptyenv()) { 8 | env <- new.env(parent = parent) 9 | env$ns <- namespace 10 | env$ans <- fallback_namespace(namespace) 11 | 12 | force(.topcall) 13 | force(.topenv) 14 | delayedAssign("fn", deparse_to_one_line(.topcall[[1]]), assign.env = env) 15 | delayedAssign("call", deparse_to_one_line(.topcall), assign.env = env) 16 | delayedAssign("topenv", top_env_name(.topenv), assign.env = env) 17 | delayedAssign("location", log_call_location(.logcall), assign.env = env) 18 | 19 | env$time <- .timestamp 20 | env$levelr <- log_level 21 | env$level <- attr(log_level, "level") 22 | 23 | delayedAssign("pid", Sys.getpid(), assign.env = env) 24 | 25 | # R and ns package versions 26 | delayedAssign( 27 | "ns_pkg_version", 28 | tryCatch(as.character(packageVersion(namespace)), error = function(e) NA_character_), 29 | assign.env = env 30 | ) 31 | delayedAssign("r_version", as.character(getRversion()), assign.env = env) 32 | 33 | # stuff from Sys.info 34 | delayedAssign(".sysinfo", Sys.info()) 35 | delayedAssign("node", .sysinfo[["nodename"]], assign.env = env) 36 | delayedAssign("arch", .sysinfo[["machine"]], assign.env = env) 37 | delayedAssign("os_name", .sysinfo[["sysname"]], assign.env = env) 38 | delayedAssign("os_release", .sysinfo[["release"]], assign.env = env) 39 | delayedAssign("os_version", .sysinfo[["version"]], assign.env = env) 40 | delayedAssign("user", .sysinfo[["user"]], assign.env = env) 41 | 42 | env 43 | } 44 | -------------------------------------------------------------------------------- /R/logger-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | ## usethis namespace: end 6 | NULL 7 | -------------------------------------------------------------------------------- /R/try.R: -------------------------------------------------------------------------------- 1 | #' Try to evaluate an expressions and evaluate another expression on 2 | #' exception 3 | #' @param try R expression 4 | #' @param except fallback R expression to be evaluated if `try` fails 5 | #' @export 6 | #' @note Suppress log messages in the `except` namespace if you don't 7 | #' want to throw a `WARN` log message on the exception branch. 8 | #' @examples 9 | #' everything %except% 42 10 | #' everything <- "640kb" 11 | #' everything %except% 42 12 | #' 13 | #' FunDoesNotExist(1:10) %except% sum(1:10) / length(1:10) 14 | #' FunDoesNotExist(1:10) %except% (sum(1:10) / length(1:10)) 15 | #' FunDoesNotExist(1:10) %except% MEAN(1:10) %except% mean(1:10) 16 | #' FunDoesNotExist(1:10) %except% (MEAN(1:10) %except% mean(1:10)) 17 | `%except%` <- function(try, except) { 18 | ## Need to capture these in the evaluation frame of `%except%` but only want 19 | ## to do the work if there's an error 20 | delayedAssign("call", sys.call(-1)) 21 | delayedAssign("env", parent.frame()) 22 | delayedAssign("except_text", deparse(substitute(except))) 23 | delayedAssign("try_text", deparse(substitute(try))) 24 | 25 | tryCatch(try, 26 | error = function(e) { 27 | log_level( 28 | WARN, 29 | paste0("Running '", except_text, "' as '", try_text, "' failed: '", e$message, "'"), 30 | namespace = "except", 31 | .topcall = call, 32 | .topenv = env 33 | ) 34 | except 35 | } 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' Check if R package can be loaded and fails loudly otherwise 2 | #' @param pkg string 3 | #' @param min_version optional minimum version needed 4 | #' @param call Call to include in error message. 5 | #' @export 6 | #' @importFrom utils packageVersion compareVersion 7 | #' @examples 8 | #' f <- function() fail_on_missing_package("foobar") 9 | #' try(f()) 10 | #' g <- function() fail_on_missing_package("stats") 11 | #' g() 12 | fail_on_missing_package <- function(pkg, min_version, call = NULL) { 13 | pc <- call %||% sys.call(which = 1) 14 | if (!requireNamespace(pkg, quietly = TRUE)) { 15 | stop( 16 | sprintf( 17 | "Please install the '%s' package to use %s", 18 | pkg, 19 | deparse(pc[[1]]) 20 | ), 21 | call. = FALSE 22 | ) 23 | } 24 | if (!missing(min_version)) { 25 | if (compareVersion(min_version, as.character(packageVersion(pkg))) == 1) { 26 | stop( 27 | sprintf( 28 | "Please install min. %s version of %s to use %s", 29 | min_version, 30 | pkg, 31 | deparse(pc[[1]]) 32 | ), 33 | call. = FALSE 34 | ) 35 | } 36 | } 37 | } 38 | 39 | 40 | #' Returns the name of the top level environment from which the logger was called 41 | #' @return string 42 | #' @noRd 43 | #' @param .topenv call environment 44 | top_env_name <- function(.topenv = parent.frame()) { 45 | environmentName(topenv(.topenv)) 46 | } 47 | 48 | #' Finds the location of the logger call (file and line) 49 | #' @return list with path and line element 50 | #' @noRd 51 | #' @param .logcall The call that emitted the log 52 | log_call_location <- function(.logcall) { 53 | call_string <- deparse(.logcall) 54 | loc <- list( 55 | path = "", 56 | line = "" 57 | ) 58 | for (trace in .traceback(0)) { 59 | if (identical(call_string, as.vector(trace))) { 60 | ref <- attr(trace, "srcref") 61 | loc$line <- ref[1L] 62 | file <- attr(ref, "srcfile") 63 | if (!is.null(file)) { 64 | loc$path <- normalizePath(file$filename, winslash = "/") 65 | } 66 | break 67 | } 68 | } 69 | loc 70 | } 71 | 72 | #' Deparse and join all lines into a single line 73 | #' 74 | #' Calling `deparse` and joining all the returned lines into a 75 | #' single line, separated by whitespace, and then cleaning up all the 76 | #' duplicated whitespace (except for excessive whitespace in strings 77 | #' between single or double quotes). 78 | #' @param x object to `deparse` 79 | #' @return string 80 | #' @export 81 | deparse_to_one_line <- function(x) { 82 | gsub('\\s+(?=(?:[^\\\'"]*[\\\'"][^\\\'"]*[\\\'"])*[^\\\'"]*$)', " ", 83 | paste(deparse(x), collapse = " "), 84 | perl = TRUE 85 | ) 86 | } 87 | 88 | 89 | #' Catch the log header 90 | #' @return string 91 | #' @param level see [log_levels()] 92 | #' @param namespace string 93 | #' @noRd 94 | #' @examples 95 | #' \dontshow{old <- logger:::namespaces_set()} 96 | #' catch_base_log(INFO, NA_character_) 97 | #' logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") 98 | #' log_layout(logger) 99 | #' catch_base_log(INFO, NA_character_) 100 | #' fun <- function() catch_base_log(INFO, NA_character_) 101 | #' fun() 102 | #' catch_base_log(INFO, NA_character_, .topcall = call("funLONG")) 103 | #' \dontshow{logger:::namespaces_set(old)} 104 | catch_base_log <- function(level, namespace, .topcall = sys.call(-1), .topenv = parent.frame()) { 105 | namespace <- fallback_namespace(namespace) 106 | 107 | old <- log_appender(appender_console, namespace = namespace) 108 | on.exit(log_appender(old, namespace = namespace)) 109 | 110 | # catch error, warning or message 111 | capture.output( 112 | log_level( 113 | level = level, 114 | "", 115 | namespace = namespace, 116 | .topcall = .topcall, 117 | .topenv = .topenv 118 | ), 119 | type = "message" 120 | ) 121 | } 122 | 123 | `%||%` <- function(x, y) { 124 | if (is.null(x)) y else x 125 | } 126 | 127 | in_pkgdown <- function() { 128 | identical(Sys.getenv("IN_PKGDOWN"), "true") 129 | } 130 | 131 | is_testing <- function() { 132 | identical(Sys.getenv("TESTTHAT"), "true") 133 | } 134 | 135 | is_checking_logger <- function() { 136 | Sys.getenv("_R_CHECK_PACKAGE_NAME_", "") == "logger" 137 | } 138 | 139 | needs_stdout <- function() { 140 | in_pkgdown() || is_testing() || is_checking_logger() 141 | } 142 | 143 | # allow mocking 144 | Sys.time <- NULL # nolint 145 | proc.time <- NULL # nolint 146 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | ## init storage for all logger settings 2 | namespaces <- new.env(parent = emptyenv()) 3 | 4 | .onLoad <- function(libname, pkgname) { 5 | namespaces_set(namespaces_default()) 6 | } 7 | 8 | namespaces_reset <- function() { 9 | rm(list = ls(namespaces), envir = namespaces) 10 | namespaces_set(namespaces_default()) 11 | } 12 | 13 | namespaces_default <- function() { 14 | has_glue <- requireNamespace("glue", quietly = TRUE) 15 | 16 | list( 17 | global = list( 18 | default = list( 19 | threshold = as.loglevel(Sys.getenv("LOGGER_LOG_LEVEL", unset = "INFO")), 20 | layout = layout_simple, 21 | formatter = if (has_glue) formatter_glue else formatter_sprintf, 22 | appender = if (needs_stdout()) appender_stdout else appender_console 23 | ) 24 | ), 25 | .logger = list( 26 | default = list( 27 | threshold = ERROR, 28 | layout = layout_simple, 29 | formatter = formatter_sprintf, 30 | appender = if (needs_stdout()) appender_stdout else appender_console 31 | ) 32 | ) 33 | ) 34 | } 35 | 36 | namespaces_set <- function(new = namespaces_default()) { 37 | old <- as.list(namespaces) 38 | 39 | rm(list = ls(namespaces), envir = namespaces) 40 | list2env(new, namespaces) 41 | 42 | invisible(old) 43 | } 44 | 45 | .onAttach <- function(libname, pkgname) { 46 | ## warn user about using sprintf instead of glue due to missing dependency 47 | if (!requireNamespace("glue", quietly = TRUE)) { 48 | packageStartupMessage( 49 | paste( 50 | 'logger: As the "glue" R package is not installed,', 51 | 'using "sprintf" as the default log message formatter instead of "glue".' 52 | ) 53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # logger logger website 17 | 18 | 19 | [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) [![CRAN](https://www.r-pkg.org/badges/version/logger)](https://cran.r-project.org/package=logger) [![Build Status](https://github.com/daroczig/logger/workflows/R-CMD-check/badge.svg)](https://github.com/daroczig/logger/actions) [![Code Coverage](https://codecov.io/gh/daroczig/logger/branch/master/graph/badge.svg)](https://app.codecov.io/gh/daroczig/logger) [![A Mikata Project](https://mikata.dev/img/badge.svg)](https://mikata.dev) 20 | 21 | 22 | A lightweight, modern and flexible logging utility for R -- heavily inspired by the `futile.logger` R package and `logging` Python module. 23 | 24 | ## Installation 25 | 26 | [![CRAN version](https://www.r-pkg.org/badges/version-ago/logger)](https://cran.r-project.org/package=logger) 27 | 28 | ```{r} 29 | #| eval: false 30 | install.packages("logger") 31 | ``` 32 | 33 | The most recent, development version of `logger` can also be installed from GitHub: 34 | 35 | ```{r} 36 | #| eval: false 37 | # install.packages("pak") 38 | pak::pak("daroczig/logger") 39 | ``` 40 | 41 | ## Quick example 42 | 43 | Setting the log level threshold to something low and logging various messages in ad-hoc and programmatic ways: 44 | 45 | ```{r} 46 | #| include: false 47 | library(logger) 48 | log_appender(appender_stdout) 49 | ``` 50 | 51 | ```{r} 52 | library(logger) 53 | log_threshold(DEBUG) 54 | log_info("Script starting up...") 55 | 56 | pkgs <- available.packages() 57 | log_info("There are {nrow(pkgs)} R packages hosted on CRAN!") 58 | 59 | for (letter in letters) { 60 | lpkgs <- sum(grepl(letter, pkgs[, "Package"], ignore.case = TRUE)) 61 | log_level( 62 | if (lpkgs < 5000) TRACE else DEBUG, 63 | "{lpkgs} R packages including the {shQuote(letter)} letter" 64 | ) 65 | } 66 | 67 | log_warn("There might be many, like {1:2} or more warnings!!!") 68 | ``` 69 | 70 | You can even use a custom log layout to render the log records with colors, as you can see in `layout_glue_colors()`: 71 | 72 | colored log output 73 | 74 | But you could set up any custom colors and layout, eg using custom colors only for the log levels, make it grayscale, include the calling function or R package namespace with specific colors etc. For more details, see `vignette("write_custom_extensions")`. 75 | 76 | ## Related work 77 | 78 | There are many other logging packages available on CRAN: 79 | 80 | - [`futile.logger`](https://cran.r-project.org/package=futile.logger): probably the most popular `log4j` variant (and I'm a big fan) 81 | - [`logging`](https://cran.r-project.org/package=logging): just like Python's `logging` package 82 | - [`lgr`](https://cran.r-project.org/package=lgr): built on top of R6. 83 | - [`loggit`](https://cran.r-project.org/package=loggit): capture `message`, `warning` and `stop` function messages in a JSON file 84 | - [`log4r`](https://cran.r-project.org/package=log4r): `log4j`-based, object-oriented logger 85 | - [`rsyslog`](https://cran.r-project.org/package=rsyslog): logging to `syslog` on 'POSIX'-compatible operating systems 86 | - [`lumberjack`](https://cran.r-project.org/package=lumberjack): provides a special operator to log changes in data 87 | 88 | Why use logger? I decided to write the `n+1`th extensible `log4j` logger that fits my liking --- and hopefully yours as well --- with the aim to: 89 | 90 | - Keep it close to `log4j`. 91 | - Respect the modern function/variable naming conventions and general R coding style. 92 | - By default, rely on `glue()` when it comes to formatting / rendering log messages, but keep it flexible if others prefer `sprintf()` (e.g. for performance reasons) or other functions. 93 | - Support vectorization (eg passing a vector to be logged on multiple lines). 94 | - Make it easy to extend with new features (e.g. custom layouts, message formats and output). 95 | - Prepare for writing to various services, streams etc 96 | - Provide support for namespaces, preferably automatically finding and creating a custom namespace for all R packages writing log messages, each with optionally configurable log level threshold, message and output formats. 97 | - Allow stacking loggers to implement logger hierarchy -- even within a namespace, so that the very same `log` call can write all the `TRACE` log messages to the console, while only pushing `ERROR`s to DataDog and eg `INFO` messages to CloudWatch. 98 | - Optionally colorize log message based on the log level. 99 | - Make logging fun! 100 | 101 | Welcome to the [Bazaar](https://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar)! If you already use any of the above packages for logging, you might find `vignette("migration")` useful. 102 | 103 | ## Interested in more details? 104 | 105 | ::: .pkgdown-hide 106 | 107 | Check out the main documentation site at or the vignettes on the below topics: 108 | 109 | * [Introduction to logger](https://daroczig.github.io/logger/articles/Intro.html) 110 | * [The Anatomy of a Log Request](https://daroczig.github.io/logger/articles/anatomy.html) 111 | * [Customizing the Format and the Destination of a Log Record](https://daroczig.github.io/logger/articles/customize_logger.html) 112 | * [Writing Custom Logger Extensions](https://daroczig.github.io/logger/articles/write_custom_extensions.html) 113 | * [Migration Guide from other logging packages](https://daroczig.github.io/logger/articles/migration.html) 114 | * [Logging from R Packages](https://daroczig.github.io/logger/articles/r_packages.html) 115 | * [Simple Benchmarks on Performance](https://daroczig.github.io/logger/articles/performance.html) 116 | 117 | ::: 118 | 119 | If you prefer visual content, you can watch the video recording of the "Getting things logged" talk at RStudio::conf(2020): 120 | 121 | [![Gergely Daroczi presenting "Getting things logged" on using the `logger` R package at the RStudio conference in 2020](https://img.youtube.com/vi/_rUuBbml9dU/0.jpg)](https://www.youtube.com/watch?v=_rUuBbml9dU) 122 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://daroczig.github.io/logger 2 | 3 | template: 4 | bootstrap: 5 5 | 6 | authors: 7 | System1: 8 | href: https://system1.com 9 | html: 10 | toc: 11 | depth: 3 12 | 13 | reference: 14 | 15 | - title: Key logging functions 16 | contents: 17 | - log_level 18 | - TRACE 19 | - log_threshold 20 | 21 | - title: Other logging helpers 22 | contents: 23 | - log_eval 24 | - log_failure 25 | - log_tictoc 26 | - log_elapsed 27 | - log_separator 28 | - log_with_separator 29 | - with_log_threshold 30 | - log_chunk_time 31 | 32 | - title: Appenders 33 | desc: > 34 | Log appenders define where logging output should be sent to. 35 | contents: 36 | - log_appender 37 | - starts_with("appender_") 38 | 39 | - title: Formatters 40 | desc: > 41 | Log formatters control how the inputs to the `log_` functions are converted 42 | to a string. The default is `formatter_glue()` when `glue` is installed. 43 | contents: 44 | - log_formatter 45 | - starts_with("formatter_") 46 | - skip_formatter 47 | 48 | - title: Layouts 49 | desc: > 50 | Logging layouts control what is sent to the appender. They always include 51 | the logged string, but might also include the timestamp, log level, etc. 52 | contents: 53 | - log_layout 54 | - starts_with("layout_") 55 | 56 | - title: Hooks for automated logging 57 | contents: 58 | - log_shiny_input_changes 59 | - log_messages 60 | - log_warnings 61 | - log_errors 62 | 63 | - title: Other helpers 64 | contents: 65 | - colorize_by_log_level 66 | - logger 67 | - delete_logger_index 68 | - "%except%" 69 | 70 | - title: Dev tools 71 | contents: 72 | - as.loglevel 73 | - deparse_to_one_line 74 | - fail_on_missing_package 75 | - get_logger_meta_variables 76 | - log_namespaces 77 | - log_indices 78 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /inst/demo-packages/logger-tester-package/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Type: Package 2 | Package: logger.tester 3 | Authors@R: c( 4 | person("Gergely", "Daroczi", , "daroczig@rapporter.net", role = c("aut", "cre")), 5 | person("System1", role = c("fnd")) 6 | ) 7 | Title: Dummy package providing testing functions for logger 8 | Description: Dummy package providing testing functions for logger 9 | Version: 0.1 10 | Date: 2018-07-04 11 | URL: https://github.com/daroczig/logger 12 | RoxygenNote: 7.3.2 13 | License: MIT + file LICENSE 14 | Imports: 15 | logger 16 | -------------------------------------------------------------------------------- /inst/demo-packages/logger-tester-package/LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2024 2 | COPYRIGHT HOLDER: logger authors 3 | -------------------------------------------------------------------------------- /inst/demo-packages/logger-tester-package/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(logger_info_tester_function) 4 | export(logger_tester_function) 5 | importFrom(logger,log_info) 6 | importFrom(logger,log_level) 7 | -------------------------------------------------------------------------------- /inst/demo-packages/logger-tester-package/R/tester.R: -------------------------------------------------------------------------------- 1 | #' Testing logging from package 2 | #' @param level foo 3 | #' @param msg bar 4 | #' @export 5 | #' @importFrom logger log_level 6 | logger_tester_function <- function(level, msg) { 7 | set.seed(1014) 8 | x <- runif(1) 9 | log_level(level, "{msg} {x}") 10 | } 11 | 12 | #' Testing logging INFO from package 13 | #' @param msg bar 14 | #' @export 15 | #' @importFrom logger log_info 16 | logger_info_tester_function <- function(msg) { 17 | everything <- 42 18 | log_info("{msg} {everything}") 19 | } 20 | -------------------------------------------------------------------------------- /inst/demo-packages/logger-tester-package/man/logger_info_tester_function.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tester.R 3 | \name{logger_info_tester_function} 4 | \alias{logger_info_tester_function} 5 | \title{Testing logging INFO from package} 6 | \usage{ 7 | logger_info_tester_function(msg) 8 | } 9 | \arguments{ 10 | \item{msg}{bar} 11 | } 12 | \description{ 13 | Testing logging INFO from package 14 | } 15 | -------------------------------------------------------------------------------- /inst/demo-packages/logger-tester-package/man/logger_tester_function.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tester.R 3 | \name{logger_tester_function} 4 | \alias{logger_tester_function} 5 | \title{Testing logging from package} 6 | \usage{ 7 | logger_tester_function(level, msg) 8 | } 9 | \arguments{ 10 | \item{level}{foo} 11 | 12 | \item{msg}{bar} 13 | } 14 | \description{ 15 | Testing logging from package 16 | } 17 | -------------------------------------------------------------------------------- /man/appender_async.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_async} 4 | \alias{appender_async} 5 | \title{Delays executing the actual appender function to the future in a 6 | background process to avoid blocking the main R session} 7 | \usage{ 8 | appender_async( 9 | appender, 10 | namespace = "async_logger", 11 | init = function() log_info("Background process started") 12 | ) 13 | } 14 | \arguments{ 15 | \item{appender}{a \code{\link[=log_appender]{log_appender()}} function with a \code{generator} 16 | attribute (TODO note not required, all fn will be passed if 17 | not)} 18 | 19 | \item{namespace}{\code{logger} namespace to use for logging messages on 20 | starting up the background process} 21 | 22 | \item{init}{optional function to run in the background process that 23 | is useful to set up the environment required for logging, eg if 24 | the \code{appender} function requires some extra packages to be 25 | loaded or some environment variables to be set etc} 26 | } 27 | \value{ 28 | function taking \code{lines} argument 29 | } 30 | \description{ 31 | Delays executing the actual appender function to the future in a 32 | background process to avoid blocking the main R session 33 | } 34 | \note{ 35 | This functionality depends on the \pkg{mirai} package. 36 | } 37 | \examples{ 38 | \dontrun{ 39 | appender_file_slow <- function(file) { 40 | force(file) 41 | function(lines) { 42 | Sys.sleep(1) 43 | cat(lines, sep = "\n", file = file, append = TRUE) 44 | } 45 | } 46 | 47 | ## log what's happening in the background 48 | log_threshold(TRACE, namespace = "async_logger") 49 | log_appender(appender_console, namespace = "async_logger") 50 | 51 | ## start async appender 52 | t <- tempfile() 53 | log_info("Logging in the background to {t}") 54 | 55 | ## use async appender 56 | log_appender(appender_async(appender_file_slow(file = t))) 57 | log_info("Was this slow?") 58 | system.time(for (i in 1:25) log_info(i)) 59 | 60 | readLines(t) 61 | Sys.sleep(10) 62 | readLines(t) 63 | 64 | } 65 | } 66 | \seealso{ 67 | Other log_appenders: 68 | \code{\link{appender_console}()}, 69 | \code{\link{appender_file}()}, 70 | \code{\link{appender_kinesis}()}, 71 | \code{\link{appender_pushbullet}()}, 72 | \code{\link{appender_slack}()}, 73 | \code{\link{appender_stdout}()}, 74 | \code{\link{appender_syslog}()}, 75 | \code{\link{appender_tee}()}, 76 | \code{\link{appender_telegram}()} 77 | } 78 | \concept{log_appenders} 79 | -------------------------------------------------------------------------------- /man/appender_console.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_console} 4 | \alias{appender_console} 5 | \alias{appender_stderr} 6 | \title{Append log record to stderr} 7 | \usage{ 8 | appender_console(lines) 9 | 10 | appender_stderr(lines) 11 | } 12 | \arguments{ 13 | \item{lines}{character vector} 14 | } 15 | \description{ 16 | Append log record to stderr 17 | } 18 | \seealso{ 19 | Other log_appenders: 20 | \code{\link{appender_async}()}, 21 | \code{\link{appender_file}()}, 22 | \code{\link{appender_kinesis}()}, 23 | \code{\link{appender_pushbullet}()}, 24 | \code{\link{appender_slack}()}, 25 | \code{\link{appender_stdout}()}, 26 | \code{\link{appender_syslog}()}, 27 | \code{\link{appender_tee}()}, 28 | \code{\link{appender_telegram}()} 29 | } 30 | \concept{log_appenders} 31 | -------------------------------------------------------------------------------- /man/appender_file.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_file} 4 | \alias{appender_file} 5 | \title{Append log messages to a file} 6 | \usage{ 7 | appender_file( 8 | file, 9 | append = TRUE, 10 | max_lines = Inf, 11 | max_bytes = Inf, 12 | max_files = 1L 13 | ) 14 | } 15 | \arguments{ 16 | \item{file}{path} 17 | 18 | \item{append}{boolean passed to \code{cat} defining if the file should 19 | be overwritten with the most recent log message instead of 20 | appending} 21 | 22 | \item{max_lines}{numeric specifying the maximum number of lines 23 | allowed in a file before rotating} 24 | 25 | \item{max_bytes}{numeric specifying the maximum number of bytes 26 | allowed in a file before rotating} 27 | 28 | \item{max_files}{integer specifying the maximum number of files to 29 | be used in rotation} 30 | } 31 | \value{ 32 | function taking \code{lines} argument 33 | } 34 | \description{ 35 | Log messages are written to a file with basic log rotation: when 36 | max number of lines or bytes is defined to be other than \code{Inf}, 37 | then the log file is renamed with a \code{.1} suffix and a new log file 38 | is created. The renaming happens recursively (eg \code{logfile.1} 39 | renamed to \code{logfile.2}) until the specified \code{max_files}, then the 40 | oldest file (\code{logfile.{max_files-1}}) is deleted. 41 | } 42 | \examples{ 43 | \dontshow{old <- logger:::namespaces_set()} 44 | ## ########################################################################## 45 | ## simple example logging to a file 46 | t <- tempfile() 47 | log_appender(appender_file(t)) 48 | for (i in 1:25) log_info(i) 49 | readLines(t) 50 | 51 | ## ########################################################################## 52 | ## more complex example of logging to file 53 | ## rotated after every 3rd line up to max 5 files 54 | 55 | ## create a folder storing the log files 56 | t <- tempfile() 57 | dir.create(t) 58 | f <- file.path(t, "log") 59 | 60 | ## define the file logger with log rotation enabled 61 | log_appender(appender_file(f, max_lines = 3, max_files = 5L)) 62 | 63 | ## enable internal logging to see what's actually happening in the logrotate steps 64 | log_threshold(TRACE, namespace = ".logger") 65 | ## log 25 messages 66 | for (i in 1:25) log_info(i) 67 | 68 | ## see what was logged 69 | lapply(list.files(t, full.names = TRUE), function(t) { 70 | cat("\n##", t, "\n") 71 | cat(readLines(t), sep = "\n") 72 | }) 73 | 74 | \dontshow{logger:::namespaces_set(old)} 75 | } 76 | \seealso{ 77 | Other log_appenders: 78 | \code{\link{appender_async}()}, 79 | \code{\link{appender_console}()}, 80 | \code{\link{appender_kinesis}()}, 81 | \code{\link{appender_pushbullet}()}, 82 | \code{\link{appender_slack}()}, 83 | \code{\link{appender_stdout}()}, 84 | \code{\link{appender_syslog}()}, 85 | \code{\link{appender_tee}()}, 86 | \code{\link{appender_telegram}()} 87 | } 88 | \concept{log_appenders} 89 | -------------------------------------------------------------------------------- /man/appender_kinesis.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_kinesis} 4 | \alias{appender_kinesis} 5 | \title{Send log messages to a Amazon Kinesis stream} 6 | \usage{ 7 | appender_kinesis(stream) 8 | } 9 | \arguments{ 10 | \item{stream}{name of the Kinesis stream} 11 | } 12 | \value{ 13 | function taking \code{lines} and optional \code{partition_key} 14 | argument 15 | } 16 | \description{ 17 | Send log messages to a Amazon Kinesis stream 18 | } 19 | \note{ 20 | This functionality depends on the \pkg{botor} package. 21 | } 22 | \seealso{ 23 | Other log_appenders: 24 | \code{\link{appender_async}()}, 25 | \code{\link{appender_console}()}, 26 | \code{\link{appender_file}()}, 27 | \code{\link{appender_pushbullet}()}, 28 | \code{\link{appender_slack}()}, 29 | \code{\link{appender_stdout}()}, 30 | \code{\link{appender_syslog}()}, 31 | \code{\link{appender_tee}()}, 32 | \code{\link{appender_telegram}()} 33 | } 34 | \concept{log_appenders} 35 | -------------------------------------------------------------------------------- /man/appender_pushbullet.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_pushbullet} 4 | \alias{appender_pushbullet} 5 | \title{Send log messages to Pushbullet} 6 | \usage{ 7 | appender_pushbullet(...) 8 | } 9 | \arguments{ 10 | \item{...}{parameters passed to \link[RPushbullet:pbPost]{RPushbullet::pbPost}, such as \code{recipients} 11 | or \code{apikey}, although it's probably much better to set all these 12 | in the \verb{~/.rpushbullet.json} as per package docs at 13 | \url{http://dirk.eddelbuettel.com/code/rpushbullet.html}} 14 | } 15 | \description{ 16 | Send log messages to Pushbullet 17 | } 18 | \note{ 19 | This functionality depends on the \pkg{RPushbullet} package. 20 | } 21 | \seealso{ 22 | Other log_appenders: 23 | \code{\link{appender_async}()}, 24 | \code{\link{appender_console}()}, 25 | \code{\link{appender_file}()}, 26 | \code{\link{appender_kinesis}()}, 27 | \code{\link{appender_slack}()}, 28 | \code{\link{appender_stdout}()}, 29 | \code{\link{appender_syslog}()}, 30 | \code{\link{appender_tee}()}, 31 | \code{\link{appender_telegram}()} 32 | } 33 | \concept{log_appenders} 34 | -------------------------------------------------------------------------------- /man/appender_slack.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_slack} 4 | \alias{appender_slack} 5 | \title{Send log messages to a Slack channel} 6 | \usage{ 7 | appender_slack( 8 | channel = Sys.getenv("SLACK_CHANNEL"), 9 | username = Sys.getenv("SLACK_USERNAME"), 10 | icon_emoji = Sys.getenv("SLACK_ICON_EMOJI"), 11 | api_token = Sys.getenv("SLACK_API_TOKEN"), 12 | preformatted = TRUE 13 | ) 14 | } 15 | \arguments{ 16 | \item{channel}{Slack channel name with a hashtag prefix for public 17 | channel and no prefix for private channels} 18 | 19 | \item{username}{Slack (bot) username} 20 | 21 | \item{icon_emoji}{optional override for the bot icon} 22 | 23 | \item{api_token}{Slack API token} 24 | 25 | \item{preformatted}{use code tags around the message?} 26 | } 27 | \value{ 28 | function taking \code{lines} argument 29 | } 30 | \description{ 31 | Send log messages to a Slack channel 32 | } 33 | \note{ 34 | This functionality depends on the \pkg{slackr} package. 35 | } 36 | \seealso{ 37 | Other log_appenders: 38 | \code{\link{appender_async}()}, 39 | \code{\link{appender_console}()}, 40 | \code{\link{appender_file}()}, 41 | \code{\link{appender_kinesis}()}, 42 | \code{\link{appender_pushbullet}()}, 43 | \code{\link{appender_stdout}()}, 44 | \code{\link{appender_syslog}()}, 45 | \code{\link{appender_tee}()}, 46 | \code{\link{appender_telegram}()} 47 | } 48 | \concept{log_appenders} 49 | -------------------------------------------------------------------------------- /man/appender_stdout.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_stdout} 4 | \alias{appender_stdout} 5 | \title{Append log record to stdout} 6 | \usage{ 7 | appender_stdout(lines) 8 | } 9 | \arguments{ 10 | \item{lines}{character vector} 11 | } 12 | \description{ 13 | Append log record to stdout 14 | } 15 | \seealso{ 16 | Other log_appenders: 17 | \code{\link{appender_async}()}, 18 | \code{\link{appender_console}()}, 19 | \code{\link{appender_file}()}, 20 | \code{\link{appender_kinesis}()}, 21 | \code{\link{appender_pushbullet}()}, 22 | \code{\link{appender_slack}()}, 23 | \code{\link{appender_syslog}()}, 24 | \code{\link{appender_tee}()}, 25 | \code{\link{appender_telegram}()} 26 | } 27 | \concept{log_appenders} 28 | -------------------------------------------------------------------------------- /man/appender_syslog.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_syslog} 4 | \alias{appender_syslog} 5 | \title{Send log messages to the POSIX system log} 6 | \usage{ 7 | appender_syslog(identifier, ...) 8 | } 9 | \arguments{ 10 | \item{identifier}{A string identifying the process.} 11 | 12 | \item{...}{Further arguments passed on to \code{\link[rsyslog:syslog]{rsyslog::open_syslog()}}.} 13 | } 14 | \value{ 15 | function taking \code{lines} argument 16 | } 17 | \description{ 18 | Send log messages to the POSIX system log 19 | } 20 | \note{ 21 | This functionality depends on the \pkg{rsyslog} package. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | if (requireNamespace("rsyslog", quietly = TRUE)) { 26 | log_appender(appender_syslog("test")) 27 | log_info("Test message.") 28 | } 29 | } 30 | } 31 | \seealso{ 32 | Other log_appenders: 33 | \code{\link{appender_async}()}, 34 | \code{\link{appender_console}()}, 35 | \code{\link{appender_file}()}, 36 | \code{\link{appender_kinesis}()}, 37 | \code{\link{appender_pushbullet}()}, 38 | \code{\link{appender_slack}()}, 39 | \code{\link{appender_stdout}()}, 40 | \code{\link{appender_tee}()}, 41 | \code{\link{appender_telegram}()} 42 | } 43 | \concept{log_appenders} 44 | -------------------------------------------------------------------------------- /man/appender_syslognet.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_syslognet} 4 | \alias{appender_syslognet} 5 | \title{Send log messages to a network syslog server} 6 | \usage{ 7 | appender_syslognet(identifier, server, port = 601L) 8 | } 9 | \arguments{ 10 | \item{identifier}{program/function identification (string).} 11 | 12 | \item{server}{machine where syslog daemon runs (string).} 13 | 14 | \item{port}{port where syslog daemon listens (integer).} 15 | } 16 | \value{ 17 | A function taking a \code{lines} argument. 18 | } 19 | \description{ 20 | Send log messages to a network syslog server 21 | } 22 | \note{ 23 | This functionality depends on the \pkg{syslognet} package. 24 | } 25 | \examples{ 26 | \dontrun{ 27 | if (requireNamespace("syslognet", quietly = TRUE)) { 28 | log_appender(appender_syslognet("test_app", "remoteserver")) 29 | log_info("Test message.") 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /man/appender_tee.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_tee} 4 | \alias{appender_tee} 5 | \title{Append log messages to a file and stdout as well} 6 | \usage{ 7 | appender_tee( 8 | file, 9 | append = TRUE, 10 | max_lines = Inf, 11 | max_bytes = Inf, 12 | max_files = 1L 13 | ) 14 | } 15 | \arguments{ 16 | \item{file}{path} 17 | 18 | \item{append}{boolean passed to \code{cat} defining if the file should 19 | be overwritten with the most recent log message instead of 20 | appending} 21 | 22 | \item{max_lines}{numeric specifying the maximum number of lines 23 | allowed in a file before rotating} 24 | 25 | \item{max_bytes}{numeric specifying the maximum number of bytes 26 | allowed in a file before rotating} 27 | 28 | \item{max_files}{integer specifying the maximum number of files to 29 | be used in rotation} 30 | } 31 | \value{ 32 | function taking \code{lines} argument 33 | } 34 | \description{ 35 | This appends log messages to both console and a file. The same 36 | rotation options are available as in \code{\link[=appender_file]{appender_file()}}. 37 | } 38 | \seealso{ 39 | Other log_appenders: 40 | \code{\link{appender_async}()}, 41 | \code{\link{appender_console}()}, 42 | \code{\link{appender_file}()}, 43 | \code{\link{appender_kinesis}()}, 44 | \code{\link{appender_pushbullet}()}, 45 | \code{\link{appender_slack}()}, 46 | \code{\link{appender_stdout}()}, 47 | \code{\link{appender_syslog}()}, 48 | \code{\link{appender_telegram}()} 49 | } 50 | \concept{log_appenders} 51 | -------------------------------------------------------------------------------- /man/appender_telegram.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_telegram} 4 | \alias{appender_telegram} 5 | \title{Send log messages to a Telegram chat} 6 | \usage{ 7 | appender_telegram( 8 | chat_id = Sys.getenv("TELEGRAM_CHAT_ID"), 9 | bot_token = Sys.getenv("TELEGRAM_BOT_TOKEN"), 10 | parse_mode = NULL 11 | ) 12 | } 13 | \arguments{ 14 | \item{chat_id}{Unique identifier for the target chat or username of 15 | the target channel (in the format @channelusername)} 16 | 17 | \item{bot_token}{Telegram Authorization token} 18 | 19 | \item{parse_mode}{Message parse mode. Allowed values: Markdown or 20 | HTML} 21 | } 22 | \value{ 23 | function taking \code{lines} argument 24 | } 25 | \description{ 26 | Send log messages to a Telegram chat 27 | } 28 | \note{ 29 | This functionality depends on the \pkg{telegram} package. 30 | } 31 | \seealso{ 32 | Other log_appenders: 33 | \code{\link{appender_async}()}, 34 | \code{\link{appender_console}()}, 35 | \code{\link{appender_file}()}, 36 | \code{\link{appender_kinesis}()}, 37 | \code{\link{appender_pushbullet}()}, 38 | \code{\link{appender_slack}()}, 39 | \code{\link{appender_stdout}()}, 40 | \code{\link{appender_syslog}()}, 41 | \code{\link{appender_tee}()} 42 | } 43 | \concept{log_appenders} 44 | -------------------------------------------------------------------------------- /man/appender_void.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/appenders.R 3 | \name{appender_void} 4 | \alias{appender_void} 5 | \title{Dummy appender not delivering the log record to anywhere} 6 | \usage{ 7 | appender_void(lines) 8 | } 9 | \arguments{ 10 | \item{lines}{character vector} 11 | } 12 | \description{ 13 | Dummy appender not delivering the log record to anywhere 14 | } 15 | -------------------------------------------------------------------------------- /man/as.loglevel.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/levels.R 3 | \name{as.loglevel} 4 | \alias{as.loglevel} 5 | \title{Convert R object into a logger log-level} 6 | \usage{ 7 | as.loglevel(x) 8 | } 9 | \arguments{ 10 | \item{x}{string or integer} 11 | } 12 | \value{ 13 | pander log-level, e.g. \code{INFO} 14 | } 15 | \description{ 16 | Convert R object into a logger log-level 17 | } 18 | \examples{ 19 | as.loglevel(INFO) 20 | as.loglevel(400L) 21 | as.loglevel(400) 22 | } 23 | -------------------------------------------------------------------------------- /man/colorize_by_log_level.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/color.R 3 | \name{colorize_by_log_level} 4 | \alias{colorize_by_log_level} 5 | \alias{grayscale_by_log_level} 6 | \title{Color string by the related log level} 7 | \usage{ 8 | colorize_by_log_level(msg, level) 9 | 10 | grayscale_by_log_level(msg, level) 11 | } 12 | \arguments{ 13 | \item{msg}{String to color.} 14 | 15 | \item{level}{see \code{\link[=log_levels]{log_levels()}}} 16 | } 17 | \value{ 18 | A string with ANSI escape codes. 19 | } 20 | \description{ 21 | Color log messages according to their severity with either a rainbow 22 | or grayscale color scheme. The greyscale theme assumes a dark background on 23 | the terminal. 24 | } 25 | \examples{ 26 | \dontshow{if (requireNamespace("crayon")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 27 | cat(colorize_by_log_level("foobar", FATAL), "\n") 28 | cat(colorize_by_log_level("foobar", ERROR), "\n") 29 | cat(colorize_by_log_level("foobar", WARN), "\n") 30 | cat(colorize_by_log_level("foobar", SUCCESS), "\n") 31 | cat(colorize_by_log_level("foobar", INFO), "\n") 32 | cat(colorize_by_log_level("foobar", DEBUG), "\n") 33 | cat(colorize_by_log_level("foobar", TRACE), "\n") 34 | 35 | cat(grayscale_by_log_level("foobar", FATAL), "\n") 36 | cat(grayscale_by_log_level("foobar", ERROR), "\n") 37 | cat(grayscale_by_log_level("foobar", WARN), "\n") 38 | cat(grayscale_by_log_level("foobar", SUCCESS), "\n") 39 | cat(grayscale_by_log_level("foobar", INFO), "\n") 40 | cat(grayscale_by_log_level("foobar", DEBUG), "\n") 41 | cat(grayscale_by_log_level("foobar", TRACE), "\n") 42 | \dontshow{\}) # examplesIf} 43 | } 44 | -------------------------------------------------------------------------------- /man/delete_logger_index.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{delete_logger_index} 4 | \alias{delete_logger_index} 5 | \title{Delete an index from a logger namespace} 6 | \usage{ 7 | delete_logger_index(namespace = "global", index) 8 | } 9 | \arguments{ 10 | \item{namespace}{logger namespace} 11 | 12 | \item{index}{index of the logger within the namespace} 13 | } 14 | \description{ 15 | Delete an index from a logger namespace 16 | } 17 | -------------------------------------------------------------------------------- /man/deparse_to_one_line.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{deparse_to_one_line} 4 | \alias{deparse_to_one_line} 5 | \title{Deparse and join all lines into a single line} 6 | \usage{ 7 | deparse_to_one_line(x) 8 | } 9 | \arguments{ 10 | \item{x}{object to \code{deparse}} 11 | } 12 | \value{ 13 | string 14 | } 15 | \description{ 16 | Calling \code{deparse} and joining all the returned lines into a 17 | single line, separated by whitespace, and then cleaning up all the 18 | duplicated whitespace (except for excessive whitespace in strings 19 | between single or double quotes). 20 | } 21 | -------------------------------------------------------------------------------- /man/fail_on_missing_package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{fail_on_missing_package} 4 | \alias{fail_on_missing_package} 5 | \title{Check if R package can be loaded and fails loudly otherwise} 6 | \usage{ 7 | fail_on_missing_package(pkg, min_version, call = NULL) 8 | } 9 | \arguments{ 10 | \item{pkg}{string} 11 | 12 | \item{min_version}{optional minimum version needed} 13 | 14 | \item{call}{Call to include in error message.} 15 | } 16 | \description{ 17 | Check if R package can be loaded and fails loudly otherwise 18 | } 19 | \examples{ 20 | f <- function() fail_on_missing_package("foobar") 21 | try(f()) 22 | g <- function() fail_on_missing_package("stats") 23 | g() 24 | } 25 | -------------------------------------------------------------------------------- /man/figures/colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/man/figures/colors.png -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/man/figures/logo.png -------------------------------------------------------------------------------- /man/formatter_cli.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{formatter_cli} 4 | \alias{formatter_cli} 5 | \title{Apply \code{\link[cli:cli_text]{cli::cli_text()}} to format string with cli syntax} 6 | \usage{ 7 | formatter_cli( 8 | ..., 9 | .logcall = sys.call(), 10 | .topcall = sys.call(-1), 11 | .topenv = parent.frame() 12 | ) 13 | } 14 | \arguments{ 15 | \item{...}{passed to \code{\link[cli:cli_text]{cli::cli_text()}} for the text interpolation} 16 | 17 | \item{.logcall}{the logging call being evaluated (useful in 18 | formatters and layouts when you want to have access to the raw, 19 | unevaluated R expression)} 20 | 21 | \item{.topcall}{R expression from which the logging function was 22 | called (useful in formatters and layouts to extract the calling 23 | function's name or arguments)} 24 | 25 | \item{.topenv}{original frame of the \code{.topcall} calling function 26 | where the formatter function will be evaluated and that is used 27 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 28 | } 29 | \value{ 30 | character vector 31 | } 32 | \description{ 33 | Apply \code{\link[cli:cli_text]{cli::cli_text()}} to format string with cli syntax 34 | } 35 | \seealso{ 36 | Other log_formatters: 37 | \code{\link{formatter_glue}()}, 38 | \code{\link{formatter_glue_or_sprintf}()}, 39 | \code{\link{formatter_glue_safe}()}, 40 | \code{\link{formatter_json}()}, 41 | \code{\link{formatter_logging}()}, 42 | \code{\link{formatter_pander}()}, 43 | \code{\link{formatter_paste}()}, 44 | \code{\link{formatter_sprintf}()} 45 | } 46 | \concept{log_formatters} 47 | -------------------------------------------------------------------------------- /man/formatter_glue.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{formatter_glue} 4 | \alias{formatter_glue} 5 | \title{Apply \code{\link[glue:glue]{glue::glue()}} to convert R objects into a character vector} 6 | \usage{ 7 | formatter_glue( 8 | ..., 9 | .logcall = sys.call(), 10 | .topcall = sys.call(-1), 11 | .topenv = parent.frame() 12 | ) 13 | } 14 | \arguments{ 15 | \item{...}{passed to \code{\link[glue:glue]{glue::glue()}} for the text interpolation} 16 | 17 | \item{.logcall}{the logging call being evaluated (useful in 18 | formatters and layouts when you want to have access to the raw, 19 | unevaluated R expression)} 20 | 21 | \item{.topcall}{R expression from which the logging function was 22 | called (useful in formatters and layouts to extract the calling 23 | function's name or arguments)} 24 | 25 | \item{.topenv}{original frame of the \code{.topcall} calling function 26 | where the formatter function will be evaluated and that is used 27 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 28 | } 29 | \value{ 30 | character vector 31 | } 32 | \description{ 33 | Apply \code{\link[glue:glue]{glue::glue()}} to convert R objects into a character vector 34 | } 35 | \note{ 36 | Although this is the default log message formatter function, 37 | but when \pkg{glue} is not installed, \code{\link[=formatter_sprintf]{formatter_sprintf()}} 38 | will be used as a fallback. 39 | } 40 | \seealso{ 41 | Other log_formatters: 42 | \code{\link{formatter_cli}()}, 43 | \code{\link{formatter_glue_or_sprintf}()}, 44 | \code{\link{formatter_glue_safe}()}, 45 | \code{\link{formatter_json}()}, 46 | \code{\link{formatter_logging}()}, 47 | \code{\link{formatter_pander}()}, 48 | \code{\link{formatter_paste}()}, 49 | \code{\link{formatter_sprintf}()} 50 | } 51 | \concept{log_formatters} 52 | -------------------------------------------------------------------------------- /man/formatter_glue_or_sprintf.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{formatter_glue_or_sprintf} 4 | \alias{formatter_glue_or_sprintf} 5 | \title{Apply \code{\link[glue:glue]{glue::glue()}} and \code{\link[=sprintf]{sprintf()}}} 6 | \usage{ 7 | formatter_glue_or_sprintf( 8 | msg, 9 | ..., 10 | .logcall = sys.call(), 11 | .topcall = sys.call(-1), 12 | .topenv = parent.frame() 13 | ) 14 | } 15 | \arguments{ 16 | \item{msg}{passed to \code{\link[=sprintf]{sprintf()}} as \code{fmt} or handled as part of \code{...} 17 | in \code{\link[glue:glue]{glue::glue()}}} 18 | 19 | \item{...}{passed to \code{\link[glue:glue]{glue::glue()}} for the text interpolation} 20 | 21 | \item{.logcall}{the logging call being evaluated (useful in 22 | formatters and layouts when you want to have access to the raw, 23 | unevaluated R expression)} 24 | 25 | \item{.topcall}{R expression from which the logging function was 26 | called (useful in formatters and layouts to extract the calling 27 | function's name or arguments)} 28 | 29 | \item{.topenv}{original frame of the \code{.topcall} calling function 30 | where the formatter function will be evaluated and that is used 31 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 32 | } 33 | \value{ 34 | character vector 35 | } 36 | \description{ 37 | The best of both words: using both formatter functions in your log 38 | messages, which can be useful eg if you are migrating from 39 | \code{\link[=sprintf]{sprintf()}} formatted log messages to \code{\link[glue:glue]{glue::glue()}} or similar. 40 | } 41 | \details{ 42 | Note that this function tries to be smart when passing arguments to 43 | \code{\link[glue:glue]{glue::glue()}} and \code{\link[=sprintf]{sprintf()}}, but might fail with some edge cases, and 44 | returns an unformatted string. 45 | } 46 | \examples{ 47 | formatter_glue_or_sprintf("{a} + {b} = \%s", a = 2, b = 3, 5) 48 | formatter_glue_or_sprintf("{pi} * {2} = \%s", pi * 2) 49 | formatter_glue_or_sprintf("{pi} * {2} = {pi*2}") 50 | 51 | formatter_glue_or_sprintf("Hi ", "{c('foo', 'bar')}, did you know that 2*4={2*4}") 52 | formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4={2*4}") 53 | formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4=\%s", 2 * 4) 54 | formatter_glue_or_sprintf("Hi \%s, did you know that 2*4={2*4}", c("foo", "bar")) 55 | formatter_glue_or_sprintf("Hi \%s, did you know that 2*4=\%s", c("foo", "bar"), 2 * 4) 56 | } 57 | \seealso{ 58 | Other log_formatters: 59 | \code{\link{formatter_cli}()}, 60 | \code{\link{formatter_glue}()}, 61 | \code{\link{formatter_glue_safe}()}, 62 | \code{\link{formatter_json}()}, 63 | \code{\link{formatter_logging}()}, 64 | \code{\link{formatter_pander}()}, 65 | \code{\link{formatter_paste}()}, 66 | \code{\link{formatter_sprintf}()} 67 | } 68 | \concept{log_formatters} 69 | -------------------------------------------------------------------------------- /man/formatter_glue_safe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{formatter_glue_safe} 4 | \alias{formatter_glue_safe} 5 | \title{Apply \code{\link[glue:glue_safe]{glue::glue_safe()}} to convert R objects into a character vector} 6 | \usage{ 7 | formatter_glue_safe( 8 | ..., 9 | .logcall = sys.call(), 10 | .topcall = sys.call(-1), 11 | .topenv = parent.frame() 12 | ) 13 | } 14 | \arguments{ 15 | \item{...}{passed to \code{\link[glue:glue_safe]{glue::glue_safe()}} for the text interpolation} 16 | 17 | \item{.logcall}{the logging call being evaluated (useful in 18 | formatters and layouts when you want to have access to the raw, 19 | unevaluated R expression)} 20 | 21 | \item{.topcall}{R expression from which the logging function was 22 | called (useful in formatters and layouts to extract the calling 23 | function's name or arguments)} 24 | 25 | \item{.topenv}{original frame of the \code{.topcall} calling function 26 | where the formatter function will be evaluated and that is used 27 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 28 | } 29 | \value{ 30 | character vector 31 | } 32 | \description{ 33 | Apply \code{\link[glue:glue_safe]{glue::glue_safe()}} to convert R objects into a character vector 34 | } 35 | \seealso{ 36 | Other log_formatters: 37 | \code{\link{formatter_cli}()}, 38 | \code{\link{formatter_glue}()}, 39 | \code{\link{formatter_glue_or_sprintf}()}, 40 | \code{\link{formatter_json}()}, 41 | \code{\link{formatter_logging}()}, 42 | \code{\link{formatter_pander}()}, 43 | \code{\link{formatter_paste}()}, 44 | \code{\link{formatter_sprintf}()} 45 | } 46 | \concept{log_formatters} 47 | -------------------------------------------------------------------------------- /man/formatter_json.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{formatter_json} 4 | \alias{formatter_json} 5 | \title{Transforms all passed R objects into a JSON list} 6 | \usage{ 7 | formatter_json( 8 | ..., 9 | .logcall = sys.call(), 10 | .topcall = sys.call(-1), 11 | .topenv = parent.frame() 12 | ) 13 | } 14 | \arguments{ 15 | \item{...}{passed to \code{toJSON} wrapped into a \code{list}} 16 | 17 | \item{.logcall}{the logging call being evaluated (useful in 18 | formatters and layouts when you want to have access to the raw, 19 | unevaluated R expression)} 20 | 21 | \item{.topcall}{R expression from which the logging function was 22 | called (useful in formatters and layouts to extract the calling 23 | function's name or arguments)} 24 | 25 | \item{.topenv}{original frame of the \code{.topcall} calling function 26 | where the formatter function will be evaluated and that is used 27 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 28 | } 29 | \value{ 30 | character vector 31 | } 32 | \description{ 33 | Transforms all passed R objects into a JSON list 34 | } 35 | \note{ 36 | This functionality depends on the \pkg{jsonlite} package. 37 | } 38 | \examples{ 39 | \dontshow{old <- logger:::namespaces_set()} 40 | log_formatter(formatter_json) 41 | log_layout(layout_json_parser()) 42 | log_info(everything = 42) 43 | log_info(mtcars = mtcars, species = iris$Species) 44 | \dontshow{logger:::namespaces_set(old)} 45 | } 46 | \seealso{ 47 | Other log_formatters: 48 | \code{\link{formatter_cli}()}, 49 | \code{\link{formatter_glue}()}, 50 | \code{\link{formatter_glue_or_sprintf}()}, 51 | \code{\link{formatter_glue_safe}()}, 52 | \code{\link{formatter_logging}()}, 53 | \code{\link{formatter_pander}()}, 54 | \code{\link{formatter_paste}()}, 55 | \code{\link{formatter_sprintf}()} 56 | } 57 | \concept{log_formatters} 58 | -------------------------------------------------------------------------------- /man/formatter_logging.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{formatter_logging} 4 | \alias{formatter_logging} 5 | \title{Mimic the default formatter used in the \pkg{logging} package} 6 | \usage{ 7 | formatter_logging( 8 | ..., 9 | .logcall = sys.call(), 10 | .topcall = sys.call(-1), 11 | .topenv = parent.frame() 12 | ) 13 | } 14 | \arguments{ 15 | \item{...}{string and further params passed to \code{\link[=sprintf]{sprintf()}} or R 16 | expressions to be evaluated} 17 | 18 | \item{.logcall}{the logging call being evaluated (useful in 19 | formatters and layouts when you want to have access to the raw, 20 | unevaluated R expression)} 21 | 22 | \item{.topcall}{R expression from which the logging function was 23 | called (useful in formatters and layouts to extract the calling 24 | function's name or arguments)} 25 | 26 | \item{.topenv}{original frame of the \code{.topcall} calling function 27 | where the formatter function will be evaluated and that is used 28 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 29 | } 30 | \value{ 31 | character vector 32 | } 33 | \description{ 34 | The \pkg{logging} package uses a formatter that behaves differently 35 | when the input is a string or other R object. If the first argument 36 | is a string, then \code{\link[=sprintf]{sprintf()}} is being called -- otherwise it does 37 | something like \code{\link[=log_eval]{log_eval()}} and logs the R expression(s) and the 38 | result(s) as well. 39 | } 40 | \examples{ 41 | \dontshow{old <- logger:::namespaces_set()} 42 | log_formatter(formatter_logging) 43 | log_info("42") 44 | log_info(42) 45 | log_info(4 + 2) 46 | log_info("foo \%s", "bar") 47 | log_info("vector \%s", 1:3) 48 | log_info(12, 1 + 1, 2 * 2) 49 | \dontshow{logger:::namespaces_set(old)} 50 | } 51 | \seealso{ 52 | Other log_formatters: 53 | \code{\link{formatter_cli}()}, 54 | \code{\link{formatter_glue}()}, 55 | \code{\link{formatter_glue_or_sprintf}()}, 56 | \code{\link{formatter_glue_safe}()}, 57 | \code{\link{formatter_json}()}, 58 | \code{\link{formatter_pander}()}, 59 | \code{\link{formatter_paste}()}, 60 | \code{\link{formatter_sprintf}()} 61 | } 62 | \concept{log_formatters} 63 | -------------------------------------------------------------------------------- /man/formatter_pander.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{formatter_pander} 4 | \alias{formatter_pander} 5 | \title{Formats R objects with pander} 6 | \usage{ 7 | formatter_pander( 8 | x, 9 | ..., 10 | .logcall = sys.call(), 11 | .topcall = sys.call(-1), 12 | .topenv = parent.frame() 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{object to be logged} 17 | 18 | \item{...}{optional parameters passed to \code{pander}} 19 | 20 | \item{.logcall}{the logging call being evaluated (useful in 21 | formatters and layouts when you want to have access to the raw, 22 | unevaluated R expression)} 23 | 24 | \item{.topcall}{R expression from which the logging function was 25 | called (useful in formatters and layouts to extract the calling 26 | function's name or arguments)} 27 | 28 | \item{.topenv}{original frame of the \code{.topcall} calling function 29 | where the formatter function will be evaluated and that is used 30 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 31 | } 32 | \value{ 33 | character vector 34 | } 35 | \description{ 36 | Formats R objects with pander 37 | } 38 | \note{ 39 | This functionality depends on the \pkg{pander} package. 40 | } 41 | \examples{ 42 | \dontshow{old <- logger:::namespaces_set()} 43 | log_formatter(formatter_pander) 44 | log_info("42") 45 | log_info(42) 46 | log_info(4 + 2) 47 | log_info(head(iris)) 48 | log_info(head(iris), style = "simple") 49 | log_info(lm(hp ~ wt, mtcars)) 50 | \dontshow{logger:::namespaces_set(old)} 51 | } 52 | \seealso{ 53 | Other log_formatters: 54 | \code{\link{formatter_cli}()}, 55 | \code{\link{formatter_glue}()}, 56 | \code{\link{formatter_glue_or_sprintf}()}, 57 | \code{\link{formatter_glue_safe}()}, 58 | \code{\link{formatter_json}()}, 59 | \code{\link{formatter_logging}()}, 60 | \code{\link{formatter_paste}()}, 61 | \code{\link{formatter_sprintf}()} 62 | } 63 | \concept{log_formatters} 64 | -------------------------------------------------------------------------------- /man/formatter_paste.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{formatter_paste} 4 | \alias{formatter_paste} 5 | \title{Concatenate R objects into a character vector via \code{paste}} 6 | \usage{ 7 | formatter_paste( 8 | ..., 9 | .logcall = sys.call(), 10 | .topcall = sys.call(-1), 11 | .topenv = parent.frame() 12 | ) 13 | } 14 | \arguments{ 15 | \item{...}{passed to \code{paste}} 16 | 17 | \item{.logcall}{the logging call being evaluated (useful in 18 | formatters and layouts when you want to have access to the raw, 19 | unevaluated R expression)} 20 | 21 | \item{.topcall}{R expression from which the logging function was 22 | called (useful in formatters and layouts to extract the calling 23 | function's name or arguments)} 24 | 25 | \item{.topenv}{original frame of the \code{.topcall} calling function 26 | where the formatter function will be evaluated and that is used 27 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 28 | } 29 | \value{ 30 | character vector 31 | } 32 | \description{ 33 | Concatenate R objects into a character vector via \code{paste} 34 | } 35 | \seealso{ 36 | Other log_formatters: 37 | \code{\link{formatter_cli}()}, 38 | \code{\link{formatter_glue}()}, 39 | \code{\link{formatter_glue_or_sprintf}()}, 40 | \code{\link{formatter_glue_safe}()}, 41 | \code{\link{formatter_json}()}, 42 | \code{\link{formatter_logging}()}, 43 | \code{\link{formatter_pander}()}, 44 | \code{\link{formatter_sprintf}()} 45 | } 46 | \concept{log_formatters} 47 | -------------------------------------------------------------------------------- /man/formatter_sprintf.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{formatter_sprintf} 4 | \alias{formatter_sprintf} 5 | \title{Apply \code{\link[=sprintf]{sprintf()}} to convert R objects into a character vector} 6 | \usage{ 7 | formatter_sprintf( 8 | fmt, 9 | ..., 10 | .logcall = sys.call(), 11 | .topcall = sys.call(-1), 12 | .topenv = parent.frame() 13 | ) 14 | } 15 | \arguments{ 16 | \item{fmt}{passed to \code{\link[=sprintf]{sprintf()}}} 17 | 18 | \item{...}{passed to \code{\link[=sprintf]{sprintf()}}} 19 | 20 | \item{.logcall}{the logging call being evaluated (useful in 21 | formatters and layouts when you want to have access to the raw, 22 | unevaluated R expression)} 23 | 24 | \item{.topcall}{R expression from which the logging function was 25 | called (useful in formatters and layouts to extract the calling 26 | function's name or arguments)} 27 | 28 | \item{.topenv}{original frame of the \code{.topcall} calling function 29 | where the formatter function will be evaluated and that is used 30 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 31 | } 32 | \value{ 33 | character vector 34 | } 35 | \description{ 36 | Apply \code{\link[=sprintf]{sprintf()}} to convert R objects into a character vector 37 | } 38 | \seealso{ 39 | Other log_formatters: 40 | \code{\link{formatter_cli}()}, 41 | \code{\link{formatter_glue}()}, 42 | \code{\link{formatter_glue_or_sprintf}()}, 43 | \code{\link{formatter_glue_safe}()}, 44 | \code{\link{formatter_json}()}, 45 | \code{\link{formatter_logging}()}, 46 | \code{\link{formatter_pander}()}, 47 | \code{\link{formatter_paste}()} 48 | } 49 | \concept{log_formatters} 50 | -------------------------------------------------------------------------------- /man/get_logger_meta_variables.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{get_logger_meta_variables} 4 | \alias{get_logger_meta_variables} 5 | \title{Collect useful information about the logging environment to be used in log messages} 6 | \usage{ 7 | get_logger_meta_variables( 8 | log_level = NULL, 9 | namespace = NA_character_, 10 | .logcall = sys.call(), 11 | .topcall = sys.call(-1), 12 | .topenv = parent.frame(), 13 | .timestamp = Sys.time() 14 | ) 15 | } 16 | \arguments{ 17 | \item{log_level}{log level as per \code{\link[=log_levels]{log_levels()}}} 18 | 19 | \item{namespace}{string referring to the \code{logger} environment / 20 | config to be used to override the target of the message record to 21 | be used instead of the default namespace, which is defined by the 22 | R package name from which the logger was called, and falls back 23 | to a common, global namespace.} 24 | 25 | \item{.logcall}{the logging call being evaluated (useful in 26 | formatters and layouts when you want to have access to the raw, 27 | unevaluated R expression)} 28 | 29 | \item{.topcall}{R expression from which the logging function was 30 | called (useful in formatters and layouts to extract the calling 31 | function's name or arguments)} 32 | 33 | \item{.topenv}{original frame of the \code{.topcall} calling function 34 | where the formatter function will be evaluated and that is used 35 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 36 | 37 | \item{.timestamp}{The time the logging occured. Defaults to the current time 38 | but may be overwritten if the logging is delayed from the time it happend} 39 | } 40 | \value{ 41 | list 42 | } 43 | \description{ 44 | Available variables to be used in the log formatter functions, eg in \code{\link[=layout_glue_generator]{layout_glue_generator()}}: 45 | } 46 | \details{ 47 | \itemize{ 48 | \item \code{levelr}: log level as an R object, eg \code{\link[=INFO]{INFO()}} 49 | \item \code{level}: log level as a string, eg \code{\link[=INFO]{INFO()}} 50 | \item \code{time}: current time as \code{POSIXct} 51 | \item \code{node}: name by which the machine is known on the network as reported by \code{Sys.info} 52 | \item \code{arch}: machine type, typically the CPU architecture 53 | \item \code{os_name}: Operating System's name 54 | \item \code{os_release}: Operating System's release 55 | \item \code{os_version}: Operating System's version 56 | \item \code{user}: name of the real user id as reported by \code{Sys.info} 57 | \item \code{pid}: the process identification number of the R session 58 | \item \code{node}: name by which the machine is known on the network as reported by \code{Sys.info} 59 | \item \code{r_version}: R's major and minor version as a string 60 | \item \code{ns}: namespace usually defaults to \code{global} or the name of the holding R package 61 | of the calling the logging function 62 | \item \code{ns_pkg_version}: the version of \code{ns} when it's a package 63 | \item \code{ans}: same as \code{ns} if there's a defined \code{\link[=logger]{logger()}} for the namespace, 64 | otherwise a fallback namespace (eg usually \code{global}) 65 | \item \code{topenv}: the name of the top environment from which the parent call was called 66 | (eg R package name or \code{GlobalEnv}) 67 | \item \code{call}: parent call (if any) calling the logging function 68 | \item \code{location}: A list with element \code{path} and \code{line} giving the location of the 69 | log call 70 | \item \code{fn}: function's (if any) name calling the logging function 71 | } 72 | } 73 | \seealso{ 74 | \code{\link[=layout_glue_generator]{layout_glue_generator()}} 75 | 76 | Other log_layouts: 77 | \code{\link{layout_blank}()}, 78 | \code{\link{layout_glue}()}, 79 | \code{\link{layout_glue_colors}()}, 80 | \code{\link{layout_glue_generator}()}, 81 | \code{\link{layout_json}()}, 82 | \code{\link{layout_json_parser}()}, 83 | \code{\link{layout_logging}()}, 84 | \code{\link{layout_simple}()} 85 | } 86 | \concept{log_layouts} 87 | -------------------------------------------------------------------------------- /man/grapes-except-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/try.R 3 | \name{\%except\%} 4 | \alias{\%except\%} 5 | \title{Try to evaluate an expressions and evaluate another expression on 6 | exception} 7 | \usage{ 8 | try \%except\% except 9 | } 10 | \arguments{ 11 | \item{try}{R expression} 12 | 13 | \item{except}{fallback R expression to be evaluated if \code{try} fails} 14 | } 15 | \description{ 16 | Try to evaluate an expressions and evaluate another expression on 17 | exception 18 | } 19 | \note{ 20 | Suppress log messages in the \code{except} namespace if you don't 21 | want to throw a \code{WARN} log message on the exception branch. 22 | } 23 | \examples{ 24 | everything \%except\% 42 25 | everything <- "640kb" 26 | everything \%except\% 42 27 | 28 | FunDoesNotExist(1:10) \%except\% sum(1:10) / length(1:10) 29 | FunDoesNotExist(1:10) \%except\% (sum(1:10) / length(1:10)) 30 | FunDoesNotExist(1:10) \%except\% MEAN(1:10) \%except\% mean(1:10) 31 | FunDoesNotExist(1:10) \%except\% (MEAN(1:10) \%except\% mean(1:10)) 32 | } 33 | -------------------------------------------------------------------------------- /man/layout_blank.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{layout_blank} 4 | \alias{layout_blank} 5 | \title{Format a log record by including the raw message without anything 6 | added or modified} 7 | \usage{ 8 | layout_blank( 9 | level, 10 | msg, 11 | namespace = NA_character_, 12 | .logcall = sys.call(), 13 | .topcall = sys.call(-1), 14 | .topenv = parent.frame(), 15 | .timestamp = Sys.time() 16 | ) 17 | } 18 | \arguments{ 19 | \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} 20 | 21 | \item{msg}{string message} 22 | 23 | \item{namespace}{string referring to the \code{logger} environment / 24 | config to be used to override the target of the message record to 25 | be used instead of the default namespace, which is defined by the 26 | R package name from which the logger was called, and falls back 27 | to a common, global namespace.} 28 | 29 | \item{.logcall}{the logging call being evaluated (useful in 30 | formatters and layouts when you want to have access to the raw, 31 | unevaluated R expression)} 32 | 33 | \item{.topcall}{R expression from which the logging function was 34 | called (useful in formatters and layouts to extract the calling 35 | function's name or arguments)} 36 | 37 | \item{.topenv}{original frame of the \code{.topcall} calling function 38 | where the formatter function will be evaluated and that is used 39 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 40 | 41 | \item{.timestamp}{The time the logging occured. Defaults to the current time 42 | but may be overwritten if the logging is delayed from the time it happend} 43 | } 44 | \value{ 45 | character vector 46 | } 47 | \description{ 48 | Format a log record by including the raw message without anything 49 | added or modified 50 | } 51 | \seealso{ 52 | Other log_layouts: 53 | \code{\link{get_logger_meta_variables}()}, 54 | \code{\link{layout_glue}()}, 55 | \code{\link{layout_glue_colors}()}, 56 | \code{\link{layout_glue_generator}()}, 57 | \code{\link{layout_json}()}, 58 | \code{\link{layout_json_parser}()}, 59 | \code{\link{layout_logging}()}, 60 | \code{\link{layout_simple}()} 61 | } 62 | \concept{log_layouts} 63 | -------------------------------------------------------------------------------- /man/layout_glue.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{layout_glue} 4 | \alias{layout_glue} 5 | \title{Format a log message with \code{\link[glue:glue]{glue::glue()}}} 6 | \usage{ 7 | layout_glue( 8 | level, 9 | msg, 10 | namespace = NA_character_, 11 | .logcall = sys.call(), 12 | .topcall = sys.call(-1), 13 | .topenv = parent.frame(), 14 | .timestamp = Sys.time() 15 | ) 16 | } 17 | \arguments{ 18 | \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} 19 | 20 | \item{msg}{string message} 21 | 22 | \item{namespace}{string referring to the \code{logger} environment / 23 | config to be used to override the target of the message record to 24 | be used instead of the default namespace, which is defined by the 25 | R package name from which the logger was called, and falls back 26 | to a common, global namespace.} 27 | 28 | \item{.logcall}{the logging call being evaluated (useful in 29 | formatters and layouts when you want to have access to the raw, 30 | unevaluated R expression)} 31 | 32 | \item{.topcall}{R expression from which the logging function was 33 | called (useful in formatters and layouts to extract the calling 34 | function's name or arguments)} 35 | 36 | \item{.topenv}{original frame of the \code{.topcall} calling function 37 | where the formatter function will be evaluated and that is used 38 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 39 | 40 | \item{.timestamp}{The time the logging occured. Defaults to the current time 41 | but may be overwritten if the logging is delayed from the time it happend} 42 | } 43 | \value{ 44 | character vector 45 | } 46 | \description{ 47 | By default, this layout includes the log level of the log record as 48 | per \code{\link[=log_levels]{log_levels()}}, the current timestamp and the actual log 49 | message -- that you can override via calling 50 | \code{\link[=layout_glue_generator]{layout_glue_generator()}} directly. For colorized output, see 51 | \code{\link[=layout_glue_colors]{layout_glue_colors()}}. 52 | } 53 | \seealso{ 54 | Other log_layouts: 55 | \code{\link{get_logger_meta_variables}()}, 56 | \code{\link{layout_blank}()}, 57 | \code{\link{layout_glue_colors}()}, 58 | \code{\link{layout_glue_generator}()}, 59 | \code{\link{layout_json}()}, 60 | \code{\link{layout_json_parser}()}, 61 | \code{\link{layout_logging}()}, 62 | \code{\link{layout_simple}()} 63 | } 64 | \concept{log_layouts} 65 | -------------------------------------------------------------------------------- /man/layout_glue_colors.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{layout_glue_colors} 4 | \alias{layout_glue_colors} 5 | \title{Format a log message with \code{\link[glue:glue]{glue::glue()}} and ANSI escape codes to add colors} 6 | \usage{ 7 | layout_glue_colors( 8 | level, 9 | msg, 10 | namespace = NA_character_, 11 | .logcall = sys.call(), 12 | .topcall = sys.call(-1), 13 | .topenv = parent.frame(), 14 | .timestamp = Sys.time() 15 | ) 16 | } 17 | \arguments{ 18 | \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} 19 | 20 | \item{msg}{string message} 21 | 22 | \item{namespace}{string referring to the \code{logger} environment / 23 | config to be used to override the target of the message record to 24 | be used instead of the default namespace, which is defined by the 25 | R package name from which the logger was called, and falls back 26 | to a common, global namespace.} 27 | 28 | \item{.logcall}{the logging call being evaluated (useful in 29 | formatters and layouts when you want to have access to the raw, 30 | unevaluated R expression)} 31 | 32 | \item{.topcall}{R expression from which the logging function was 33 | called (useful in formatters and layouts to extract the calling 34 | function's name or arguments)} 35 | 36 | \item{.topenv}{original frame of the \code{.topcall} calling function 37 | where the formatter function will be evaluated and that is used 38 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 39 | 40 | \item{.timestamp}{The time the logging occured. Defaults to the current time 41 | but may be overwritten if the logging is delayed from the time it happend} 42 | } 43 | \value{ 44 | character vector 45 | } 46 | \description{ 47 | Colour log levels based on their severity. Log levels are coloured 48 | with \code{\link[=colorize_by_log_level]{colorize_by_log_level()}} and the messages are coloured with 49 | \code{\link[=grayscale_by_log_level]{grayscale_by_log_level()}}. 50 | } 51 | \note{ 52 | This functionality depends on the \pkg{crayon} package. 53 | } 54 | \examples{ 55 | \dontshow{if (requireNamespace("crayon")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 56 | log_layout(layout_glue_colors) 57 | log_threshold(TRACE) 58 | log_info("Starting the script...") 59 | log_debug("This is the second line") 60 | log_trace("That is being placed right after the first one.") 61 | log_warn("Some errors might come!") 62 | log_error("This is a problem") 63 | log_debug("Getting an error is usually bad") 64 | log_error("This is another problem") 65 | log_fatal("The last problem.") 66 | \dontshow{\}) # examplesIf} 67 | } 68 | \seealso{ 69 | Other log_layouts: 70 | \code{\link{get_logger_meta_variables}()}, 71 | \code{\link{layout_blank}()}, 72 | \code{\link{layout_glue}()}, 73 | \code{\link{layout_glue_generator}()}, 74 | \code{\link{layout_json}()}, 75 | \code{\link{layout_json_parser}()}, 76 | \code{\link{layout_logging}()}, 77 | \code{\link{layout_simple}()} 78 | } 79 | \concept{log_layouts} 80 | -------------------------------------------------------------------------------- /man/layout_glue_generator.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{layout_glue_generator} 4 | \alias{layout_glue_generator} 5 | \title{Generate log layout function using common variables available via glue syntax} 6 | \usage{ 7 | layout_glue_generator( 8 | format = "{level} [{format(time, \\"\%Y-\%m-\%d \%H:\%M:\%S\\")}] {msg}" 9 | ) 10 | } 11 | \arguments{ 12 | \item{format}{\code{\link[glue:glue]{glue::glue()}}-flavored layout of the message using the above 13 | variables} 14 | } 15 | \value{ 16 | function taking \code{level} and \code{msg} arguments - keeping the 17 | original call creating the generator in the \code{generator} attribute 18 | that is returned when calling \code{\link[=log_layout]{log_layout()}} for the currently 19 | used layout 20 | } 21 | \description{ 22 | \code{format} is passed to \code{\link[glue:glue]{glue::glue()}} with access to the below variables: 23 | \itemize{ \item msg: the actual log message \item further variables 24 | set by \code{\link[=get_logger_meta_variables]{get_logger_meta_variables()}} } 25 | } 26 | \examples{ 27 | \dontshow{old <- logger:::namespaces_set()} 28 | example_layout <- layout_glue_generator( 29 | format = "{node}/{pid}/{ns}/{ans}/{topenv}/{fn} {time} {level}: {msg}" 30 | ) 31 | example_layout(INFO, "try {runif(1)}") 32 | 33 | log_layout(example_layout) 34 | log_info("try {runif(1)}") 35 | \dontshow{logger:::namespaces_set(old)} 36 | } 37 | \seealso{ 38 | See example calls from \code{\link[=layout_glue]{layout_glue()}} and \code{\link[=layout_glue_colors]{layout_glue_colors()}}. 39 | 40 | Other log_layouts: 41 | \code{\link{get_logger_meta_variables}()}, 42 | \code{\link{layout_blank}()}, 43 | \code{\link{layout_glue}()}, 44 | \code{\link{layout_glue_colors}()}, 45 | \code{\link{layout_json}()}, 46 | \code{\link{layout_json_parser}()}, 47 | \code{\link{layout_logging}()}, 48 | \code{\link{layout_simple}()} 49 | } 50 | \concept{log_layouts} 51 | -------------------------------------------------------------------------------- /man/layout_json.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{layout_json} 4 | \alias{layout_json} 5 | \title{Generate log layout function rendering JSON} 6 | \usage{ 7 | layout_json(fields = default_fields()) 8 | } 9 | \arguments{ 10 | \item{fields}{character vector of field names to be included in the 11 | JSON} 12 | } 13 | \value{ 14 | character vector 15 | } 16 | \description{ 17 | Generate log layout function rendering JSON 18 | } 19 | \note{ 20 | This functionality depends on the \pkg{jsonlite} package. 21 | } 22 | \examples{ 23 | \dontshow{old <- logger:::namespaces_set()} 24 | log_layout(layout_json()) 25 | log_info(42) 26 | log_info("ok {1:3} + {1:3} = {2*(1:3)}") 27 | \dontshow{logger:::namespaces_set(old)} 28 | } 29 | \seealso{ 30 | Other log_layouts: 31 | \code{\link{get_logger_meta_variables}()}, 32 | \code{\link{layout_blank}()}, 33 | \code{\link{layout_glue}()}, 34 | \code{\link{layout_glue_colors}()}, 35 | \code{\link{layout_glue_generator}()}, 36 | \code{\link{layout_json_parser}()}, 37 | \code{\link{layout_logging}()}, 38 | \code{\link{layout_simple}()} 39 | } 40 | \concept{log_layouts} 41 | -------------------------------------------------------------------------------- /man/layout_json_parser.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{layout_json_parser} 4 | \alias{layout_json_parser} 5 | \title{Generate log layout function rendering JSON after merging meta 6 | fields with parsed list from JSON message} 7 | \usage{ 8 | layout_json_parser(fields = default_fields()) 9 | } 10 | \arguments{ 11 | \item{fields}{character vector of field names to be included in the 12 | JSON. If named, the names will be used as field names in the JSON.} 13 | } 14 | \description{ 15 | Generate log layout function rendering JSON after merging meta 16 | fields with parsed list from JSON message 17 | } 18 | \note{ 19 | This functionality depends on the \pkg{jsonlite} package. 20 | } 21 | \examples{ 22 | \dontshow{old <- logger:::namespaces_set()} 23 | log_formatter(formatter_json) 24 | log_info(everything = 42) 25 | 26 | log_layout(layout_json_parser()) 27 | log_info(everything = 42) 28 | 29 | log_layout(layout_json_parser(fields = c("time", "node"))) 30 | log_info(cars = row.names(mtcars), species = unique(iris$Species)) 31 | 32 | log_layout(layout_json_parser(fields = c(timestamp = "time", "node"))) 33 | log_info( 34 | message = paste( 35 | "Compared to the previous example, 36 | the 'time' field is renamed to 'timestamp'" 37 | ) 38 | ) 39 | \dontshow{logger:::namespaces_set(old)} 40 | } 41 | \seealso{ 42 | Other log_layouts: 43 | \code{\link{get_logger_meta_variables}()}, 44 | \code{\link{layout_blank}()}, 45 | \code{\link{layout_glue}()}, 46 | \code{\link{layout_glue_colors}()}, 47 | \code{\link{layout_glue_generator}()}, 48 | \code{\link{layout_json}()}, 49 | \code{\link{layout_logging}()}, 50 | \code{\link{layout_simple}()} 51 | } 52 | \concept{log_layouts} 53 | -------------------------------------------------------------------------------- /man/layout_logging.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{layout_logging} 4 | \alias{layout_logging} 5 | \title{Format a log record as the logging package does by default} 6 | \usage{ 7 | layout_logging( 8 | level, 9 | msg, 10 | namespace = NA_character_, 11 | .logcall = sys.call(), 12 | .topcall = sys.call(-1), 13 | .topenv = parent.frame(), 14 | .timestamp = Sys.time() 15 | ) 16 | } 17 | \arguments{ 18 | \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} 19 | 20 | \item{msg}{string message} 21 | 22 | \item{namespace}{string referring to the \code{logger} environment / 23 | config to be used to override the target of the message record to 24 | be used instead of the default namespace, which is defined by the 25 | R package name from which the logger was called, and falls back 26 | to a common, global namespace.} 27 | 28 | \item{.logcall}{the logging call being evaluated (useful in 29 | formatters and layouts when you want to have access to the raw, 30 | unevaluated R expression)} 31 | 32 | \item{.topcall}{R expression from which the logging function was 33 | called (useful in formatters and layouts to extract the calling 34 | function's name or arguments)} 35 | 36 | \item{.topenv}{original frame of the \code{.topcall} calling function 37 | where the formatter function will be evaluated and that is used 38 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 39 | 40 | \item{.timestamp}{The time the logging occured. Defaults to the current time 41 | but may be overwritten if the logging is delayed from the time it happend} 42 | } 43 | \value{ 44 | character vector 45 | } 46 | \description{ 47 | Format a log record as the logging package does by default 48 | } 49 | \examples{ 50 | \dontshow{old <- logger:::namespaces_set()} 51 | log_layout(layout_logging) 52 | log_info(42) 53 | log_info(42, namespace = "everything") 54 | 55 | \dontrun{ 56 | devtools::load_all(system.file("demo-packages/logger-tester-package", package = "logger")) 57 | logger_tester_function(INFO, 42) 58 | } 59 | \dontshow{logger:::namespaces_set(old)} 60 | } 61 | \seealso{ 62 | Other log_layouts: 63 | \code{\link{get_logger_meta_variables}()}, 64 | \code{\link{layout_blank}()}, 65 | \code{\link{layout_glue}()}, 66 | \code{\link{layout_glue_colors}()}, 67 | \code{\link{layout_glue_generator}()}, 68 | \code{\link{layout_json}()}, 69 | \code{\link{layout_json_parser}()}, 70 | \code{\link{layout_simple}()} 71 | } 72 | \concept{log_layouts} 73 | -------------------------------------------------------------------------------- /man/layout_simple.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{layout_simple} 4 | \alias{layout_simple} 5 | \title{Format a log record by concatenating the log level, timestamp and 6 | message} 7 | \usage{ 8 | layout_simple( 9 | level, 10 | msg, 11 | namespace = NA_character_, 12 | .logcall = sys.call(), 13 | .topcall = sys.call(-1), 14 | .topenv = parent.frame(), 15 | .timestamp = Sys.time() 16 | ) 17 | } 18 | \arguments{ 19 | \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} 20 | 21 | \item{msg}{string message} 22 | 23 | \item{namespace}{string referring to the \code{logger} environment / 24 | config to be used to override the target of the message record to 25 | be used instead of the default namespace, which is defined by the 26 | R package name from which the logger was called, and falls back 27 | to a common, global namespace.} 28 | 29 | \item{.logcall}{the logging call being evaluated (useful in 30 | formatters and layouts when you want to have access to the raw, 31 | unevaluated R expression)} 32 | 33 | \item{.topcall}{R expression from which the logging function was 34 | called (useful in formatters and layouts to extract the calling 35 | function's name or arguments)} 36 | 37 | \item{.topenv}{original frame of the \code{.topcall} calling function 38 | where the formatter function will be evaluated and that is used 39 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 40 | 41 | \item{.timestamp}{The time the logging occured. Defaults to the current time 42 | but may be overwritten if the logging is delayed from the time it happend} 43 | } 44 | \value{ 45 | character vector 46 | } 47 | \description{ 48 | Format a log record by concatenating the log level, timestamp and 49 | message 50 | } 51 | \seealso{ 52 | Other log_layouts: 53 | \code{\link{get_logger_meta_variables}()}, 54 | \code{\link{layout_blank}()}, 55 | \code{\link{layout_glue}()}, 56 | \code{\link{layout_glue_colors}()}, 57 | \code{\link{layout_glue_generator}()}, 58 | \code{\link{layout_json}()}, 59 | \code{\link{layout_json_parser}()}, 60 | \code{\link{layout_logging}()} 61 | } 62 | \concept{log_layouts} 63 | -------------------------------------------------------------------------------- /man/layout_syslognet.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/layouts.R 3 | \name{layout_syslognet} 4 | \alias{layout_syslognet} 5 | \title{Format a log record for syslognet} 6 | \usage{ 7 | layout_syslognet( 8 | level, 9 | msg, 10 | namespace = NA_character_, 11 | .logcall = sys.call(), 12 | .topcall = sys.call(-1), 13 | .topenv = parent.frame(), 14 | .timestamp = Sys.time() 15 | ) 16 | } 17 | \arguments{ 18 | \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} 19 | 20 | \item{msg}{string message} 21 | 22 | \item{namespace}{string referring to the \code{logger} environment / 23 | config to be used to override the target of the message record to 24 | be used instead of the default namespace, which is defined by the 25 | R package name from which the logger was called, and falls back 26 | to a common, global namespace.} 27 | 28 | \item{.logcall}{the logging call being evaluated (useful in 29 | formatters and layouts when you want to have access to the raw, 30 | unevaluated R expression)} 31 | 32 | \item{.topcall}{R expression from which the logging function was 33 | called (useful in formatters and layouts to extract the calling 34 | function's name or arguments)} 35 | 36 | \item{.topenv}{original frame of the \code{.topcall} calling function 37 | where the formatter function will be evaluated and that is used 38 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 39 | 40 | \item{.timestamp}{The time the logging occured. Defaults to the current time 41 | but may be overwritten if the logging is delayed from the time it happend} 42 | } 43 | \value{ 44 | A character vector with a severity attribute. 45 | } 46 | \description{ 47 | Format a log record for syslognet. 48 | This function converts the logger log level to a 49 | log severity level according to RFC 5424 "The Syslog Protocol". 50 | } 51 | -------------------------------------------------------------------------------- /man/log_appender.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{log_appender} 4 | \alias{log_appender} 5 | \title{Get or set log record appender function} 6 | \usage{ 7 | log_appender(appender = NULL, namespace = "global", index = 1) 8 | } 9 | \arguments{ 10 | \item{appender}{function delivering a log record to the 11 | destination, eg \code{\link[=appender_console]{appender_console()}}, \code{\link[=appender_file]{appender_file()}} or 12 | \code{\link[=appender_tee]{appender_tee()}}, default NULL} 13 | 14 | \item{namespace}{logger namespace} 15 | 16 | \item{index}{index of the logger within the namespace} 17 | } 18 | \description{ 19 | Get or set log record appender function 20 | } 21 | \examples{ 22 | \dontshow{old <- logger:::namespaces_set()} 23 | ## change appender to "tee" that writes to the console and a file as well 24 | t <- tempfile() 25 | log_appender(appender_tee(t)) 26 | log_info(42) 27 | log_info(43) 28 | log_info(44) 29 | readLines(t) 30 | 31 | ## poor man's tee by stacking loggers in the namespace 32 | t <- tempfile() 33 | log_appender(appender_stdout) 34 | log_appender(appender_file(t), index = 2) 35 | log_info(42) 36 | readLines(t) 37 | \dontshow{logger:::namespaces_set(old)} 38 | } 39 | \seealso{ 40 | Other log configutation functions: 41 | \code{\link{log_formatter}()}, 42 | \code{\link{log_layout}()}, 43 | \code{\link{log_threshold}()} 44 | } 45 | \concept{log configutation functions} 46 | -------------------------------------------------------------------------------- /man/log_chunk_time.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{log_chunk_time} 4 | \alias{log_chunk_time} 5 | \title{Automatically log execution time of knitr chunks} 6 | \usage{ 7 | log_chunk_time(..., level = INFO, namespace = NA_character_) 8 | } 9 | \arguments{ 10 | \item{...}{passed to \code{\link[=log_level]{log_level()}}} 11 | 12 | \item{level}{see \code{\link[=log_levels]{log_levels()}}} 13 | 14 | \item{namespace}{x} 15 | } 16 | \description{ 17 | Calling this function in the first chunk of a document will instruct knitr 18 | to automatically log the execution time of each chunk. If using 19 | \code{\link[=formatter_glue]{formatter_glue()}} or \code{\link[=formatter_cli]{formatter_cli()}} then the \code{options} variable will be 20 | available, providing the chunk options such as chunk label etc. 21 | } 22 | \examples{ 23 | # To be put in the first chunk of a document 24 | log_chunk_time("chunk {options$label}") 25 | 26 | } 27 | -------------------------------------------------------------------------------- /man/log_elapsed.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{log_elapsed} 4 | \alias{log_elapsed} 5 | \alias{log_elapsed_start} 6 | \title{Log cumulative running time} 7 | \usage{ 8 | log_elapsed(..., level = INFO, namespace = NA_character_) 9 | 10 | log_elapsed_start(level = INFO, namespace = NA_character_, quiet = FALSE) 11 | } 12 | \arguments{ 13 | \item{...}{passed to \code{\link[=log_level]{log_level()}}} 14 | 15 | \item{level}{see \code{\link[=log_levels]{log_levels()}}} 16 | 17 | \item{namespace}{x} 18 | 19 | \item{quiet}{Should starting the time emit a log message} 20 | } 21 | \description{ 22 | This function is working like \code{\link[=log_tictoc]{log_tictoc()}} but differs in that it continues 23 | to count up rather than resetting the timer at every call. You can set the 24 | start time using \code{log_elapsed_start()}, but if that hasn't been called it 25 | will show the time since the R session started. 26 | } 27 | \examples{ 28 | log_elapsed_start() 29 | Sys.sleep(0.4) 30 | log_elapsed("Tast 1") 31 | Sys.sleep(0.2) 32 | log_elapsed("Task 2") 33 | 34 | } 35 | -------------------------------------------------------------------------------- /man/log_errors.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hooks.R 3 | \name{log_errors} 4 | \alias{log_errors} 5 | \title{Injects a logger call to standard errors} 6 | \usage{ 7 | log_errors( 8 | muffle = getOption("logger_muffle_errors", FALSE), 9 | traceback = FALSE 10 | ) 11 | } 12 | \arguments{ 13 | \item{muffle}{if TRUE, the error is not thrown after being logged} 14 | 15 | \item{traceback}{if TRUE the error traceback is logged along with the error 16 | message} 17 | } 18 | \description{ 19 | This function uses \code{\link[=trace]{trace()}} to add a \code{\link[=log_error]{log_error()}} function call when 20 | \code{\link[=stop]{stop()}} is called to log the error messages with the \code{logger} layout 21 | and appender. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | log_errors() 26 | stop("foobar") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /man/log_eval.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{log_eval} 4 | \alias{log_eval} 5 | \title{Evaluate an expression and log results} 6 | \usage{ 7 | log_eval(expr, level = TRACE, multiline = FALSE) 8 | } 9 | \arguments{ 10 | \item{expr}{R expression to be evaluated while logging the 11 | expression itself along with the result} 12 | 13 | \item{level}{\code{\link[=log_levels]{log_levels()}}} 14 | 15 | \item{multiline}{setting to \code{FALSE} will print both the expression 16 | (enforced to be on one line by removing line-breaks if any) and 17 | its result on a single line separated by \verb{=>}, while setting to 18 | \code{TRUE} will log the expression and the result in separate 19 | sections reserving line-breaks and rendering the printed results} 20 | } 21 | \description{ 22 | Evaluate an expression and log results 23 | } 24 | \examples{ 25 | \dontshow{old <- logger:::namespaces_set()} 26 | log_eval(pi * 2, level = INFO) 27 | 28 | ## lowering the log level threshold so that we don't have to set a higher level in log_eval 29 | log_threshold(TRACE) 30 | log_eval(x <- 4) 31 | log_eval(sqrt(x)) 32 | 33 | ## log_eval can be called in-line as well as returning the return value of the expression 34 | x <- log_eval(mean(runif(1e3))) 35 | x 36 | 37 | ## https://twitter.com/krlmlr/status/1067864829547999232 38 | f <- sqrt 39 | g <- mean 40 | x <- 1:31 41 | log_eval(f(g(x)), level = INFO) 42 | log_eval(y <- f(g(x)), level = INFO) 43 | 44 | ## returning a function 45 | log_eval(f <- sqrt) 46 | log_eval(f) 47 | 48 | ## evaluating something returning a wall of "text" 49 | log_eval(f <- log_eval) 50 | log_eval(f <- log_eval, multiline = TRUE) 51 | 52 | ## doing something computationally intensive 53 | log_eval(system.time(for (i in 1:100) mad(runif(1000))), multiline = TRUE) 54 | \dontshow{logger:::namespaces_set(old)} 55 | } 56 | -------------------------------------------------------------------------------- /man/log_failure.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{log_failure} 4 | \alias{log_failure} 5 | \title{Logs the error message to console before failing} 6 | \usage{ 7 | log_failure(expression) 8 | } 9 | \arguments{ 10 | \item{expression}{call} 11 | } 12 | \description{ 13 | Logs the error message to console before failing 14 | } 15 | \examples{ 16 | log_failure("foobar") 17 | try(log_failure(foobar)) 18 | } 19 | -------------------------------------------------------------------------------- /man/log_formatter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{log_formatter} 4 | \alias{log_formatter} 5 | \title{Get or set log message formatter} 6 | \usage{ 7 | log_formatter(formatter = NULL, namespace = "global", index = 1) 8 | } 9 | \arguments{ 10 | \item{formatter}{function defining how R objects are converted into 11 | a single string, eg \code{\link[=formatter_paste]{formatter_paste()}}, \code{\link[=formatter_sprintf]{formatter_sprintf()}}, 12 | \code{\link[=formatter_glue]{formatter_glue()}}, \code{\link[=formatter_glue_or_sprintf]{formatter_glue_or_sprintf()}}, 13 | \code{\link[=formatter_logging]{formatter_logging()}}, default NULL} 14 | 15 | \item{namespace}{logger namespace} 16 | 17 | \item{index}{index of the logger within the namespace} 18 | } 19 | \description{ 20 | Get or set log message formatter 21 | } 22 | \seealso{ 23 | Other log configutation functions: 24 | \code{\link{log_appender}()}, 25 | \code{\link{log_layout}()}, 26 | \code{\link{log_threshold}()} 27 | } 28 | \concept{log configutation functions} 29 | -------------------------------------------------------------------------------- /man/log_indices.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{log_indices} 4 | \alias{log_indices} 5 | \title{Returns number of currently active indices} 6 | \usage{ 7 | log_indices(namespace = "global") 8 | } 9 | \arguments{ 10 | \item{namespace}{override the default / auto-picked namespace with 11 | a custom string} 12 | } 13 | \value{ 14 | number of indices 15 | } 16 | \description{ 17 | Returns number of currently active indices 18 | } 19 | -------------------------------------------------------------------------------- /man/log_layout.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{log_layout} 4 | \alias{log_layout} 5 | \title{Get or set log record layout} 6 | \usage{ 7 | log_layout(layout = NULL, namespace = "global", index = 1) 8 | } 9 | \arguments{ 10 | \item{layout}{function defining the structure of a log record, eg 11 | \code{\link[=layout_simple]{layout_simple()}}, \code{\link[=layout_glue]{layout_glue()}} or \code{\link[=layout_glue_colors]{layout_glue_colors()}}, 12 | \code{\link[=layout_json]{layout_json()}}, or generator functions such as 13 | \code{\link[=layout_glue_generator]{layout_glue_generator()}}, default NULL} 14 | 15 | \item{namespace}{logger namespace} 16 | 17 | \item{index}{index of the logger within the namespace} 18 | } 19 | \description{ 20 | Get or set log record layout 21 | } 22 | \examples{ 23 | \dontshow{old <- logger:::namespaces_set()} 24 | log_layout(layout_json()) 25 | log_info(42) 26 | \dontshow{logger:::namespaces_set(old)} 27 | } 28 | \seealso{ 29 | Other log configutation functions: 30 | \code{\link{log_appender}()}, 31 | \code{\link{log_formatter}()}, 32 | \code{\link{log_threshold}()} 33 | } 34 | \concept{log configutation functions} 35 | -------------------------------------------------------------------------------- /man/log_level.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{log_level} 4 | \alias{log_level} 5 | \alias{log_fatal} 6 | \alias{log_error} 7 | \alias{log_warn} 8 | \alias{log_success} 9 | \alias{log_info} 10 | \alias{log_debug} 11 | \alias{log_trace} 12 | \title{Log a message with given log level} 13 | \usage{ 14 | log_level( 15 | level, 16 | ..., 17 | namespace = NA_character_, 18 | .logcall = sys.call(), 19 | .topcall = sys.call(-1), 20 | .topenv = parent.frame(), 21 | .timestamp = Sys.time() 22 | ) 23 | 24 | log_fatal( 25 | ..., 26 | namespace = NA_character_, 27 | .logcall = sys.call(), 28 | .topcall = sys.call(-1), 29 | .topenv = parent.frame(), 30 | .timestamp = Sys.time() 31 | ) 32 | 33 | log_error( 34 | ..., 35 | namespace = NA_character_, 36 | .logcall = sys.call(), 37 | .topcall = sys.call(-1), 38 | .topenv = parent.frame(), 39 | .timestamp = Sys.time() 40 | ) 41 | 42 | log_warn( 43 | ..., 44 | namespace = NA_character_, 45 | .logcall = sys.call(), 46 | .topcall = sys.call(-1), 47 | .topenv = parent.frame(), 48 | .timestamp = Sys.time() 49 | ) 50 | 51 | log_success( 52 | ..., 53 | namespace = NA_character_, 54 | .logcall = sys.call(), 55 | .topcall = sys.call(-1), 56 | .topenv = parent.frame(), 57 | .timestamp = Sys.time() 58 | ) 59 | 60 | log_info( 61 | ..., 62 | namespace = NA_character_, 63 | .logcall = sys.call(), 64 | .topcall = sys.call(-1), 65 | .topenv = parent.frame(), 66 | .timestamp = Sys.time() 67 | ) 68 | 69 | log_debug( 70 | ..., 71 | namespace = NA_character_, 72 | .logcall = sys.call(), 73 | .topcall = sys.call(-1), 74 | .topenv = parent.frame(), 75 | .timestamp = Sys.time() 76 | ) 77 | 78 | log_trace( 79 | ..., 80 | namespace = NA_character_, 81 | .logcall = sys.call(), 82 | .topcall = sys.call(-1), 83 | .topenv = parent.frame(), 84 | .timestamp = Sys.time() 85 | ) 86 | } 87 | \arguments{ 88 | \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} 89 | 90 | \item{...}{R objects that can be converted to a character vector 91 | via the active message formatter function} 92 | 93 | \item{namespace}{string referring to the \code{logger} environment / 94 | config to be used to override the target of the message record to 95 | be used instead of the default namespace, which is defined by the 96 | R package name from which the logger was called, and falls back 97 | to a common, global namespace.} 98 | 99 | \item{.logcall}{the logging call being evaluated (useful in 100 | formatters and layouts when you want to have access to the raw, 101 | unevaluated R expression)} 102 | 103 | \item{.topcall}{R expression from which the logging function was 104 | called (useful in formatters and layouts to extract the calling 105 | function's name or arguments)} 106 | 107 | \item{.topenv}{original frame of the \code{.topcall} calling function 108 | where the formatter function will be evaluated and that is used 109 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 110 | 111 | \item{.timestamp}{The time the logging occured. Defaults to the current time 112 | but may be overwritten if the logging is delayed from the time it happend} 113 | } 114 | \value{ 115 | Invisible \code{list} of \code{logger} objects. See \code{\link[=logger]{logger()}} for more details on the format. 116 | } 117 | \description{ 118 | Log a message with given log level 119 | } 120 | \examples{ 121 | \dontshow{old <- logger:::namespaces_set()} 122 | log_level(INFO, "hi there") 123 | log_info("hi there") 124 | 125 | ## output omitted 126 | log_debug("hi there") 127 | 128 | ## lower threshold and retry 129 | log_threshold(TRACE) 130 | log_debug("hi there") 131 | 132 | ## multiple lines 133 | log_info("ok {1:3} + {1:3} = {2*(1:3)}") 134 | 135 | ## use json layout 136 | log_layout(layout_json(c("time", "level"))) 137 | log_info("ok {1:3} + {1:3} = {2*(1:3)}") 138 | \dontshow{logger:::namespaces_set(old)} 139 | } 140 | -------------------------------------------------------------------------------- /man/log_levels.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/levels.R 3 | \docType{data} 4 | \name{log_levels} 5 | \alias{log_levels} 6 | \alias{OFF} 7 | \alias{FATAL} 8 | \alias{ERROR} 9 | \alias{WARN} 10 | \alias{SUCCESS} 11 | \alias{INFO} 12 | \alias{DEBUG} 13 | \alias{TRACE} 14 | \title{Log levels} 15 | \usage{ 16 | OFF 17 | 18 | FATAL 19 | 20 | ERROR 21 | 22 | WARN 23 | 24 | SUCCESS 25 | 26 | INFO 27 | 28 | DEBUG 29 | 30 | TRACE 31 | } 32 | \description{ 33 | The standard Apache logj4 log levels plus a custom level for 34 | \code{SUCCESS}. For the full list of these log levels and suggested 35 | usage, check the below Details. 36 | } 37 | \details{ 38 | List of supported log levels: 39 | \itemize{ 40 | \item \code{OFF} No events will be logged 41 | \item \code{FATAL} Severe error that will prevent the application from continuing 42 | \item \code{ERROR} An error in the application, possibly recoverable 43 | \item \code{WARN} An event that might possible lead to an error 44 | \item \code{SUCCESS} An explicit success event above the INFO level that you want to log 45 | \item \code{INFO} An event for informational purposes 46 | \item \code{DEBUG} A general debugging event 47 | \item \code{TRACE} A fine-grained debug message, typically capturing the flow through the application. 48 | } 49 | } 50 | \references{ 51 | \url{https://logging.apache.org/log4j/2.x/javadoc/log4j-api/org/apache/logging/log4j/Level.html}, 52 | \url{https://logging.apache.org/log4j/2.x/manual/customloglevels.html} 53 | } 54 | \keyword{datasets} 55 | -------------------------------------------------------------------------------- /man/log_messages.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hooks.R 3 | \name{log_messages} 4 | \alias{log_messages} 5 | \title{Injects a logger call to standard messages} 6 | \usage{ 7 | log_messages() 8 | } 9 | \description{ 10 | This function uses \code{\link[=trace]{trace()}} to add a \code{\link[=log_info]{log_info()}} function call when 11 | \code{message} is called to log the informative messages with the 12 | \code{logger} layout and appender. 13 | } 14 | \examples{ 15 | \dontrun{ 16 | log_messages() 17 | message("hi there") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /man/log_namespaces.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{log_namespaces} 4 | \alias{log_namespaces} 5 | \title{Looks up logger namespaces} 6 | \usage{ 7 | log_namespaces() 8 | } 9 | \value{ 10 | character vector of namespace names 11 | } 12 | \description{ 13 | Looks up logger namespaces 14 | } 15 | -------------------------------------------------------------------------------- /man/log_separator.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{log_separator} 4 | \alias{log_separator} 5 | \title{Logs a long line to stand out from the console} 6 | \usage{ 7 | log_separator( 8 | level = INFO, 9 | namespace = NA_character_, 10 | separator = "=", 11 | width = 80, 12 | .logcall = sys.call(), 13 | .topcall = sys.call(-1), 14 | .topenv = parent.frame(), 15 | .timestamp = Sys.time() 16 | ) 17 | } 18 | \arguments{ 19 | \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} 20 | 21 | \item{namespace}{string referring to the \code{logger} environment / 22 | config to be used to override the target of the message record to 23 | be used instead of the default namespace, which is defined by the 24 | R package name from which the logger was called, and falls back 25 | to a common, global namespace.} 26 | 27 | \item{separator}{character to be used as a separator} 28 | 29 | \item{width}{max width of message -- longer text will be wrapped into multiple lines} 30 | 31 | \item{.logcall}{the logging call being evaluated (useful in 32 | formatters and layouts when you want to have access to the raw, 33 | unevaluated R expression)} 34 | 35 | \item{.topcall}{R expression from which the logging function was 36 | called (useful in formatters and layouts to extract the calling 37 | function's name or arguments)} 38 | 39 | \item{.topenv}{original frame of the \code{.topcall} calling function 40 | where the formatter function will be evaluated and that is used 41 | to look up the \code{namespace} as well via \code{logger:::top_env_name}} 42 | 43 | \item{.timestamp}{The time the logging occured. Defaults to the current time 44 | but may be overwritten if the logging is delayed from the time it happend} 45 | } 46 | \description{ 47 | Logs a long line to stand out from the console 48 | } 49 | \examples{ 50 | \dontshow{old <- logger:::namespaces_set()} 51 | log_separator() 52 | log_separator(ERROR, separator = "!", width = 60) 53 | log_separator(ERROR, separator = "!", width = 100) 54 | logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") 55 | log_layout(logger) 56 | log_separator(ERROR, separator = "!", width = 100) 57 | log_layout(layout_blank) 58 | log_separator(ERROR, separator = "!", width = 80) 59 | \dontshow{logger:::namespaces_set(old)} 60 | } 61 | \seealso{ 62 | \code{\link[=log_with_separator]{log_with_separator()}} 63 | } 64 | -------------------------------------------------------------------------------- /man/log_shiny_input_changes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hooks.R 3 | \name{log_shiny_input_changes} 4 | \alias{log_shiny_input_changes} 5 | \title{Auto logging input changes in Shiny app} 6 | \usage{ 7 | log_shiny_input_changes( 8 | input, 9 | level = INFO, 10 | namespace = NA_character_, 11 | excluded_inputs = character() 12 | ) 13 | } 14 | \arguments{ 15 | \item{input}{passed from Shiny's \code{server}} 16 | 17 | \item{level}{log level} 18 | 19 | \item{namespace}{the name of the namespace} 20 | 21 | \item{excluded_inputs}{character vector of input names to exclude from logging} 22 | } 23 | \description{ 24 | This is to be called in the \code{server} section of the Shiny app. 25 | } 26 | \examples{ 27 | \dontrun{ 28 | library(shiny) 29 | 30 | ui <- bootstrapPage( 31 | numericInput("mean", "mean", 0), 32 | numericInput("sd", "sd", 1), 33 | textInput("title", "title", "title"), 34 | textInput("foo", "This is not used at all, still gets logged", "foo"), 35 | passwordInput("password", "Password not to be logged", "secret"), 36 | plotOutput("plot") 37 | ) 38 | 39 | server <- function(input, output) { 40 | logger::log_shiny_input_changes(input, excluded_inputs = "password") 41 | 42 | output$plot <- renderPlot({ 43 | hist(rnorm(1e3, input$mean, input$sd), main = input$title) 44 | }) 45 | } 46 | 47 | shinyApp(ui = ui, server = server) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /man/log_threshold.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{log_threshold} 4 | \alias{log_threshold} 5 | \title{Get or set log level threshold} 6 | \usage{ 7 | log_threshold(level = NULL, namespace = "global", index = 1) 8 | } 9 | \arguments{ 10 | \item{level}{see \code{\link[=log_levels]{log_levels()}}} 11 | 12 | \item{namespace}{logger namespace} 13 | 14 | \item{index}{index of the logger within the namespace} 15 | } 16 | \value{ 17 | currently set log level threshold 18 | } 19 | \description{ 20 | Get or set log level threshold 21 | } 22 | \examples{ 23 | \dontshow{old <- logger:::namespaces_set()} 24 | ## check the currently set log level threshold 25 | log_threshold() 26 | 27 | ## change the log level threshold to WARN 28 | log_threshold(WARN) 29 | log_info(1) 30 | log_warn(2) 31 | 32 | ## add another logger with a lower log level threshold and check the number of logged messages 33 | log_threshold(INFO, index = 2) 34 | log_info(1) 35 | log_warn(2) 36 | 37 | ## set the log level threshold in all namespaces to ERROR 38 | log_threshold(ERROR, namespace = log_namespaces()) 39 | \dontshow{logger:::namespaces_set(old)} 40 | } 41 | \seealso{ 42 | Other log configutation functions: 43 | \code{\link{log_appender}()}, 44 | \code{\link{log_formatter}()}, 45 | \code{\link{log_layout}()} 46 | } 47 | \concept{log configutation functions} 48 | -------------------------------------------------------------------------------- /man/log_tictoc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{log_tictoc} 4 | \alias{log_tictoc} 5 | \title{Tic-toc logging} 6 | \usage{ 7 | log_tictoc(..., level = INFO, namespace = NA_character_) 8 | } 9 | \arguments{ 10 | \item{...}{passed to \code{\link[=log_level]{log_level()}}} 11 | 12 | \item{level}{see \code{\link[=log_levels]{log_levels()}}} 13 | 14 | \item{namespace}{x} 15 | } 16 | \description{ 17 | Tic-toc logging 18 | } 19 | \examples{ 20 | log_tictoc("warming up") 21 | Sys.sleep(0.1) 22 | log_tictoc("running") 23 | Sys.sleep(0.1) 24 | log_tictoc("running") 25 | Sys.sleep(runif(1)) 26 | log_tictoc("and running") 27 | } 28 | \author{ 29 | Thanks to Neal Fultz for the idea and original implementation! 30 | } 31 | -------------------------------------------------------------------------------- /man/log_warnings.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hooks.R 3 | \name{log_warnings} 4 | \alias{log_warnings} 5 | \title{Injects a logger call to standard warnings} 6 | \usage{ 7 | log_warnings(muffle = getOption("logger_muffle_warnings", FALSE)) 8 | } 9 | \arguments{ 10 | \item{muffle}{if TRUE, the warning is not shown after being logged} 11 | } 12 | \description{ 13 | This function uses \code{trace} to add a \code{log_warn} function call when 14 | \code{warning} is called to log the warning messages with the \code{logger} 15 | layout and appender. 16 | } 17 | \examples{ 18 | \dontrun{ 19 | log_warnings() 20 | for (i in 1:5) { 21 | Sys.sleep(runif(1)) 22 | warning(i) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /man/log_with_separator.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{log_with_separator} 4 | \alias{log_with_separator} 5 | \title{Logs a message in a very visible way} 6 | \usage{ 7 | log_with_separator( 8 | ..., 9 | level = INFO, 10 | namespace = NA_character_, 11 | separator = "=", 12 | width = 80 13 | ) 14 | } 15 | \arguments{ 16 | \item{...}{R objects that can be converted to a character vector 17 | via the active message formatter function} 18 | 19 | \item{level}{log level, see \code{\link[=log_levels]{log_levels()}} for more details} 20 | 21 | \item{namespace}{string referring to the \code{logger} environment / 22 | config to be used to override the target of the message record to 23 | be used instead of the default namespace, which is defined by the 24 | R package name from which the logger was called, and falls back 25 | to a common, global namespace.} 26 | 27 | \item{separator}{character to be used as a separator} 28 | 29 | \item{width}{max width of message -- longer text will be wrapped into multiple lines} 30 | } 31 | \description{ 32 | Logs a message in a very visible way 33 | } 34 | \examples{ 35 | \dontshow{old <- logger:::namespaces_set()} 36 | log_with_separator("An important message") 37 | log_with_separator("Some critical KPI down!!!", separator = "$") 38 | log_with_separator("This message is worth a {1e3} words") 39 | log_with_separator(paste( 40 | "A very important message with a bunch of extra words that will", 41 | "eventually wrap into a multi-line message for our quite nice demo :wow:" 42 | )) 43 | log_with_separator( 44 | paste( 45 | "A very important message with a bunch of extra words that will", 46 | "eventually wrap into a multi-line message for our quite nice demo :wow:" 47 | ), 48 | width = 60 49 | ) 50 | log_with_separator("Boo!", level = FATAL) 51 | log_layout(layout_blank) 52 | log_with_separator("Boo!", level = FATAL) 53 | logger <- layout_glue_generator(format = "{node}/{pid}/{namespace}/{fn} {time} {level}: {msg}") 54 | log_layout(logger) 55 | log_with_separator("Boo!", level = FATAL, width = 120) 56 | \dontshow{logger:::namespaces_set(old)} 57 | } 58 | \seealso{ 59 | \code{\link[=log_separator]{log_separator()}} 60 | } 61 | -------------------------------------------------------------------------------- /man/logger-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger-package.R 3 | \docType{package} 4 | \name{logger-package} 5 | \alias{logger-package} 6 | \title{logger: A Lightweight, Modern and Flexible Logging Utility} 7 | \description{ 8 | \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} 9 | 10 | 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. 11 | } 12 | \seealso{ 13 | Useful links: 14 | \itemize{ 15 | \item \url{https://daroczig.github.io/logger/} 16 | \item Report bugs at \url{https://github.com/daroczig/logger/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Gergely Daróczi \email{daroczig@rapporter.net} (\href{https://orcid.org/0000-0003-3149-8537}{ORCID}) 22 | 23 | Authors: 24 | \itemize{ 25 | \item Hadley Wickham \email{hadley@posit.co} (\href{https://orcid.org/0000-0003-4757-117X}{ORCID}) 26 | } 27 | 28 | Other contributors: 29 | \itemize{ 30 | \item System1 [funder] 31 | } 32 | 33 | } 34 | \keyword{internal} 35 | -------------------------------------------------------------------------------- /man/logger.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{logger} 4 | \alias{logger} 5 | \title{Generate logging utility} 6 | \usage{ 7 | logger(threshold, formatter, layout, appender) 8 | } 9 | \arguments{ 10 | \item{threshold}{omit log messages below this \code{\link[=log_levels]{log_levels()}}} 11 | 12 | \item{formatter}{function pre-processing the message of the log 13 | record when it's not wrapped in a \code{\link[=skip_formatter]{skip_formatter()}} call} 14 | 15 | \item{layout}{function rendering the layout of the actual log 16 | record} 17 | 18 | \item{appender}{function writing the log record} 19 | } 20 | \value{ 21 | A function taking the log \code{level} to compare with the set 22 | threshold, all the \code{...} arguments passed to the formatter 23 | function, besides the standard \code{namespace}, \code{.logcall}, 24 | \code{.topcall} and \code{.topenv} arguments (see \code{\link[=log_level]{log_level()}} for more 25 | details). The function invisibly returns a list including the 26 | original \code{level}, \code{namespace}, all \code{...} transformed to a list as 27 | \code{params}, the log \code{message} (after calling the \code{formatter} 28 | function) and the log \code{record} (after calling the \code{layout} 29 | function), and a list of \code{handlers} with the \code{formatter}, 30 | \code{layout} and \code{appender} functions. 31 | } 32 | \description{ 33 | A logger consists of a log level \code{threshold}, a log message 34 | \code{formatter} function, a log record \code{layout} formatting function and 35 | the \code{appender} function deciding on the destination of the log 36 | record. For more details, see the package \code{README.md}. 37 | } 38 | \details{ 39 | By default, a general logger definition is created when loading the \code{logger} package, that uses 40 | \itemize{ 41 | \item \code{\link[=INFO]{INFO()}} (or as per the \code{LOGGER_LOG_LEVEL} environment variable override) as the log level threshold 42 | \item \code{\link[=layout_simple]{layout_simple()}} as the layout function showing the log level, timestamp and log message 43 | \item \code{\link[=formatter_glue]{formatter_glue()}} (or \code{\link[=formatter_sprintf]{formatter_sprintf()}} if \pkg{glue} is not installed) as the 44 | default formatter function transforming the R objects to be logged to a character vector 45 | \item \code{\link[=appender_console]{appender_console()}} as the default log record destination 46 | } 47 | } 48 | \note{ 49 | It's quite unlikely that you need to call this function 50 | directly, but instead set the logger parameters and functions at 51 | \code{\link[=log_threshold]{log_threshold()}}, \code{\link[=log_formatter]{log_formatter()}}, \code{\link[=log_layout]{log_layout()}} and 52 | \code{\link[=log_appender]{log_appender()}} and then call \code{\link[=log_levels]{log_levels()}} and its 53 | derivatives, such as \code{\link[=log_info]{log_info()}} directly. 54 | } 55 | \examples{ 56 | \dontrun{ 57 | do.call(logger, logger:::namespaces$global[[1]])(INFO, 42) 58 | do.call(logger, logger:::namespaces$global[[1]])(INFO, "{pi}") 59 | x <- 42 60 | do.call(logger, logger:::namespaces$global[[1]])(INFO, "{x}^2 = {x^2}") 61 | } 62 | } 63 | \references{ 64 | For more details, see the Anatomy of a Log Request 65 | vignette at 66 | \url{https://daroczig.github.io/logger/articles/anatomy.html}. 67 | } 68 | -------------------------------------------------------------------------------- /man/skip_formatter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/formatters.R 3 | \name{skip_formatter} 4 | \alias{skip_formatter} 5 | \title{Skip the formatter function} 6 | \usage{ 7 | skip_formatter(message, ...) 8 | } 9 | \arguments{ 10 | \item{message}{character vector directly passed to the appender 11 | function in \code{\link[=logger]{logger()}}} 12 | 13 | \item{...}{should be never set} 14 | } 15 | \value{ 16 | character vector with \code{skip_formatter} attribute set to 17 | \code{TRUE} 18 | } 19 | \description{ 20 | Adds the \code{skip_formatter} attribute to an object so that logger 21 | will skip calling the formatter function(s). This is useful if you 22 | want to preprocess the log message with a custom function instead 23 | of the active formatter function(s). Note that the \code{message} should 24 | be a string, and \code{skip_formatter} should be the only input for the 25 | logging function to make this work. 26 | } 27 | -------------------------------------------------------------------------------- /man/with_log_threshold.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{with_log_threshold} 4 | \alias{with_log_threshold} 5 | \title{Evaluate R expression with a temporarily updated log level threshold} 6 | \usage{ 7 | with_log_threshold( 8 | expression, 9 | threshold = ERROR, 10 | namespace = "global", 11 | index = 1 12 | ) 13 | } 14 | \arguments{ 15 | \item{expression}{R command} 16 | 17 | \item{threshold}{\code{\link[=log_levels]{log_levels()}}} 18 | 19 | \item{namespace}{logger namespace} 20 | 21 | \item{index}{index of the logger within the namespace} 22 | } 23 | \description{ 24 | Evaluate R expression with a temporarily updated log level threshold 25 | } 26 | \examples{ 27 | \dontshow{old <- logger:::namespaces_set()} 28 | log_threshold(TRACE) 29 | log_trace("Logging everything!") 30 | x <- with_log_threshold( 31 | { 32 | log_info("Now we are temporarily suppressing eg INFO messages") 33 | log_warn("WARN") 34 | log_debug("Debug messages are suppressed as well") 35 | log_error("ERROR") 36 | invisible(42) 37 | }, 38 | threshold = WARN 39 | ) 40 | x 41 | log_trace("DONE") 42 | \dontshow{logger:::namespaces_set(old)} 43 | } 44 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daroczig/logger/9adf2c4e697140e12dab86c78b53f85b8fb79b95/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(logger) 11 | 12 | test_check("logger") 13 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/formatters.md: -------------------------------------------------------------------------------- 1 | # glue works 2 | 3 | Code 4 | formatter_glue("malformed {") 5 | Condition 6 | Error in `h()`: 7 | ! `glue` failed in `formatter_glue` on: 8 | 9 | chr "malformed {" 10 | 11 | Raw error message: 12 | 13 | Expecting '}' 14 | 15 | Please consider using another `log_formatter` or `skip_formatter` on strings with curly braces. 16 | 17 | # glue gives informative error if message contains curlies 18 | 19 | Code 20 | log_info("hi{") 21 | Condition 22 | Error in `h()`: 23 | ! `glue` failed in `formatter_glue` on: 24 | 25 | chr "hi{" 26 | 27 | Raw error message: 28 | 29 | Expecting '}' 30 | 31 | Please consider using another `log_formatter` or `skip_formatter` on strings with curly braces. 32 | 33 | # glue_safe works 34 | 35 | Code 36 | formatter_glue_safe("Hi {42}") 37 | Condition 38 | Error in `value[[3L]]()`: 39 | ! `glue_safe` failed in `formatter_glue_safe` on: 40 | 41 | chr "Hi {42}" 42 | 43 | Raw error message: 44 | 45 | object '42' not found 46 | 47 | Please consider using another `log_formatter` or `skip_formatter` on strings with curly braces. 48 | Code 49 | formatter_glue_safe("malformed {") 50 | Condition 51 | Error in `value[[3L]]()`: 52 | ! `glue_safe` failed in `formatter_glue_safe` on: 53 | 54 | chr "malformed {" 55 | 56 | Raw error message: 57 | 58 | Expecting '}' 59 | 60 | Please consider using another `log_formatter` or `skip_formatter` on strings with curly braces. 61 | 62 | # sprintf works 63 | 64 | Code 65 | formatter_sprintf("%s and %i", 1) 66 | Condition 67 | Error in `sprintf()`: 68 | ! too few arguments 69 | 70 | # skip formatter 71 | 72 | Code 73 | log_info(skip_formatter("hi {x}", x = 4)) 74 | Condition 75 | Error in `skip_formatter()`: 76 | ! Cannot skip the formatter function if further arguments are passed besides the actual log message(s) 77 | 78 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/helpers.md: -------------------------------------------------------------------------------- 1 | # log failure 2 | 3 | Code 4 | capture.output(log_failure(foobar)) 5 | Condition 6 | Error: 7 | ! object 'foobar' not found 8 | 9 | # log with separator 10 | 11 | Code 12 | log_with_separator(42) 13 | Output 14 | INFO =========================================================================== 15 | INFO = 42 = 16 | INFO =========================================================================== 17 | Code 18 | log_with_separator(42, separator = "|") 19 | Output 20 | INFO ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 21 | INFO | 42 | 22 | INFO ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 23 | 24 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/hooks.md: -------------------------------------------------------------------------------- 1 | # log_messages 2 | 3 | Code 4 | writeLines(eval_outside("log_messages()", "message(42)")) 5 | Output 6 | INFO 42 7 | 8 | # log_warnings 9 | 10 | Code 11 | writeLines(eval_outside("log_warnings(TRUE)", "warning(42)", "log(-1)")) 12 | Output 13 | WARN 42 14 | WARN NaNs produced 15 | 16 | # log_errors 17 | 18 | Code 19 | writeLines(eval_outside("log_errors()", "stop(42)")) 20 | Output 21 | ERROR 42 22 | Code 23 | writeLines(eval_outside("log_errors()", "foobar")) 24 | Output 25 | ERROR object 'foobar' not found 26 | Code 27 | writeLines(eval_outside("log_errors()", "f<-function(x) {42 * \"foobar\"}; f()")) 28 | Output 29 | ERROR non-numeric argument to binary operator 30 | Code 31 | writeLines(eval_outside("log_errors(traceback = TRUE)", 32 | "source(\"helper.R\", keep.source = TRUE)", "function_that_fails()")) 33 | Output 34 | ERROR I'm failing 35 | ERROR Traceback: 36 | ERROR 2: stop("I'm failing") at helper.R #46 37 | ERROR 1: function_that_fails() 38 | 39 | # shiny input initialization is detected 40 | 41 | Code 42 | writeLines(obs) 43 | Output 44 | INFO mock-session Default Shiny inputs initialized: {} 45 | 46 | # shiny input initialization is detected with different log-level 47 | 48 | Code 49 | writeLines(obs) 50 | Output 51 | ERROR mock-session Default Shiny inputs initialized: {} 52 | 53 | # shiny input change is detected 54 | 55 | Code 56 | writeLines(obs) 57 | Output 58 | INFO mock-session Default Shiny inputs initialized: {} 59 | INFO mock-session Shiny input change detected in a: NULL -> 2 60 | 61 | # shiny input change is logged with different level 62 | 63 | Code 64 | writeLines(obs) 65 | Output 66 | ERROR mock-session Default Shiny inputs initialized: {} 67 | ERROR mock-session Shiny input change detected in a: NULL -> 2 68 | 69 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/layouts.md: -------------------------------------------------------------------------------- 1 | # JSON layout warns if you include msg 2 | 3 | Code 4 | layout <- layout_json(fields = "msg") 5 | Condition 6 | Warning in `layout_json()`: 7 | 'msg' is always automatically included 8 | 9 | # must throw errors 10 | 11 | Code 12 | layout_simple(FOOBAR) 13 | Condition 14 | Error: 15 | ! object 'FOOBAR' not found 16 | Code 17 | layout_simple(42) 18 | Condition 19 | Error in `layout_simple()`: 20 | ! argument "msg" is missing, with no default 21 | Code 22 | layout_simple(msg = "foobar") 23 | Condition 24 | Error in `layout_simple()`: 25 | ! argument "level" is missing, with no default 26 | 27 | --- 28 | 29 | Code 30 | layout_glue(FOOBAR) 31 | Condition 32 | Error: 33 | ! object 'FOOBAR' not found 34 | Code 35 | layout_glue(42) 36 | Condition 37 | Error in `layout_glue()`: 38 | ! Invalid log level, see ?log_levels 39 | Code 40 | layout_glue(msg = "foobar") 41 | Condition 42 | Error in `layout_glue()`: 43 | ! argument "level" is missing, with no default 44 | Code 45 | layout_glue(level = 53, msg = "foobar") 46 | Condition 47 | Error in `layout_glue()`: 48 | ! Invalid log level, see ?log_levels 49 | 50 | # log_info() captures local info 51 | 52 | Code 53 | log_info("foobar") 54 | Output 55 | logger / global / logger / eval / eval(expr, envir) 56 | Code 57 | f() 58 | Output 59 | logger / global / logger / f / f() 60 | Code 61 | g() 62 | Output 63 | logger / global / logger / f / f() 64 | 65 | # log_info() captures package info 66 | 67 | Code 68 | logger_tester_function(INFO, "x = ") 69 | Output 70 | logger.tester INFO x = 0.0807501375675201 71 | Code 72 | logger_info_tester_function("everything = ") 73 | Output 74 | logger.tester INFO everything = 42 75 | 76 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/logger.md: -------------------------------------------------------------------------------- 1 | # setters check inputs 2 | 3 | Code 4 | log_appender(1) 5 | Condition 6 | Error in `log_appender()`: 7 | ! `appender` must be a function 8 | Code 9 | log_formatter(1) 10 | Condition 11 | Error in `log_formatter()`: 12 | ! `formatter` must be a function 13 | Code 14 | log_layout(1) 15 | Condition 16 | Error in `log_layout()`: 17 | ! `layout` must be a function 18 | Code 19 | log_threshold("x") 20 | Condition 21 | Error in `validate_log_level()`: 22 | ! Invalid log level 23 | 24 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/try.md: -------------------------------------------------------------------------------- 1 | # %except% logs errors and returns default value 2 | 3 | Code 4 | out <- f() 5 | Output 6 | except / global / logger / f / f() 7 | WARN Running '1' as 'FunDoesNotExist(1:10)' failed: 'could not find function "FunDoesNotExist"' 8 | 9 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/utils.md: -------------------------------------------------------------------------------- 1 | # fail_on_missing_package 2 | 3 | Code 4 | fail_on_missing_package("logger", "9.9.9", call = quote(f())) 5 | Condition 6 | Error: 7 | ! Please install min. 9.9.9 version of logger to use f 8 | Code 9 | fail_on_missing_package("an.R.package-that-doesNotExists", call = quote(f())) 10 | Condition 11 | Error: 12 | ! Please install the 'an.R.package-that-doesNotExists' package to use f 13 | 14 | # validate_log_level 15 | 16 | Code 17 | validate_log_level("FOOBAR") 18 | Condition 19 | Error in `validate_log_level()`: 20 | ! Invalid log level 21 | 22 | -------------------------------------------------------------------------------- /tests/testthat/helper.R: -------------------------------------------------------------------------------- 1 | # Do not move this to another line as the location of this piece of code is tested for 2 | test_info <- function() { 3 | log_info("TEST") 4 | } 5 | 6 | local_test_logger <- function(threshold = INFO, 7 | formatter = formatter_glue, 8 | layout = layout_simple, 9 | appender = appender_stdout, 10 | namespace = "global", 11 | frame = parent.frame()) { 12 | old <- namespaces[[namespace]] 13 | 14 | namespaces[[namespace]] <- list( 15 | default = list( 16 | threshold = as.loglevel(threshold), 17 | layout = layout, 18 | formatter = formatter, 19 | appender = appender 20 | ) 21 | ) 22 | 23 | withr::defer(namespaces[[namespace]] <- old, frame) 24 | invisible() 25 | } 26 | 27 | eval_outside <- function(...) { 28 | input <- normalizePath(withr::local_tempfile(lines = character()), winslash = "/") 29 | output <- normalizePath(withr::local_tempfile(lines = character()), winslash = "/") 30 | writeLines(con = input, c( 31 | "library(logger)", 32 | "log_layout(layout_glue_generator('{level} {msg}'))", 33 | paste0("log_appender(appender_file('", output, "'))"), 34 | ... 35 | )) 36 | path <- file.path(R.home("bin"), "Rscript") 37 | if (Sys.info()[["sysname"]] == "Windows") { 38 | path <- paste0(path, ".exe") 39 | } 40 | suppressWarnings(system2(path, input, stdout = TRUE, stderr = TRUE)) 41 | readLines(output) 42 | } 43 | 44 | # This function is needed to test traceback logging 45 | function_that_fails <- function() { 46 | stop("I'm failing") 47 | } 48 | -------------------------------------------------------------------------------- /tests/testthat/test-appenders.R: -------------------------------------------------------------------------------- 1 | test_that("append to file", { 2 | t <- withr::local_tempfile() 3 | local_test_logger( 4 | appender = appender_file(t), 5 | layout = layout_glue_generator("{level} {msg}"), 6 | threshold = TRACE 7 | ) 8 | log_info("foobar") 9 | log_info("{1:2}") 10 | expect_equal(length(readLines(t)), 3) 11 | expect_equal(readLines(t)[1], "INFO foobar") 12 | expect_equal(readLines(t)[3], "INFO 2") 13 | }) 14 | 15 | test_that("overwrite file", { 16 | t <- withr::local_tempfile() 17 | local_test_logger( 18 | appender = appender_file(t, append = FALSE), 19 | layout = layout_glue_generator("{level} {msg}"), 20 | threshold = TRACE 21 | ) 22 | 23 | log_info("foobar") 24 | log_info("{1:2}") 25 | expect_equal(length(readLines(t)), 2) 26 | expect_equal(readLines(t), c("INFO 1", "INFO 2")) 27 | 28 | log_info("42") 29 | expect_equal(length(readLines(t)), 1) 30 | expect_equal(readLines(t), "INFO 42") 31 | }) 32 | 33 | test_that("append to file + print to console", { 34 | t <- withr::local_tempfile() 35 | local_test_logger( 36 | appender = appender_tee(t), 37 | layout = layout_glue_generator("{level} {msg}"), 38 | ) 39 | 40 | expect_output(log_info("foobar"), "INFO foobar") 41 | capture.output(log_info("{1:2}")) 42 | expect_equal(length(readLines(t)), 3) 43 | expect_equal(readLines(t)[1], "INFO foobar") 44 | }) 45 | 46 | test_that("logrotate", { 47 | t <- withr::local_tempdir() 48 | f <- file.path(t, "log") 49 | local_test_logger( 50 | appender = appender_file(f, max_lines = 2, max_files = 5L), 51 | layout = layout_glue_generator("{msg}"), 52 | threshold = TRACE 53 | ) 54 | 55 | for (i in 1:24) log_info(i) 56 | expect_equal(length(readLines(f)), 2) 57 | expect_equal(length(list.files(t)), 5) 58 | expect_equal(readLines(f), c("23", "24")) 59 | log_info("42") 60 | expect_equal(length(readLines(f)), 1) 61 | expect_equal(readLines(f), "42") 62 | }) 63 | 64 | test_that("async logging", { 65 | skip_on_cran() 66 | 67 | t <- withr::local_tempfile() 68 | local_test_logger( 69 | layout = layout_blank, 70 | appender = appender_async(appender_file(file = t)) 71 | ) 72 | 73 | for (i in 1:5) log_info(i) 74 | Sys.sleep(1) 75 | 76 | expect_equal(readLines(t)[1], "1") 77 | expect_equal(length(readLines(t)), 5) 78 | }) 79 | -------------------------------------------------------------------------------- /tests/testthat/test-eval.R: -------------------------------------------------------------------------------- 1 | test_that("single line", { 2 | local_test_logger(layout = layout_glue_generator("{level} {msg}")) 3 | 4 | expect_output(log_eval(4, INFO), sprintf("INFO %s => %s", shQuote(4), shQuote(4))) 5 | }) 6 | 7 | test_that("multi line", { 8 | local_test_logger(layout = layout_glue_generator("{level} {msg}")) 9 | 10 | expect_output(log_eval(4, INFO, multiline = TRUE), "Running expression") 11 | expect_output(log_eval(4, INFO, multiline = TRUE), "Results:") 12 | expect_output(log_eval(4, INFO, multiline = TRUE), "INFO 4") 13 | }) 14 | 15 | test_that("invisible return", { 16 | local_test_logger(layout = layout_glue_generator("{level} {msg}")) 17 | expect_output(log_eval(require(logger), INFO), sprintf( 18 | "INFO %s => %s", 19 | shQuote("require\\(logger\\)"), 20 | shQuote(TRUE) 21 | )) 22 | }) 23 | 24 | test_that("lower log level", { 25 | local_test_logger(TRACE, layout = layout_glue_generator("{level} {msg}")) 26 | expect_output(log_eval(4), sprintf("TRACE %s => %s", shQuote(4), shQuote(4))) 27 | }) 28 | -------------------------------------------------------------------------------- /tests/testthat/test-formatters.R: -------------------------------------------------------------------------------- 1 | test_that("glue works", { 2 | local_test_logger(formatter = formatter_glue) 3 | a <- 43 4 | 5 | expect_equal(formatter_glue("Hi"), "Hi") 6 | expect_equal(formatter_glue(" Hi"), " Hi") 7 | expect_equal(formatter_glue("1 + {1}"), "1 + 1") 8 | expect_equal(formatter_glue("{1:2}"), as.character(1:2)) 9 | expect_equal(formatter_glue("pi is {round(pi, 2)}"), "pi is 3.14") 10 | expect_equal(formatter_glue("Hi {42}"), "Hi 42") 11 | expect_equal(formatter_glue("Hi {a}", a = 42), "Hi 42") 12 | expect_equal(formatter_glue("Hi {1:2}"), paste("Hi", 1:2)) 13 | 14 | expect_output(do.call(logger, namespaces$global[[1]])(INFO, 42), "42") 15 | expect_output(do.call(logger, namespaces$global[[1]])(INFO, "Hi {a}"), "43") 16 | 17 | expect_equal(formatter_glue("Hi {a}"), "Hi 43") 18 | expect_output(log_info("Hi {a}"), "43") 19 | expect_output(log_warn("Hi {a}"), "43") 20 | f <- function() log_info("Hi {a}") 21 | expect_output(f(), "43") 22 | 23 | local_test_logger( 24 | formatter = formatter_glue, 25 | appender = appender_void, 26 | ) 27 | expect_snapshot(formatter_glue("malformed {"), error = TRUE) 28 | expect_no_error(formatter_glue("malformed {{")) 29 | 30 | ## nolint start 31 | ## disabled for https://github.com/atalv/azlogr/issues/35 32 | ## expect_warning(formatter_glue(NULL)) 33 | ## expect_warning(log_info(NULL)) 34 | ## expect_warning(log_info(a = 42, b = "foobar")) 35 | ## nolint end 36 | }) 37 | 38 | test_that("glue gives informative error if message contains curlies", { 39 | local_test_logger(formatter = formatter_glue) 40 | expect_snapshot(log_info("hi{"), error = TRUE) 41 | }) 42 | 43 | test_that("glue_safe works", { 44 | local_test_logger(formatter = formatter_glue_safe) 45 | 46 | expect_equal(formatter_glue_safe("Hi"), "Hi") 47 | expect_equal(formatter_glue_safe(" Hi"), " Hi") 48 | expect_equal(formatter_glue_safe("Hi {a}", a = 42), "Hi 42") 49 | 50 | a <- 43 51 | expect_equal(formatter_glue_safe("Hi {a}"), "Hi 43") 52 | expect_output(log_info("Hi {a}"), "43") 53 | expect_output(log_warn("Hi {a}"), "43") 54 | f <- function() log_info("Hi {a}") 55 | expect_output(f(), "43") 56 | 57 | expect_snapshot(error = TRUE, { 58 | formatter_glue_safe("Hi {42}") 59 | formatter_glue_safe("malformed {") 60 | }) 61 | expect_no_error(formatter_glue_safe("malformed {{")) 62 | }) 63 | 64 | test_that("sprintf works", { 65 | local_test_logger(formatter = formatter_sprintf) 66 | 67 | expect_equal(formatter_sprintf("Hi"), "Hi") 68 | expect_equal(formatter_sprintf("Hi %s", 42), "Hi 42") 69 | expect_equal(formatter_sprintf("Hi %s", 1:2), paste("Hi", 1:2)) 70 | expect_equal(formatter_sprintf("1 + %s", 1), "1 + 1") 71 | expect_equal(formatter_sprintf("=>%2i", 2), "=> 2") 72 | expect_equal(formatter_sprintf("%s", 1:2), as.character(1:2)) 73 | expect_equal(formatter_sprintf("pi is %s", round(pi, 2)), "pi is 3.14") 74 | expect_equal(formatter_sprintf("pi is %1.2f", pi), "pi is 3.14") 75 | 76 | expect_snapshot(formatter_sprintf("%s and %i", 1), error = TRUE) 77 | expect_equal(formatter_sprintf("%s and %i", 1, 2), "1 and 2") 78 | 79 | a <- 43 80 | expect_output(log_info("Hi %s", a), "43") 81 | expect_equal(formatter_sprintf("Hi %s", a), "Hi 43") 82 | f <- function() log_info("Hi %s", a) 83 | expect_output(f(), "43") 84 | }) 85 | 86 | 87 | test_that("glue+sprintf works", { 88 | result <- c( 89 | "Hi foo, did you know that 2*4=8?", 90 | "Hi bar, did you know that 2*4=8?" 91 | ) 92 | 93 | expect_equal(formatter_glue_or_sprintf("Hi ", "{c('foo', 'bar')}, did you know that 2*4={2*4}?"), result) 94 | expect_equal(formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4={2*4}?"), result) 95 | expect_equal(formatter_glue_or_sprintf("Hi {c('foo', 'bar')}, did you know that 2*4=%s?", 2 * 4), result) 96 | expect_equal(formatter_glue_or_sprintf("Hi %s, did you know that 2*4={2*4}?", c("foo", "bar")), result) 97 | expect_equal(formatter_glue_or_sprintf("Hi %s, did you know that 2*4=%s?", c("foo", "bar"), 2 * 4), result) 98 | 99 | expect_equal(formatter_glue_or_sprintf("%s and %i"), "%s and %i") 100 | expect_equal(formatter_glue_or_sprintf("%s and %i", 1), "%s and %i") 101 | expect_equal(formatter_glue_or_sprintf("fun{fun}"), "fun{fun}") 102 | 103 | for (fn in c(formatter_sprintf, formatter_glue_or_sprintf)) { 104 | local_test_logger(formatter = fn, appender = appender_void) 105 | expect_no_error(log_info(character(0))) 106 | 107 | local_test_logger(formatter = fn) 108 | expect_output(log_info(character(0)), "INFO") 109 | } 110 | }) 111 | 112 | test_that("cli works", { 113 | local_test_logger(formatter = formatter_cli) 114 | 115 | a <- 43 116 | 117 | expect_equal(formatter_cli("Hi"), "Hi") 118 | expect_equal(formatter_cli("{.arg Hi}"), "`Hi`") 119 | expect_equal(formatter_cli("1 + {1}"), "1 + 1") 120 | expect_equal(formatter_cli("{1:2}"), "1 and 2") 121 | expect_equal(formatter_cli("pi is {round(pi, 2)}"), "pi is 3.14") 122 | expect_equal(formatter_cli("Hi {42}"), "Hi 42") 123 | expect_equal(formatter_cli("Hi {1:2}"), paste("Hi 1 and 2")) 124 | 125 | expect_output(do.call(logger, namespaces$global[[1]])(INFO, 42), "42") 126 | expect_output(do.call(logger, namespaces$global[[1]])(INFO, "Hi {a}"), "43") 127 | 128 | expect_equal(formatter_cli("Hi {a}"), "Hi 43") 129 | expect_output(log_info("Hi {a}"), "43") 130 | expect_output(log_warn("Hi {a}"), "43") 131 | f <- function() log_info("Hi {a}") 132 | expect_output(f(), "43") 133 | }) 134 | 135 | test_that("formatter_logging works", { 136 | local_test_logger(formatter = formatter_logging) 137 | 138 | expect_output(log_info("42"), "42") 139 | expect_output(log_info(42), "42") 140 | expect_output(log_info(4 + 2), "4 \\+ 2") 141 | expect_output(log_info(4 + 2), "6") 142 | expect_output(log_info("foo %s", "bar"), "foo bar") 143 | expect_output(log_info(12, 100 + 100, 2 * 2), "12") 144 | expect_output(log_info(12, 100 + 100, 2 * 2), "100 \\+ 100") 145 | expect_output(log_info(12, 100 + 100, 2 * 2), "200") 146 | expect_output(log_info(12, 100 + 100, 2 * 2), "2 \\* 2") 147 | expect_output(log_info(12, 100 + 100, 2 * 2), "4") 148 | }) 149 | 150 | test_that("special chars in the text work", { 151 | array <- "[1, 2, 3, 4]" 152 | object <- '{"x": 1, "y": 2}' 153 | expect_equal(formatter_glue("JSON: {array}"), paste0("JSON: ", array)) 154 | expect_equal(formatter_glue("JSON: {object}"), paste0("JSON: ", object)) 155 | 156 | local_test_logger() 157 | expect_output(log_info("JSON: {array}"), paste0("JSON: ", array), fixed = TRUE) 158 | expect_output(log_info("JSON: {object}"), paste0("JSON: ", object), fixed = TRUE) 159 | }) 160 | 161 | test_that("pander formatter", { 162 | local_test_logger(formatter = formatter_pander) 163 | # pander partially matches coef to coefficient 164 | withr::local_options(warnPartialMatchDollar = FALSE) 165 | 166 | expect_output(log_info(42), "_42_") 167 | expect_output(log_info("42"), "42") 168 | expect_output(log_info(head(iris)), "Sepal.Length") 169 | expect_output(log_info(lm(hp ~ wt, mtcars)), "Fitting linear model") 170 | }) 171 | 172 | test_that("paste formatter in actual logs", { 173 | local_test_logger(formatter = formatter_paste) 174 | expect_output(log_info("hi", 5), "hi 5") 175 | }) 176 | 177 | test_that("skip formatter", { 178 | local_test_logger(formatter = formatter_glue) 179 | expect_output(log_info(skip_formatter("hi {pi}")), "hi \\{pi\\}") 180 | expect_snapshot(log_info(skip_formatter("hi {x}", x = 4)), error = TRUE) 181 | }) 182 | 183 | test_that("skip formatter", { 184 | local_test_logger(formatter = formatter_json) 185 | expect_output(log_info(skip_formatter("hi {pi}")), "hi \\{pi\\}") 186 | expect_output(log_info(x = 1), '\\{"x":1\\}') 187 | }) 188 | -------------------------------------------------------------------------------- /tests/testthat/test-helpers.R: -------------------------------------------------------------------------------- 1 | test_that("separator", { 2 | local_test_logger(layout = layout_blank) 3 | expect_output(log_separator(), "={80,80}") 4 | 5 | local_test_logger() 6 | expect_output(log_separator(separator = "-"), "---") 7 | expect_output(log_separator(), "INFO") 8 | expect_output(log_separator(WARN), "WARN") 9 | }) 10 | 11 | test_that("tictoc", { 12 | local_test_logger() 13 | local_mocked_bindings(Sys.time = function() as.POSIXct("2024-01-01 00:00:00")) 14 | expect_output(log_tictoc(), "timer tic 0 secs") 15 | ## simulate time passing 16 | local_mocked_bindings(Sys.time = function() as.POSIXct("2024-01-01 00:01:00")) 17 | expect_output(log_tictoc(), "timer toc 1 mins") 18 | }) 19 | 20 | test_that("elapsed", { 21 | local_test_logger() 22 | 23 | proc_null <- proc.time() * 0 24 | 25 | with_mocked_bindings( 26 | expect_output(log_elapsed(), "timer 2 secs elapsed"), 27 | proc.time = function() proc_null + 2 28 | ) 29 | 30 | with_mocked_bindings( 31 | expect_output(log_elapsed_start(), "starting global timer"), 32 | proc.time = function() proc_null + 1 33 | ) 34 | 35 | with_mocked_bindings( 36 | expect_output(log_elapsed(), "timer 1 secs elapsed"), 37 | proc.time = function() proc_null + 2 38 | ) 39 | 40 | with_mocked_bindings( 41 | expect_silent(log_elapsed_start(quiet = TRUE)), 42 | proc.time = function() proc_null 43 | ) 44 | }) 45 | 46 | test_that("log with separator", { 47 | local_test_logger() 48 | expect_output(log_with_separator(42), "===") 49 | expect_output(log_with_separator("Boo!", level = FATAL, width = 120), width = 120) 50 | }) 51 | 52 | test_that("log failure", { 53 | skip_if_not(getRversion() >= "4.3") # error call changed 54 | 55 | local_test_logger() 56 | expect_output(log_failure("foobar"), NA) 57 | expect_output(try(log_failure(foobar), silent = TRUE), "ERROR.*foobar") 58 | expect_no_error(log_failure("foobar")) 59 | expect_snapshot(capture.output(log_failure(foobar)), error = TRUE) 60 | }) 61 | 62 | test_that("log with separator", { 63 | local_test_logger(layout = layout_glue_generator("{level} {msg}")) 64 | 65 | expect_snapshot({ 66 | log_with_separator(42) 67 | log_with_separator(42, separator = "|") 68 | }) 69 | }) 70 | 71 | 72 | test_that("single line", { 73 | local_test_logger(layout = layout_glue_generator("{level} {msg}")) 74 | 75 | expect_output(log_eval(4, INFO), sprintf("INFO %s => %s", shQuote(4), shQuote(4))) 76 | }) 77 | 78 | test_that("multi line", { 79 | local_test_logger(layout = layout_glue_generator("{level} {msg}")) 80 | 81 | expect_output(log_eval(4, INFO, multiline = TRUE), "Running expression") 82 | expect_output(log_eval(4, INFO, multiline = TRUE), "Results:") 83 | expect_output(log_eval(4, INFO, multiline = TRUE), "INFO 4") 84 | }) 85 | 86 | test_that("invisible return", { 87 | local_test_logger(layout = layout_glue_generator("{level} {msg}")) 88 | expect_output(log_eval(require(logger), INFO), sprintf( 89 | "INFO %s => %s", 90 | shQuote("require\\(logger\\)"), 91 | shQuote(TRUE) 92 | )) 93 | }) 94 | 95 | test_that("lower log level", { 96 | local_test_logger(TRACE, layout = layout_glue_generator("{level} {msg}")) 97 | expect_output(log_eval(4), sprintf("TRACE %s => %s", shQuote(4), shQuote(4))) 98 | }) 99 | 100 | test_that("knitr hook gets applied", { 101 | local_test_logger() 102 | mock_doc <- c( 103 | "```{r, include = FALSE}", 104 | "library(logger)", 105 | "log_chunk_time()", 106 | "```", 107 | "```{r}", 108 | "Sys.sleep(0.2)", 109 | "```" 110 | ) 111 | expect_output(knitr::knit(text = mock_doc, quiet = TRUE), "global timer 0.2") 112 | }) 113 | -------------------------------------------------------------------------------- /tests/testthat/test-hooks.R: -------------------------------------------------------------------------------- 1 | test_that("log_messages", { 2 | expect_snapshot({ 3 | writeLines(eval_outside("log_messages()", "message(42)")) 4 | }) 5 | }) 6 | 7 | test_that("log_warnings", { 8 | expect_snapshot({ 9 | writeLines(eval_outside("log_warnings(TRUE)", "warning(42)", "log(-1)")) 10 | }) 11 | }) 12 | 13 | test_that("log_errors", { 14 | expect_snapshot({ 15 | writeLines(eval_outside("log_errors()", "stop(42)")) 16 | writeLines(eval_outside("log_errors()", "foobar")) 17 | writeLines(eval_outside("log_errors()", 'f<-function(x) {42 * "foobar"}; f()')) 18 | writeLines(eval_outside("log_errors(traceback = TRUE)", 19 | 'source("helper.R", keep.source = TRUE)', 20 | "function_that_fails()")) 21 | }) 22 | }) 23 | 24 | test_that("shiny input initialization is detected", { 25 | obs <- eval_outside(" 26 | .globals <- shiny:::.globals 27 | .globals$appState <- new.env(parent = emptyenv()) 28 | server <- function(input, output, session) { 29 | logger::log_shiny_input_changes(input) 30 | } 31 | shiny::testServer(server, {}) 32 | ") 33 | expect_snapshot(writeLines(obs)) 34 | }) 35 | 36 | test_that("shiny input initialization is detected with different log-level", { 37 | obs <- eval_outside(" 38 | .globals <- shiny:::.globals 39 | .globals$appState <- new.env(parent = emptyenv()) 40 | server <- function(input, output, session) { 41 | logger::log_shiny_input_changes(input, level = logger::ERROR) 42 | } 43 | shiny::testServer(server, {}) 44 | ") 45 | expect_snapshot(writeLines(obs)) 46 | }) 47 | 48 | test_that("shiny input change is detected", { 49 | obs <- eval_outside(" 50 | .globals <- shiny:::.globals 51 | .globals$appState <- new.env(parent = emptyenv()) 52 | server <- function(input, output, session) { 53 | logger::log_shiny_input_changes(input) 54 | x <- shiny::reactive(input$a) 55 | } 56 | shiny::testServer(server, { 57 | session$setInputs(a = 2) 58 | }) 59 | ") 60 | expect_snapshot(writeLines(obs)) 61 | }) 62 | 63 | test_that("shiny input change is logged with different level", { 64 | obs <- eval_outside(" 65 | .globals <- shiny:::.globals 66 | .globals$appState <- new.env(parent = emptyenv()) 67 | server <- function(input, output, session) { 68 | logger::log_shiny_input_changes(input, level = logger::ERROR) 69 | x <- shiny::reactive(input$a) 70 | } 71 | shiny::testServer(server, { 72 | session$setInputs(a = 2) 73 | }) 74 | ") 75 | expect_snapshot(writeLines(obs)) 76 | }) 77 | -------------------------------------------------------------------------------- /tests/testthat/test-layouts.R: -------------------------------------------------------------------------------- 1 | test_that("blank layout", { 2 | local_test_logger(layout = layout_blank) 3 | expect_output(log_info("foobar"), "foobar") 4 | expect_equal(capture.output(log_info("foobar")), "foobar") 5 | }) 6 | 7 | test_that("colorized layout", { 8 | local_test_logger(layout = layout_glue_colors) 9 | expect_output(log_info("foobar"), "INFO") 10 | expect_output(log_info("foobar"), "foobar") 11 | expect_output(log_error("foobar"), "ERROR") 12 | expect_output(log_error("foobar"), "foobar") 13 | }) 14 | 15 | test_that("metavars", { 16 | local_test_logger(layout = layout_glue_generator("{level} {ans} {fn}")) 17 | 18 | f_info <- function() log_info() 19 | expect_output(f_info(), "INFO global f_info()") 20 | 21 | f_warn <- function() log_warn() 22 | expect_output(f_warn(), "WARN global f_warn()") 23 | 24 | local_test_logger(layout = layout_glue_generator("{time}")) 25 | time <- as.POSIXct("2025-04-04 10:31:57 CEST") 26 | expect_output(log_info("test", .timestamp = time), "2025-04-04 10:31:57") 27 | }) 28 | 29 | test_that("JSON layout", { 30 | local_test_logger(layout = layout_json(fields = "level")) 31 | 32 | out <- jsonlite::fromJSON(capture.output(log_info("foobar"))) 33 | expect_equal(out, list(level = "INFO", msg = "foobar")) 34 | }) 35 | 36 | test_that("JSON layout warns if you include msg", { 37 | expect_snapshot(layout <- layout_json(fields = "msg")) 38 | local_test_logger(layout = layout) 39 | out <- jsonlite::fromJSON(capture.output(log_info("foobar"))) 40 | expect_equal(out, list(msg = "foobar")) 41 | }) 42 | 43 | test_that("JSON parser layout", { 44 | local_test_logger(layout = layout_json_parser(fields = character())) 45 | expect_output(log_info(skip_formatter('{"x": 4}')), '{"x":4}', fixed = TRUE) 46 | }) 47 | 48 | test_that("JSON parser layout can be renamed", { 49 | local_test_logger(layout = layout_json_parser(c(LEVEL = "level"))) 50 | expect_output(log_info(skip_formatter('{"x": 4}')), '{"LEVEL":"INFO","x":4}', fixed = TRUE) 51 | }) 52 | 53 | test_that("must throw errors", { 54 | skip_if_not(getRversion() >= "4.3") # error call changed 55 | 56 | expect_snapshot(error = TRUE, { 57 | layout_simple(FOOBAR) 58 | layout_simple(42) 59 | layout_simple(msg = "foobar") 60 | }) 61 | 62 | expect_snapshot(error = TRUE, { 63 | layout_glue(FOOBAR) 64 | layout_glue(42) 65 | layout_glue(msg = "foobar") 66 | layout_glue(level = 53, msg = "foobar") 67 | }) 68 | }) 69 | 70 | test_that("logging layout", { 71 | local_test_logger(layout = layout_logging) 72 | expect_output(log_level(INFO, "foo", namespace = "bar"), "INFO:bar:foo") 73 | expect_output(log_info("foobar"), "INFO") 74 | expect_output(log_info("foo", namespace = "bar"), "foo") 75 | expect_output(log_info("foo", namespace = "bar"), "bar") 76 | expect_output(log_info("foo", namespace = "bar"), "INFO:bar:foo") 77 | }) 78 | 79 | test_that("log_info() captures local info", { 80 | local_test_logger( 81 | layout = layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}") 82 | ) 83 | f <- function() log_info("foobar") 84 | g <- function() f() 85 | 86 | expect_snapshot({ 87 | log_info("foobar") 88 | f() 89 | g() 90 | }) 91 | }) 92 | 93 | test_that("log_info() captures package info", { 94 | devtools::load_all( 95 | system.file("demo-packages/logger-tester-package", package = "logger"), 96 | quiet = TRUE 97 | ) 98 | withr::defer(devtools::unload("logger.tester")) 99 | 100 | local_test_logger(layout = layout_glue_generator("{ns} {level} {msg}")) 101 | expect_snapshot({ 102 | logger_tester_function(INFO, "x = ") 103 | logger_info_tester_function("everything = ") 104 | }) 105 | }) 106 | -------------------------------------------------------------------------------- /tests/testthat/test-logger-meta.R: -------------------------------------------------------------------------------- 1 | test_that("captures call/environment info", { 2 | f <- function(...) logger_meta_env() 3 | env <- f(x = 1) 4 | 5 | # values are computed lazily 6 | expect_type(substitute(fn, env), "language") 7 | expect_type(substitute(call, env), "language") 8 | 9 | # and give correct values 10 | expect_equal(env$fn, "f") 11 | expect_equal(env$call, "f(x = 1)") 12 | expect_equal(env$topenv, "logger") 13 | }) 14 | 15 | test_that("captures namespace info", { 16 | env <- logger_meta_env(namespace = "testthat") 17 | expect_equal(env$ns, "testthat") 18 | expect_equal(env$ans, "global") 19 | expect_equal(env$ns_pkg_version, as.character(packageVersion("testthat"))) 20 | }) 21 | 22 | test_that("captures other environmental metadata", { 23 | env <- logger_meta_env() 24 | expect_equal(env$pid, Sys.getpid()) 25 | expect_equal(env$r_version, as.character(getRversion())) 26 | 27 | sysinfo <- as.list(Sys.info()) 28 | expect_equal(env$node, sysinfo$nodename) 29 | expect_equal(env$arch, sysinfo$machine) 30 | expect_equal(env$os_name, sysinfo$sysname) 31 | expect_equal(env$os_release, sysinfo$release) 32 | expect_equal(env$os_version, sysinfo$version) 33 | expect_equal(env$user, sysinfo$user) 34 | 35 | local_test_logger(layout = layout_glue_generator("{location$path}#{location$line}: {msg}")) 36 | expect_output(test_info(), file.path(getwd(), "helper.R#3: TEST")) 37 | }) 38 | -------------------------------------------------------------------------------- /tests/testthat/test-logger.R: -------------------------------------------------------------------------------- 1 | test_that("log levels", { 2 | local_test_logger(WARN) 3 | 4 | expect_output(log_fatal("foo"), "FATAL.*foo") 5 | expect_output(log_error("foo"), "ERROR.*foo") 6 | expect_output(log_warn("foo"), "WARN.*foo") 7 | expect_output(log_success("foo"), NA) 8 | expect_output(log_info("foo"), NA) 9 | expect_output(log_debug("foo"), NA) 10 | expect_output(log_trace("foo"), NA) 11 | expect_output(log_level("ERROR", "foo"), "ERROR.*foo") 12 | expect_output(log_level(ERROR, "foo"), "ERROR.*foo") 13 | expect_output(log_level(as.loglevel(ERROR), "foo"), "ERROR.*foo") 14 | expect_output(log_level(as.loglevel("ERROR"), "foo"), "ERROR.*foo") 15 | expect_output(log_level(as.loglevel(200L), "foo"), "ERROR.*foo") 16 | expect_output(log_level("TRACE", "foo"), NA) 17 | expect_output(log_level(TRACE, "foo"), NA) 18 | expect_output(log_level(as.loglevel(TRACE), "foo"), NA) 19 | expect_output(log_level(as.loglevel("TRACE"), "foo"), NA) 20 | expect_output(log_level(as.loglevel(600L), "foo"), NA) 21 | }) 22 | 23 | test_that("log levels - OFF", { 24 | local_test_logger(OFF) 25 | expect_output(log_fatal("foo"), NA) 26 | expect_output(log_error("foo"), NA) 27 | expect_output(log_warn("foo"), NA) 28 | expect_output(log_success("foo"), NA) 29 | expect_output(log_info("foo"), NA) 30 | expect_output(log_debug("foo"), NA) 31 | expect_output(log_trace("foo"), NA) 32 | }) 33 | 34 | test_that("log thresholds", { 35 | local_test_logger(TRACE) 36 | expect_output(log_fatal("foo"), "FATAL.*foo") 37 | expect_output(log_error("foo"), "ERROR.*foo") 38 | expect_output(log_warn("foo"), "WARN.*foo") 39 | expect_output(log_success("foo"), "SUCCESS.*foo") 40 | expect_output(log_info("foo"), "INFO.*foo") 41 | expect_output(log_debug("foo"), "DEBUG.*foo") 42 | expect_output(log_trace("foo"), "TRACE.*foo") 43 | }) 44 | 45 | test_that("with log thresholds", { 46 | local_test_logger(WARN) 47 | expect_output(with_log_threshold(log_fatal("foo"), threshold = TRACE), "FATAL.*foo") 48 | expect_output(with_log_threshold(log_error("foo"), threshold = TRACE), "ERROR.*foo") 49 | expect_output(with_log_threshold(log_error("foo"), threshold = FATAL), NA) 50 | expect_output(with_log_threshold(log_error("foo"), threshold = INFO), "ERROR.*foo") 51 | expect_output(with_log_threshold(log_debug("foo"), threshold = INFO), NA) 52 | }) 53 | 54 | test_that("simple glue layout with no threshold", { 55 | local_test_logger(TRACE, layout = layout_glue_generator("{level} {msg}")) 56 | 57 | expect_equal(capture.output(log_fatal("foobar")), "FATAL foobar") 58 | expect_equal(capture.output(log_error("foobar")), "ERROR foobar") 59 | expect_equal(capture.output(log_warn("foobar")), "WARN foobar") 60 | expect_equal(capture.output(log_info("foobar")), "INFO foobar") 61 | expect_equal(capture.output(log_debug("foobar")), "DEBUG foobar") 62 | expect_equal(capture.output(log_trace("foobar")), "TRACE foobar") 63 | }) 64 | 65 | test_that("simple glue layout with threshold", { 66 | local_test_logger(INFO, layout = layout_glue_generator("{level} {msg}")) 67 | expect_equal(capture.output(log_fatal("foobar")), "FATAL foobar") 68 | expect_equal(capture.output(log_error("foobar")), "ERROR foobar") 69 | expect_equal(capture.output(log_warn("foobar")), "WARN foobar") 70 | expect_equal(capture.output(log_info("foobar")), "INFO foobar") 71 | expect_equal(capture.output(log_debug("foobar")), character()) 72 | expect_equal(capture.output(log_trace("foobar")), character()) 73 | }) 74 | 75 | test_that("namespaces", { 76 | local_test_logger(ERROR, namespace = "custom", layout = layout_glue_generator("{level} {msg}")) 77 | expect_output(log_fatal("foobar", namespace = "custom"), "FATAL foobar") 78 | expect_output(log_error("foobar", namespace = "custom"), "ERROR foobar") 79 | expect_output(log_info("foobar", namespace = "custom"), NA) 80 | expect_output(log_debug("foobar", namespace = "custom"), NA) 81 | 82 | local_test_logger(INFO, namespace = "custom", layout = layout_glue_generator("{level} {msg}")) 83 | expect_output(log_info("foobar", namespace = "custom"), "INFO foobar") 84 | expect_output(log_debug("foobar", namespace = "custom"), NA) 85 | 86 | log_threshold(TRACE, namespace = log_namespaces()) 87 | expect_output(log_debug("foobar", namespace = "custom"), "DEBUG foobar") 88 | }) 89 | 90 | test_that("simple glue layout with threshold directly calling log", { 91 | local_test_logger(layout = layout_glue_generator("{level} {msg}")) 92 | expect_equal(capture.output(log_level(FATAL, "foobar")), "FATAL foobar") 93 | expect_equal(capture.output(log_level(ERROR, "foobar")), "ERROR foobar") 94 | expect_equal(capture.output(log_level(WARN, "foobar")), "WARN foobar") 95 | expect_equal(capture.output(log_level(INFO, "foobar")), "INFO foobar") 96 | expect_equal(capture.output(log_level(DEBUG, "foobar")), character()) 97 | expect_equal(capture.output(log_level(TRACE, "foobar")), character()) 98 | }) 99 | 100 | test_that("built in variables: pid", { 101 | local_test_logger(layout = layout_glue_generator("{pid}")) 102 | expect_equal(capture.output(log_info("foobar")), as.character(Sys.getpid())) 103 | }) 104 | 105 | test_that("built in variables: fn and call", { 106 | local_test_logger(layout = layout_glue_generator("{fn} / {call}")) 107 | f <- function() log_info("foobar") 108 | expect_output(f(), "f / f()") 109 | g <- function() f() 110 | expect_output(g(), "f / f()") 111 | g <- f 112 | expect_output(g(), "g / g()") 113 | }) 114 | 115 | test_that("built in variables: namespace", { 116 | local_test_logger(layout = layout_glue_generator("{ns}")) 117 | expect_output(log_info("bar", namespace = "foo"), "foo") 118 | 119 | local_test_logger(layout = layout_glue_generator("{ans}")) 120 | expect_output(log_info("bar", namespace = "foo"), "global") 121 | }) 122 | 123 | test_that("print.level", { 124 | expect_equal(capture.output(print(INFO)), "Log level: INFO") 125 | }) 126 | 127 | test_that("config setter called from do.call", { 128 | local_test_logger() 129 | 130 | t <- withr::local_tempfile() 131 | expect_no_error(do.call(log_appender, list(appender_file(t)))) 132 | log_info(42) 133 | expect_length(readLines(t), 1) 134 | expect_no_error(do.call(log_threshold, list(ERROR))) 135 | log_info(42) 136 | expect_length(readLines(t), 1) 137 | expect_no_error(do.call(log_threshold, list(INFO))) 138 | log_info(42) 139 | expect_length(readLines(t), 2) 140 | expect_no_error(do.call(log_layout, list(formatter_paste))) 141 | log_info(42) 142 | expect_length(readLines(t), 3) 143 | }) 144 | 145 | test_that("providing log_level() args to wrappers diretly is OK", { 146 | local_test_logger(WARN) 147 | expect_silent(log_info("{Sepal.Length}", .topenv = list2env(iris))) 148 | }) 149 | 150 | test_that("setters check inputs", { 151 | expect_snapshot(error = TRUE, { 152 | log_appender(1) 153 | log_formatter(1) 154 | log_layout(1) 155 | log_threshold("x") 156 | }) 157 | }) 158 | -------------------------------------------------------------------------------- /tests/testthat/test-return.R: -------------------------------------------------------------------------------- 1 | glue_or_sprintf_result <- c( 2 | "Hi foo, did you know that 2*4=8?", 3 | "Hi bar, did you know that 2*4=8?" 4 | ) 5 | 6 | test_that("return value is formatted string", { 7 | local_test_logger(appender = appender_file(withr::local_tempfile())) 8 | 9 | log_formatter(formatter_glue) 10 | expect_equal(log_info("pi is {round(pi, 2)}")[[1]]$message, "pi is 3.14") 11 | expect_match(log_info("pi is {round(pi, 2)}")[[1]]$record, "INFO [[0-9: -]*] pi is 3.14") 12 | log_formatter(formatter_paste, index = 2) 13 | expect_equal(log_info("pi is {round(pi, 2)}")[[1]]$message, "pi is 3.14") 14 | expect_equal(log_info("pi is {round(pi, 2)}")[[2]]$message, "pi is {round(pi, 2)}") 15 | }) 16 | -------------------------------------------------------------------------------- /tests/testthat/test-try.R: -------------------------------------------------------------------------------- 1 | test_that("%except% logs errors and returns default value", { 2 | local_test_logger(layout = layout_glue_generator("{ns} / {ans} / {topenv} / {fn} / {call}\n{level} {msg}")) 3 | 4 | f <- function() { 5 | FunDoesNotExist(1:10) %except% 1 6 | } 7 | 8 | expect_snapshot(out <- f()) 9 | expect_equal(out, 1) 10 | }) 11 | 12 | test_that("%except% returns value when no error", { 13 | expect_equal(5 %except% 1, 5) 14 | }) 15 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that("fail_on_missing_package", { 2 | expect_no_error(fail_on_missing_package("logger")) 3 | 4 | expect_snapshot(error = TRUE, { 5 | fail_on_missing_package("logger", "9.9.9", call = quote(f())) 6 | fail_on_missing_package("an.R.package-that-doesNotExists", call = quote(f())) 7 | }) 8 | }) 9 | 10 | test_that("validate_log_level", { 11 | expect_equal(validate_log_level(ERROR), ERROR) 12 | expect_equal(validate_log_level("ERROR"), ERROR) 13 | expect_snapshot(validate_log_level("FOOBAR"), error = TRUE) 14 | }) 15 | 16 | test_that("catch_base_log", { 17 | expect_true(nchar(catch_base_log(ERROR, NA_character_)) == 28) 18 | expect_true(nchar(catch_base_log(INFO, NA_character_)) == 27) 19 | local_test_logger(layout = layout_blank) 20 | expect_true(nchar(catch_base_log(INFO, NA_character_)) == 0) 21 | 22 | local_test_logger(layout = layout_glue_generator(format = "{namespace}/{fn}")) 23 | expect_equal(catch_base_log(INFO, "TEMP", .topcall = NA), "global/NA") 24 | expect_equal(catch_base_log(INFO, "TEMP", .topcall = quote(f())), "global/f") 25 | }) 26 | -------------------------------------------------------------------------------- /vignettes/Intro.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to logger" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Introduction to logger} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r setup, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | 16 | library(logger) 17 | log_appender(appender_stdout) 18 | ``` 19 | 20 | If you are not only using R in the interactive console for ad-hoc data analysis, but running eg batch jobs (for ETL, reporting, modeling, forecasting etc) as well, then logging the status(changes) of your script is a must so that later on you can review / debug what have happened. 21 | 22 | For most cases, it's enough to load the package and use the functions with the `log` prefix to log important and not so important messages, for example: 23 | 24 | ```{r} 25 | library(logger) 26 | log_info("Loading data") 27 | data(mtcars) 28 | log_info("The dataset includes {nrow(mtcars)} rows") 29 | if (max(mtcars$hp) < 1000) { 30 | log_warn("Oh, no! There are no cars with more than 1K horsepower in the dataset :/") 31 | log_debug("The most powerful car is {rownames(mtcars)[which.max(mtcars$hp)]} with {max(mtcars$hp)} hp") 32 | } 33 | ``` 34 | 35 | Interestingly, the most powerful car was not being logged -- because by default the `logger` prints messages with at least the `INFO` log level: 36 | 37 | ```{r} 38 | log_threshold() 39 | ``` 40 | 41 | To change that, specify the new log level threshold, eg `TRACE` to log everything: 42 | 43 | ```{r} 44 | log_threshold(TRACE) 45 | ``` 46 | 47 | The rerunning the above code chunk: 48 | 49 | ```{r} 50 | log_info("Loading data") 51 | data(mtcars) 52 | log_info("The dataset includes {nrow(mtcars)} rows") 53 | if (max(mtcars$hp) < 1000) { 54 | log_warn("Oh, no! There are no cars with more than 1K horsepower in the dataset :/") 55 | log_debug("The most powerful car is {rownames(mtcars)[which.max(mtcars$hp)]} with {max(mtcars$hp)} hp") 56 | } 57 | ``` 58 | 59 | You may also find the `?log_eval` function useful to log both an R expression and its result in the same log record: 60 | 61 | ```{r} 62 | f <- sqrt 63 | g <- mean 64 | x <- 1:31 65 | log_eval(y <- f(g(x)), level = INFO) 66 | str(y) 67 | ``` 68 | 69 | 70 | 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: 71 | 72 | ```{r knitr-pander-setup, include = FALSE} 73 | ppo1 <- pander::panderOptions("knitr.auto.asis") 74 | ppo2 <- pander::panderOptions("table.style") 75 | pander::panderOptions("knitr.auto.asis", FALSE) 76 | pander::panderOptions("table.style", "simple") 77 | ``` 78 | 79 | ```{r} 80 | log_formatter(formatter_pander) 81 | log_info(head(iris)) 82 | ``` 83 | 84 | ```{r knitr-pander-revert, include = FALSE} 85 | pander::panderOptions("knitr.auto.asis", ppo1) 86 | pander::panderOptions("table.style", ppo2) 87 | ``` 88 | 89 | 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. 90 | 91 | ```{r cleanup, include = FALSE} 92 | logger:::namespaces_reset() 93 | ``` 94 | -------------------------------------------------------------------------------- /vignettes/anatomy.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "The Anatomy of a Log Request" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{The Anatomy of a Log Request} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | resource_files: 9 | - logger_structure.svg 10 | --- 11 | 12 | ```{r setup, include = FALSE} 13 | knitr::opts_chunk$set( 14 | collapse = TRUE, 15 | comment = "#>" 16 | ) 17 | library(logger) 18 | log_appender(appender_stdout) 19 | ``` 20 | 21 | ```{r loggerStructureImage, echo=FALSE, out.extra='style="width: 100%;" title="The structure of a logger and the flow of a log record request" alt="The structure of a logger and the flow of a log record request"'} 22 | knitr::include_graphics("logger_structure.svg") 23 | ``` 24 | 25 | To make a successful log record, `logger` requires the below components: 26 | 27 | - a **log request**, eg 28 | 29 | ```r 30 | log_error('Oops') 31 | ``` 32 | 33 | - including the log level (importance) of the record, which will be later used to decide if the log record is to be delivered or not: `ERROR` in this case 34 | - R objects to be logged: a simple string in this case, although it could be a character vector or any R object(s) that can be converted into a character vector by the `formatter` function 35 | 36 | - the **environment** and meta-information of the log request, eg actual timestamp, hostname of the computer, the name of the user running the R script, the pid of the R process, calling function and the actual call etc. 37 | 38 | 39 | ```{r} 40 | f <- function() get_logger_meta_variables(log_level = INFO) 41 | f() 42 | ``` 43 | 44 | - a **logger definition** to process the log request, including 45 | 46 | - 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 47 | 48 | ```{r} 49 | log_threshold() 50 | ERROR <= INFO 51 | log_error("Oops") 52 | ``` 53 | 54 | - `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: 55 | 56 | 57 | ```{r} 58 | formatter <- function(...) paste(..., collapse = " ", sep = " ") 59 | formatter(1:3, c("foo", "bar")) 60 | ``` 61 | 62 | - `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 63 | 64 | 65 | ```r 66 | library(jsonlite) 67 | layout <- function(level, msg) toJSON(level = level, timestamp = time, hostname = node, message = msg) 68 | layout(INFO, 'Happy Thursday!') 69 | #> {'level': 'INFO', 'timestamp': '1970-01-01 00:00:00', 'hostname': 'foobar', 'message': 'Happy Thursday!'} 70 | ``` 71 | 72 | - `appender` function, which takes fully-rendered log record(s) and delivers to somewhere, eg `stdout`, a file or a streaming service, eg 73 | 74 | ```{r} 75 | appender <- function(line) cat(line, "\n") 76 | appender("INFO [now] I am a log message") 77 | ``` 78 | 79 | Putting all these together (by explicitly setting the default config in the `global` namespace): 80 | 81 | ```{r} 82 | log_threshold(INFO) 83 | log_formatter(formatter_glue) 84 | log_layout(layout_simple) 85 | log_appender(appender_stdout) 86 | log_debug("I am a low level log message that will not be printed with a high log level threshold") 87 | log_warn("I am a higher level log message that is very likely to be printed") 88 | ``` 89 | 90 | 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. 91 | 92 | ```{r cleanup, include = FALSE} 93 | logger:::namespaces_reset() 94 | ``` 95 | -------------------------------------------------------------------------------- /vignettes/performance.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Simple Benchmarks on logger Performance" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Simple Benchmarks on logger Performance} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | Although this has not been an important feature in the early development and overall design of this `logger` implementation, but with the default `?layout_simple` and `?formatter_glue`, it seems to perform pretty well when comparing with `futile.logger` and `logging` packages: 11 | 12 | ```r 13 | library(microbenchmark) 14 | 15 | ## fl 16 | library(futile.logger) 17 | t1 <- tempfile() 18 | flog.appender(appender.file(t1)) 19 | #> NULL 20 | 21 | ## lg 22 | library(logging) 23 | t2 <- tempfile() 24 | addHandler(writeToFile, file = t2) 25 | 26 | ## lr 27 | library(logger) 28 | #> The following objects are masked from ‘package:futile.logger’: DEBUG, ERROR, FATAL, INFO, TRACE, WARN 29 | t3 <- tempfile() 30 | log_appender(appender_file(t3)) 31 | 32 | string_fl <- function() flog.info('hi') 33 | string_lg <- function() loginfo('hi') 34 | string_lr <- function() log_info('hi') 35 | dynamic_fl <- function() flog.info('hi %s', 42) 36 | dynamic_lg <- function() loginfo('hi %s', 42) 37 | dynamic_lr <- function() log_info('hi {42}') 38 | vector_fl <- function() flog.info(paste('hi', 1:5)) 39 | vector_lg <- function() loginfo(paste('hi', 1:5)) 40 | vector_lr <- function() log_info('hi {1:5}') 41 | 42 | microbenchmark( 43 | string_fl(), string_lg(), string_lr(), 44 | vector_fl(), vector_lg(), vector_lr(), 45 | dynamic_fl(), dynamic_lg(), dynamic_lr(), 46 | times = 1e3) 47 | #> Unit: microseconds 48 | #> expr min lq mean median uq max neval 49 | #> string_fl() 1533.379 1650.7915 2510.5517 1759.9345 2885.4465 20835.425 1000 50 | #> string_lg() 172.963 206.7615 315.6177 237.3150 335.3010 12738.735 1000 51 | #> string_lr() 227.981 263.4715 390.7139 301.9045 409.0400 11926.974 1000 52 | #> vector_fl() 1552.706 1661.7030 2434.0460 1766.7485 2819.5525 40892.197 1000 53 | #> vector_lg() 198.338 234.2355 330.3268 266.7695 358.2510 9969.333 1000 54 | #> vector_lr() 290.169 337.4730 592.0041 382.4335 537.5485 101946.435 1000 55 | #> dynamic_fl() 1538.985 1663.7890 2564.6668 1782.1160 2932.7555 46039.686 1000 56 | #> dynamic_lg() 188.213 226.5370 387.2470 255.1745 350.2015 60737.562 1000 57 | #> dynamic_lr() 271.478 317.3350 486.1123 360.5815 483.5830 12070.936 1000 58 | 59 | paste(t1, length(readLines(t1))) 60 | #> [1] "/tmp/Rtmp3Fp6qa/file7a8919485a36 7000" 61 | paste(t2, length(readLines(t2))) 62 | #> [1] "/tmp/Rtmp3Fp6qa/file7a89b17929f 7000" 63 | paste(t3, length(readLines(t3))) 64 | #> [1] "/tmp/Rtmp3Fp6qa/file289f24c88c41 7000" 65 | ``` 66 | 67 | So based on the above, non-comprehensive benchmark, it seems that when it comes to using the very base functionality of a logging engine, `logging` comes first, then `logger` performs with a bit of overhead due to using `glue` by default, then comes a bit slower `futile.logger`. 68 | 69 | On the other hand, there are some low-hanging fruits to improve performance, eg caching the `logger` function in the namespace, or using much faster message formatters (eg `paste0` or `sprintf` instead of `glue`) if needed -- like what `futile.logger` and `logging` are using instead of `glue`, so a quick `logger` comparison: 70 | 71 | ```r 72 | log_formatter(formatter_sprintf) 73 | string <- function() log_info('hi') 74 | dynamic <- function() log_info('hi %s', 42) 75 | vector <- function() log_info(paste('hi', 1:5)) 76 | 77 | microbenchmark(string(), vector(), dynamic(), times = 1e3) 78 | #> Unit: microseconds 79 | #> expr min lq mean median uq max neval cld 80 | #> string() 110.192 118.4850 148.5848 137.1825 152.7275 1312.903 1000 a 81 | #> vector() 129.111 136.8245 168.9274 155.5840 172.6795 3230.528 1000 b 82 | #> dynamic() 116.347 124.7620 159.1570 143.2140 160.5040 4397.640 1000 ab 83 | ``` 84 | 85 | Which suggests that `logger` is a pretty well-performing log framework. 86 | 87 | If you need even more performance with slower appenders, then asynchronous logging is your friend: passing the log messages to a reliable message queue, and a background process delivering those to the actual log destination in the background -- without blocking the main R process. This can be easily achieved in `logger` by wrapping any appender function in the `appender_async` function, such as: 88 | 89 | ```r 90 | ## demo log appender that's pretty slow 91 | appender_file_slow <- function(file) { 92 | force(file) 93 | function(lines) { 94 | Sys.sleep(1) 95 | cat(lines, sep = '\n', file = file, append = TRUE) 96 | } 97 | } 98 | 99 | ## create an async appender and start using it right away 100 | log_appender(appender_async(appender_file_slow(file = tempfile()))) 101 | 102 | async <- function() log_info('Was this slow?') 103 | microbenchmark(async(), times = 1e3) 104 | # Unit: microseconds 105 | # expr min lq mean median uq max neval 106 | # async() 298.275 315.5565 329.6235 322.219 333.371 894.579 1000 107 | ``` 108 | 109 | Please note that although this ~0.3 ms is 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. 110 | 111 | ```{r cleanup, include = FALSE} 112 | logger:::namespaces_reset() 113 | ``` 114 | -------------------------------------------------------------------------------- /vignettes/r_packages.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Logging from R Packages" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Logging from R Packages} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r pkgchecks, echo = FALSE} 11 | ## check if other logger packages are available and exit if not 12 | for (pkg in c("devtools")) { 13 | if (!requireNamespace(pkg, quietly = TRUE)) { 14 | warning(paste(pkg, "package not available, so cannot build this vignette")) 15 | knitr::knit_exit() 16 | } 17 | } 18 | ``` 19 | 20 | ```{r setup, include = FALSE} 21 | knitr::opts_chunk$set( 22 | collapse = TRUE, 23 | comment = "#>" 24 | ) 25 | ``` 26 | 27 | In this vignette I suppose that you are already familiar with [Customizing the format and destination of log records](https://daroczig.github.io/logger/articles/customize_logger.html) vignette, especially with the [Log namespaces](https://daroczig.github.io/logger/articles/customize_logger.html#log-namespaces) section. 28 | 29 | So that your R package's users can suppress (or render with custom layout) the log messages triggered by your R package, it's wise to record all those log messages in a custom namespace. By default, if you are calling the `?log_level` function family from an R package after importing from the `logger` package, then `logger` will try to auto-guess the calling R package name and use that as the default namespace, see eg: 30 | 31 | ```{r} 32 | library(logger) 33 | devtools::load_all(system.file("demo-packages/logger-tester-package", package = "logger")) 34 | logger_tester_function(INFO, "hi from tester package") 35 | ``` 36 | 37 | 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`. 38 | 39 | 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. 40 | 41 | ```{r cleanup, include = FALSE} 42 | logger:::namespaces_reset() 43 | ``` 44 | -------------------------------------------------------------------------------- /vignettes/write_custom_extensions.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Writing Custom Logger Extensions" 3 | vignette: > 4 | %\VignetteIndexEntry{Writing Custom Logger Extensions} 5 | %\VignetteEngine{knitr::rmarkdown} 6 | %\VignetteEncoding{UTF-8} 7 | --- 8 | 9 | ```{r setup, include = FALSE} 10 | knitr::opts_chunk$set( 11 | collapse = TRUE, 12 | comment = "#>" 13 | ) 14 | ``` 15 | 16 | In this vignette I suppose that you are already familiar with [Customizing the format and destination of log records](https://daroczig.github.io/logger/articles/customize_logger.html) vignette. 17 | 18 | ## Custom log message formatter functions 19 | 20 | The log message formatter function should be able to take any number of R objects and convert those into a character vector that is safe to pass to the layout function. 21 | 22 | This transformer function can be as simple as calling `paste`, `glue` or `sprintf` or something complex as well, eg looking up user attributes of a user id mentioned in a log record etc. 23 | 24 | When writing a custom formatter function, it should also accept the original logging function call as `.logcall`, the parent call as `.topcall` and the log request's environment as `.topenv`, which can be used to find the relevant variables in your formatter. 25 | 26 | If you are writing such a function or a generator function returning a log message formatter function, please keep the actual call resulting in the formatter function (eg `match.call()` in the generator function or the quoted function call) recorded in the `generator` attribute of the function so that `?log_formatter` can pretty-print that instead of the unnamed function body. See the [`formatters.R`](https://github.com/daroczig/logger/blob/master/R/formatters.R) file for more examples. 27 | 28 | ## Custom log layout rendering functions 29 | 30 | The layout functions return the log record and take at least two arguments: 31 | 32 | - the log level and 33 | - a message already formatted as a string by a log message formatter function 34 | - the namespace (as `namespace`), calling function (as `.topcall`) and its environment (as `.topenv`) of the log request, and the actual log call (as `.logcall`) automatically collected by `?log_level` 35 | 36 | Such layout rendering function can be as simple as the default `?layout_simple`: 37 | 38 | ```r 39 | layout_simple <- function(level, msg, ...) { 40 | paste0(attr(level, 'level'), ' [', format(Sys.time(), "%Y-%m-%d %H:%M:%S"), '] ', msg) 41 | } 42 | ``` 43 | 44 | Or much more complex, eg looking up the hostname of the machine, public IP address etc and logging all these automatically with the message of the log request. 45 | 46 | Your easiest option to set up a custom layout is calling `?layout_glue_generator` that comes with a nice API being able to access a bunch of meta-information on the log request via `?get_logger_meta_variables`. For such example, see the [Customizing the format and destination of log records](https://daroczig.github.io/logger/articles/customize_logger.html) vignette. 47 | 48 | If you are writing such a function or a generator function returning a log message formatter function, please keep the actual call resulting in the formatter function (eg `match.call()` in the generator function or the quoted function call) recorded in the `generator` attribute of the function so that `?log_layout` can pretty-print that instead of the unnamed function body. See the [`layouts.R`](https://github.com/daroczig/logger/blob/master/R/layouts.R) file for more examples. 49 | 50 | ## Custom log record appenders 51 | 52 | The appender functions take log records and delivers those to the desired destination. This can be as simple as writing to the console (`?appender_console`) or to a local file (`?appender_file`), or delivering the log record via an API request to a remote service, streaming somewhere or sending a Slack message (`?appender_slack`). 53 | 54 | If you are writing such a function or a generator function returning a log message formatter function, please keep the actual call resulting in the formatter function (eg `match.call()` in the generator function or the quoted function call) recorded in the `generator` attribute of the function so that `?log_appender` can pretty-print that instead of the unnamed function body. See the [`appenders.R`](https://github.com/daroczig/logger/blob/master/R/appenders.R) file for more examples. 55 | 56 | An example for a custom appender delivering log messages to a database table: 57 | 58 | ```r 59 | ## the dbr package provides and easy and secure way of connecting to databased from R 60 | ## although if you want to minimize the dependencies, feel free to stick with DBI etc. 61 | library(dbr) 62 | ## init a persistent connection to the database using a yaml config in the background thanks to dbr 63 | ## NOTE that this is optional and a temporarily connection could be also used 64 | ## for higher reliability but lower throughput 65 | con <- db_connect('mydb') 66 | ## define custom function writing the log message to a table 67 | log_appender(function(lines) { 68 | db_append( 69 | df = data.frame(timestamp = Sys.time(), message = lines), 70 | table = 'logs', db = con) 71 | }) 72 | ``` 73 | 74 | ```{r cleanup, include = FALSE} 75 | logger:::namespaces_reset() 76 | ``` 77 | --------------------------------------------------------------------------------