diff --git a/DESCRIPTION b/DESCRIPTION index a28e0d7..76b00b7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,19 +1,12 @@ Package: loggit -Title: Modern Logging for the R Ecosystem +Title: Easy-to-use, dependencyless Logger for R Description: - An effortless 'ndjson' (newline-delimited 'JSON') logger, with two primary - log-writing interfaces. It provides a set of wrappings for base R's - message(), warning(), and stop() functions that maintain identical - functionality, but also log the handler message to an 'ndjson' log file. - 'loggit' also exports its internal 'loggit()' function for powerful and - configurable custom logging. No change in existing code is necessary to use - this package, and should only require additions to fully leverage the power - of the logging system. 'loggit' also provides a log reader for reading an - 'ndjson' log file into a data frame, log rotation, and live echo of the - 'ndjson' log messages to terminal 'stdout' for log capture by external - systems (like containers). 'loggit' is ideal for Shiny apps, data pipelines, - modeling work flows, and more. Please see the vignettes for detailed example - use cases. + An easy-to-use 'ndjson' (newline-delimited 'JSON') logger. + It provides a set of wrappings for base R's message(), warning(), and + stop() functions that maintain identical functionality, but also log + the handler message to an 'ndjson' log file. + No change in existing code is necessary to use this package, and should + only require additions to fully leverage the power of the logging system. Version: 2.1.1.9999 Authors@R: person(given = "Ryan", diff --git a/README.Rmd b/README.Rmd index b34977e..745e7df 100644 --- a/README.Rmd +++ b/README.Rmd @@ -14,120 +14,94 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) +options(width = 150) ``` -Modern Logging for the R Ecosystem +Easy-to-use, dependencyless Logger for R ================================== Ryan Price [![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/loggit)](https://cran.r-project.org/package=loggit) -[![Monthly downloads](https://cranlogs.r-pkg.org/badges/loggit)](https://cran.r-project.org/package=loggit) +[![Downloads](https://cranlogs.r-pkg.org/badges/grand-total/loggit)](https://cran.r-project.org/package=loggit) [![R-CMD-check](https://github.com/MEO265/loggit_private/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/MEO265/loggit_private/actions/workflows/R-CMD-check.yaml) [![codecov](https://codecov.io/gh/MEO265/loggit_private/graph/badge.svg?token=DGPQGD4DUH)](https://codecov.io/gh/MEO265/loggit_private) ------------------------------------------------------------------------ -`loggit` is an [`ndJSON`](https://github.com/ndjson/ndjson-spec) logging library -for R software. It is blazingly fast when writing logs, and has _zero_ external -dependencies. `loggit` can be as simple and unobstrusive as you'd like, or as -involved as your application needs it to be. +`loggit` is an easy-to-use [`ndJSON`](https://github.com/ndjson/ndjson-spec) logging library for R software, +with _zero_ external dependencies. -Please see below for some quick examples, and [read the -vignettes](https://cran.r-project.org/web/packages/loggit/vignettes/) for the -Getting Started guide, as well as some other use case examples. +Please see below for some quick examples, and read the [vignettes](https://cran.r-project.org/web/packages/loggit/vignettes/) for the +Getting Started guide. Why use `loggit`? ----------------- -There are indeed several logging packages available for R. `loggit`, however, -takes a more modern approach approach to logging in R: +`loggit` takes a modern approach to logging in R: -- Opting to use the JSON format, which is parsable by most modern software -- Designed with log streams in mind -- Unobtrusive, yet highly flexible -- Convenient ability to log data, then analyze that log data on the same host. +- Opting to use the JSON format +- Highly flexible log streams +- Enables log data analysis on the same host +- _Zero_ external dependencies -Additionally, the boilerplate to get going with `loggit` is minimal at worst, -only requiring you to point to the log file. If deploying your R code in a -container ecosystem, you don't even need to do that, since `loggit` will -echo its formatted logs to `stdout`. No need to write custom formatters, -handlers, levels, etc. -- ***just f&ck#n' loggit!*** +Additionally, the boilerplate to get going with `loggit` is minimal at worst. +No need to write custom formatters, handlers, levels, etc. -- ***just loggit!*** -Quick Examples +Usage -------------- -The quickest way to get up & running with `loggit` is to... do nothing special. -`loggit`'s simplest functionality does its best to stay out of your way. You'll -probably want to point it to a log file, though; otherwise, logs will print to -the console, but land in a tempfile. +The quickest way to get up & running with `loggit` is to do nearly nothing. -```{r handlers} -library(loggit) -# set_logfile("./loggit.log") - -message("This is a message") -warning("This is a warning") -# stop("This actually throws a critical error, so I'm not actually going to run it here :)")) -#> {"timestamp": "2020-05-31T20:59:33-0500", "log_lvl": "ERROR", "log_msg": "This actually throws a critical error, so I'm not actually going to run it here :)"} +`loggit` provides a set of wrappings for base R's `message()`, `warning()`, and +`stop()` functions that maintain identical functionality, making it sufficient to +import the `loggit` namespace, for example by using `library("loggit")`, or by +prefixing `loggit::` at the desired locations. +```{r, error = TRUE} +loggit::message("This is a message") +loggit::warning("This is a warning") +loggit::stop("This is an error") ``` -You can suppress each part of the console output separately (both the `loggit` -ndJSON and the regular R output) but the default is to post both. Only the -ndJSON is written to the log file. +You can suppress the additional console output by using `echo = FALSE` and +you won't notice any difference to the base functions (except that the log +will be filled in the background). + +```{r, error = TRUE} +loggit::message("This is another message", echo = FALSE) +loggit::warning("This is another warning", echo = FALSE) +loggit::stop("This is another error", echo = FALSE) +``` -You can also use the `loggit()` function directly to compose much more custom -logs, including ***entirely custom fields*** (and prevent throwing actual status -codes until you wish to handle them). `loggit` doesn't require that you set -custom logger objects or anything like that: just throw whatever you want at it, -and it'll become a structured log. +You can also use `loggit()` directly to compose much more custom +logs, including ***entirely custom fields*** (and prevent throwing actual conditions). +`loggit` doesn't require that you set custom logger objects or anything like that: +just throw whatever you want at it, and it'll become a structured log. ```{r loggit} -loggit("ERROR", "This will log an error - but not actually throw one yet", rows = nrow(iris), anything_else = "you want to include") +loggit::loggit("ERROR", "This will log an error", anything_else = "you want to include") # Read log file into data frame to implement logic based on entries -logdata <- read_logs() -print(logdata) -if (any(logdata$log_lvl == "ERROR")) { - print("Errors detected somewhere in your code!") # but you can throw a stop() here, too, for example -} +loggit::read_logs() ``` -Again, [check out the -vignettes](https://cran.r-project.org/web/packages/loggit/vignettes/) for more -details! +Check out the [vignettes](https://cran.r-project.org/web/packages/loggit/vignettes/) for more details. Installation ------------ You can install the latest CRAN release of `loggit` via -`install.packages("loggit")`. -Or, to get the latest development version from GitHub -- + install.packages("loggit") -Via [devtools](https://github.com/hadley/devtools): +or, get the latest development version from GitHub via devtools::install_github("ryapric/loggit") -Or, clone & build from source: - - cd /path/to/your/repos - git clone https://github.com/ryapric/loggit.git loggit - make install - -To use the most recent development version of `loggit` in your own package, you -can include it in your `Remotes:` field in your DESCRIPTION file: - - Remotes: github::ryapric/loggit - -Note that packages being submitted to CRAN *cannot* have a `Remotes` field. -Refer -[here](https://cran.r-project.org/web/packages/devtools/vignettes/dependencies.html) -for more info. - -License -------- +Acknowledgments +----------- +This package is based on the [eponymous package by Ryan Price](https://github.com/ryapric/loggit), specifically version 2.1.1. -MIT +Due to technical reasons, this repository is not a GitHub fork of [Ryan's repository](https://github.com/ryapric/loggit). diff --git a/README.md b/README.md index 5fb33e5..07de543 100644 --- a/README.md +++ b/README.md @@ -1,140 +1,116 @@ -# Modern Logging for the R Ecosystem +# Easy-to-use, dependencyless Logger for R Ryan Price [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/loggit)](https://cran.r-project.org/package=loggit) -[![Monthly -downloads](https://cranlogs.r-pkg.org/badges/loggit)](https://cran.r-project.org/package=loggit) +[![Downloads](https://cranlogs.r-pkg.org/badges/grand-total/loggit)](https://cran.r-project.org/package=loggit) [![R-CMD-check](https://github.com/MEO265/loggit_private/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/MEO265/loggit_private/actions/workflows/R-CMD-check.yaml) [![codecov](https://codecov.io/gh/MEO265/loggit_private/graph/badge.svg?token=DGPQGD4DUH)](https://codecov.io/gh/MEO265/loggit_private) ------------------------------------------------------------------------ -`loggit` is an [`ndJSON`](https://github.com/ndjson/ndjson-spec) logging -library for R software. It is blazingly fast when writing logs, and has -*zero* external dependencies. `loggit` can be as simple and unobstrusive -as you’d like, or as involved as your application needs it to be. +`loggit` is an easy-to-use +[`ndJSON`](https://github.com/ndjson/ndjson-spec) logging library for R +software, with *zero* external dependencies. -Please see below for some quick examples, and [read the -vignettes](https://cran.r-project.org/web/packages/loggit/vignettes/) -for the Getting Started guide, as well as some other use case examples. +Please see below for some quick examples, and read the +[vignettes](https://cran.r-project.org/web/packages/loggit/vignettes/) +for the Getting Started guide. ## Why use `loggit`? -There are indeed several logging packages available for R. `loggit`, -however, takes a more modern approach approach to logging in R: +`loggit` takes a modern approach to logging in R: -- Opting to use the JSON format, which is parsable by most modern - software -- Designed with log streams in mind -- Unobtrusive, yet highly flexible -- Convenient ability to log data, then analyze that log data on the same - host. +- Opting to use the JSON format +- Highly flexible log streams +- Enables log data analysis on the same host +- *Zero* external dependencies Additionally, the boilerplate to get going with `loggit` is minimal at -worst, only requiring you to point to the log file. If deploying your R -code in a container ecosystem, you don’t even need to do that, since -`loggit` will echo its formatted logs to `stdout`. No need to write -custom formatters, handlers, levels, etc. – ***just f&ck#n’ loggit!*** +worst. No need to write custom formatters, handlers, levels, etc. – +***just loggit!*** -## Quick Examples +## Usage -The quickest way to get up & running with `loggit` is to… do nothing -special. `loggit`’s simplest functionality does its best to stay out of -your way. You’ll probably want to point it to a log file, though; -otherwise, logs will print to the console, but land in a tempfile. +The quickest way to get up & running with `loggit` is to do nearly +nothing. + +`loggit` provides a set of wrappings for base R’s `message()`, +`warning()`, and `stop()` functions that maintain identical +functionality, apart from an additional output to `stdout` and writing to a log file. +Making it sufficient to import the `loggit` namespace, +for example by using `library("loggit")`, or by prefixing `loggit::` at +the desired locations. ``` r -library(loggit) -#> Warning: Paket 'loggit' wurde unter R Version 4.1.3 erstellt -#> -#> Attache Paket: 'loggit' -#> Die folgenden Objekte sind maskiert von 'package:base': -#> -#> message, stop, warning -# set_logfile("./loggit.log") - -message("This is a message") -#> {"timestamp": "2023-12-19T10:46:32+0100", "log_lvl": "INFO", "log_msg": "This is a message"} +loggit::message("This is a message") +#> {"timestamp": "2024-01-07T20:40:16+0100", "log_lvl": "INFO", "log_msg": "This is a message"} #> This is a message -warning("This is a warning") -#> {"timestamp": "2023-12-19T10:46:32+0100", "log_lvl": "WARN", "log_msg": "This is a warning"} -#> Warning in warning("This is a warning"): This is a warning -# stop("This actually throws a critical error, so I'm not actually going to run it here :)")) -#> {"timestamp": "2020-05-31T20:59:33-0500", "log_lvl": "ERROR", "log_msg": "This actually throws a critical error, so I'm not actually going to run it here :)"} +loggit::warning("This is a warning") +#> {"timestamp": "2024-01-07T20:40:16+0100", "log_lvl": "WARN", "log_msg": "This is a warning"} +#> Warning in loggit::warning("This is a warning"): This is a warning +loggit::stop("This is an error") +#> {"timestamp": "2024-01-07T20:40:16+0100", "log_lvl": "ERROR", "log_msg": "This is an error"} +#> Error in loggit::stop("This is an error"): This is an error ``` -You can suppress each part of the console output separately (both the -`loggit` ndJSON and the regular R output) but the default is to post -both. Only the ndJSON is written to the log file. +The additional output to `stdout` allows easy logging, especially in container environments. +You can suppress the additional console output by using `echo = FALSE` +and you won’t notice any difference to the base functions (except that +the log will be filled in the background). + +``` r +loggit::message("This is another message", echo = FALSE) +#> This is another message +loggit::warning("This is another warning", echo = FALSE) +#> Warning in loggit::warning("This is another warning", echo = FALSE): This is another warning +loggit::stop("This is another error", echo = FALSE) +#> Error in loggit::stop("This is another error", echo = FALSE): This is another error +``` -You can also use the `loggit()` function directly to compose much more -custom logs, including ***entirely custom fields*** (and prevent -throwing actual status codes until you wish to handle them). `loggit` -doesn’t require that you set custom logger objects or anything like -that: just throw whatever you want at it, and it’ll become a structured -log. +You can also use `loggit()` directly to compose much more custom logs, +including ***entirely custom fields*** (and prevent throwing actual +conditions). `loggit` doesn’t require that you set custom logger objects +or anything like that: just throw whatever you want at it, and it’ll +become a structured log. ``` r -loggit("ERROR", "This will log an error - but not actually throw one yet", rows = nrow(iris), anything_else = "you want to include") -#> {"timestamp": "2023-12-19T10:46:32+0100", "log_lvl": "ERROR", "log_msg": "This will log an error - but not actually throw one yet", "rows": "150", "anything_else": "you want to include"} +loggit::loggit("ERROR", "This will log an error", anything_else = "you want to include") +#> {"timestamp": "2024-01-07T20:40:16+0100", "log_lvl": "ERROR", "log_msg": "This will log an error", "anything_else": "you want to include"} # Read log file into data frame to implement logic based on entries -logdata <- read_logs() -print(logdata) -#> timestamp log_lvl -#> 1 2023-12-19T10:46:32+0100 INFO -#> 2 2023-12-19T10:46:32+0100 WARN -#> 3 2023-12-19T10:46:32+0100 ERROR -#> log_msg rows -#> 1 This is a message -#> 2 This is a warning -#> 3 This will log an error - but not actually throw one yet 150 -#> anything_else -#> 1 -#> 2 -#> 3 you want to include -if (any(logdata$log_lvl == "ERROR")) { - print("Errors detected somewhere in your code!") # but you can throw a stop() here, too, for example -} -#> [1] "Errors detected somewhere in your code!" +loggit::read_logs() +#> timestamp log_lvl log_msg anything_else +#> 1 2024-01-07T20:40:16+0100 INFO This is a message +#> 2 2024-01-07T20:40:16+0100 WARN This is a warning +#> 3 2024-01-07T20:40:16+0100 ERROR This is an error +#> 4 2024-01-07T20:40:16+0100 INFO This is another message +#> 5 2024-01-07T20:40:16+0100 WARN This is another warning +#> 6 2024-01-07T20:40:16+0100 ERROR This is another error +#> 7 2024-01-07T20:40:16+0100 ERROR This will log an error you want to include ``` -Again, [check out the -vignettes](https://cran.r-project.org/web/packages/loggit/vignettes/) -for more details! +Check out the +[vignettes](https://cran.r-project.org/web/packages/loggit/vignettes/) +for more details. ## Installation You can install the latest CRAN release of `loggit` via -`install.packages("loggit")`. -Or, to get the latest development version from GitHub – + install.packages("loggit") -Via [devtools](https://github.com/hadley/devtools): +or, get the latest development version from GitHub via devtools::install_github("ryapric/loggit") -Or, clone & build from source: - - cd /path/to/your/repos - git clone https://github.com/ryapric/loggit.git loggit - make install - -To use the most recent development version of `loggit` in your own -package, you can include it in your `Remotes:` field in your DESCRIPTION -file: - - Remotes: github::ryapric/loggit - -Note that packages being submitted to CRAN *cannot* have a `Remotes` -field. Refer -[here](https://cran.r-project.org/web/packages/devtools/vignettes/dependencies.html) -for more info. +## Acknowledgments -## License +This package is based on the [eponymous package by Ryan +Price](https://github.com/ryapric/loggit), specifically version 2.1.1. -MIT +Due to technical reasons, this repository is not a GitHub fork of +[Ryan’s repository](https://github.com/ryapric/loggit). diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd index 2f8b164..646ab77 100644 --- a/vignettes/getting-started.Rmd +++ b/vignettes/getting-started.Rmd @@ -12,6 +12,7 @@ knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) +options(width = 200) ``` ```{r setup, include = FALSE} @@ -28,8 +29,8 @@ is halted because of an error. However, R itself provides nothing to record this diagnostic post-hoc; useRs are left with what is printed to the console as their only means of analyzing the what-went-wrong of their code. There are some slightly hacky ways of capturing this console output, such as `sink`ing to a -text file, repetitively `cat`ing identical exception messages that are passed to -existing handler calls, etc. But there are two main issues with these +text file, repetitively `cat`ing identical exception messages that are passed +to existing handler calls, etc. But there are two main issues with these approaches: 1. The console output is not at all easy to parse, so that a user can quickly @@ -39,52 +40,10 @@ approaches: have to ensure consistency in that output across all their work, and there is still the issue of parsing that text file into a familiar, usable format -Enter: [JSON](https://www.json.org/) - -For those unaware: JSON is a lightweight, portable (standardized) data format -that is easy to read and write by both humans and machines. An excerpt from the -introduction of the JSON link above: - ->JSON (JavaScript Object Notation) is a lightweight data-interchange format. It -is easy for humans to read and write. It is easy for machines to parse and -generate. It is based on a subset of the JavaScript Programming Language, -Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is -completely language independent but uses conventions that are familiar to -programmers of the C-family of languages, including C, C++, C\#, Java, -JavaScript, Perl, Python, and many others. These properties make JSON an ideal -data-interchange language. - -Basically, you can think of JSON objects like you would think of `list`s in R: a -set of named key-value pairs. Since R `list`s are subsets of the `data.frame` -class, logs written by `loggit` are easily retrievable as data frames -- this -means you can analyze your log data, *right from your R code!* - -What `loggit` does a bit differently is write logs as *newline-delimited JSON* -(`ndsjon`). Instead of a JSON file that looks like this: - -``` -[ - { - "key1": "value1" - }, - { - "key2": "value2" - } -] -``` - -`loggit`'s logs containing the same data will instead put each object on its own -line: - -``` -{"key1": "value1"} -{"key2": "value2"} -``` - -This makes the log entries themselves exhibit very fast disk write speeds, while -still being machine-parsable, human-readable, and ideal for log stream -collection systems (like the `stdout` of your terminal, or a container in Docker -or Kubernetes). +`loggit` addresses these issues by writing logs as *newline-delimited +[JSON](https://www.json.org/)* (`ndjson`). This format exhibits very fast +disk write speeds, while still being machine-parsable, human-readable, +and ideal for log stream collection systems. How to Use `loggit` ------------------- @@ -97,10 +56,10 @@ library(loggit) set_logfile("/path/to/my/log/directory/loggit.log") # loggit enforces no specific file extension ``` -```{r handlers} +```{r handlers, error = TRUE} message("This is a message") warning("This is a warning") -# stop("This is a critical error, so I'm not actually going to run it in this vignette") +stop("This is a critical error") ``` You can see that the handlers will pring both the `loggit`-generated log entry, @@ -121,7 +80,7 @@ function is also exported for use by the developer: ```{r loggit_func} loggit("INFO", "This is also a message") loggit("WARN", "This is also a warning") -loggit("ERROR", "This is an error, but it won't stop your code from running like `stop()` does") +loggit("ERROR", "This is an error, but it won't stop") ``` *"But why wouldn't I just use the handlers instead?"* @@ -135,9 +94,7 @@ loggit( "This is a message", but_maybe = "you want more fields?", sure = "why not?", - like = 2, - or = 10, - what = "ever" + like = 2 ) ``` @@ -177,12 +134,12 @@ The other helpful utilities are as follows: Things to keep in mind ---------------------- -- `loggit` will default to writing to an R temporary directory. As per CRAN - policies, ***a package cannot write*** to a user's "home filespace" without - approval. Therefore, you need to set the log file before any logs are written - to disk, using `set_logfile(logfile)` (I recommend in your working directory, - and naming it "loggit.log"). If you are using loggit in your own package, you - can wrap this in a call to `.onLoad()`, so that logging is set on package - load. If not, then make the set call as soon as possible (e.g. at the top of - your script(s), right after your calls to `library()`); otherwise, no logs - will be written to persistent storage! +`loggit` will default to writing to an R temporary directory. As per CRAN +policies, ***a package cannot write*** to a user's "home filespace" without +approval. Therefore, you need to set the log file before any logs are written +to disk, using `set_logfile(logfile)` (I recommend in your working directory, +and naming it "loggit.log"). If you are using loggit in your own package, you +can wrap this in a call to `.onLoad()`, so that logging is set on package +load. If not, then make the set call as soon as possible (e.g. at the top of +your script(s), right after your calls to `library()`); otherwise, no logs +will be written to persistent storage!