|
| 1 | + |
| 2 | +<!-- README.md is generated from README.Rmd. Please edit that file --> |
| 3 | + |
| 4 | +# rotor |
| 5 | + |
| 6 | +<!-- badges: start --> |
| 7 | + |
| 8 | +[](https://travis-ci.org/s-fleck/rotor) |
| 10 | +[](https://www.tidyverse.org/lifecycle/#maturing) |
| 12 | +[](https://codecov.io/gh/s-fleck/rotor?branch=master) |
| 14 | +[](https://cran.r-project.org/package=rotor) |
| 16 | +<!-- badges: end --> |
| 17 | + |
| 18 | +**rotor** provides a cross platform R reimagination of |
| 19 | +[logrotate](https://linux.die.net/man/8/logrotate). It is a companion |
| 20 | +package to the logging package [lgr](https://github.com/s-fleck/lgr). In |
| 21 | +contrast to logrotate, rotor relies solely on information encoded in a |
| 22 | +suffixes of file names for conditionally creating backups (i.e. a |
| 23 | +timestamp or index). It therefore also works with backups created by |
| 24 | +other tools, as long as the filename has a format that rotor can |
| 25 | +understand. |
| 26 | + |
| 27 | +`rotate()`, `rotate_date()`, and `rotate_time()` move a file and insert |
| 28 | +a suffix (either an integer or a timestamp) into the filename. In |
| 29 | +addition, they create an empty file in place of the original one. This |
| 30 | +is useful for log rotation. `backup()`, `backup_date()` and |
| 31 | +`backup_time()` do the same but keep the original file. |
| 32 | + |
| 33 | +rotor also includes utility functions for finding and examining the |
| 34 | +backups of a file: `list_backups()`, `backup_info()`, `n_backups`, |
| 35 | +`newest_backup()`, `oldest_backup()`. See the [function |
| 36 | +reference](https://s-fleck.github.io/rotor/reference/index.html) for |
| 37 | +details. |
| 38 | + |
| 39 | +## Installation |
| 40 | + |
| 41 | +You can install the released version of rotor from |
| 42 | +[CRAN](https://CRAN.R-project.org) with: |
| 43 | + |
| 44 | +``` r |
| 45 | +install.packages("rotor") |
| 46 | +``` |
| 47 | + |
| 48 | +And the development version from [GitHub](https://github.com/) with: |
| 49 | + |
| 50 | +``` r |
| 51 | +# install.packages("remotes") |
| 52 | +remotes::install_github("s-fleck/rotor") |
| 53 | +``` |
| 54 | + |
| 55 | +## Example |
| 56 | + |
| 57 | +First we create a temporary directory for the files created by the code |
| 58 | +examples |
| 59 | + |
| 60 | +``` r |
| 61 | +library(rotor) |
| 62 | + |
| 63 | +# create a directory |
| 64 | +td <- file.path(tempdir(), "rotor") |
| 65 | +dir.create(td, recursive = TRUE) |
| 66 | + |
| 67 | +# create an example logfile |
| 68 | +tf <- file.path(td, "mylogfile.log") |
| 69 | +writeLines("An important message", tf) |
| 70 | +``` |
| 71 | + |
| 72 | +### Indexed backups |
| 73 | + |
| 74 | +`backup()` makes a copy of a file and inserts an index between the |
| 75 | +filename and the file extension. The file with the index `1` is always |
| 76 | +the most recently made backup. |
| 77 | + |
| 78 | +``` r |
| 79 | +backup(tf) |
| 80 | + |
| 81 | +# backup and rotate also support compression |
| 82 | +backup(tf, compression = TRUE) |
| 83 | + |
| 84 | +# display backups of a file |
| 85 | +list_backups(tf) |
| 86 | +#> [1] "/tmp/RtmpTB9vY3/rotor/mylogfile.1.log.zip" |
| 87 | +#> [2] "/tmp/RtmpTB9vY3/rotor/mylogfile.2.log" |
| 88 | +``` |
| 89 | + |
| 90 | +`rotate()` also backs up a file, but replaces the original file with an |
| 91 | +empty one. |
| 92 | + |
| 93 | +``` r |
| 94 | +rotate(tf) |
| 95 | +list_backups(tf) |
| 96 | +#> [1] "/tmp/RtmpTB9vY3/rotor/mylogfile.1.log" |
| 97 | +#> [2] "/tmp/RtmpTB9vY3/rotor/mylogfile.2.log.zip" |
| 98 | +#> [3] "/tmp/RtmpTB9vY3/rotor/mylogfile.3.log" |
| 99 | + |
| 100 | +# the original file is now empty |
| 101 | +readLines(tf) |
| 102 | +#> character(0) |
| 103 | + |
| 104 | +# its content was moved to the first backup |
| 105 | +readLines(list_backups(tf)[[1]]) |
| 106 | +#> [1] "An important message" |
| 107 | + |
| 108 | +# we can now safely write to the original file |
| 109 | +writeLines("another important message", tf) |
| 110 | +``` |
| 111 | + |
| 112 | +The `max_backups` parameter limits the maximum number of backups rotor |
| 113 | +will keep of a file. Notice how the zipped backup we created above moves |
| 114 | +to index 4 as we create two new backups. |
| 115 | + |
| 116 | +``` r |
| 117 | +backup(tf, max_backups = 4) |
| 118 | +backup(tf, max_backups = 4) |
| 119 | + |
| 120 | +list_backups(tf) |
| 121 | +#> [1] "/tmp/RtmpTB9vY3/rotor/mylogfile.1.log" |
| 122 | +#> [2] "/tmp/RtmpTB9vY3/rotor/mylogfile.2.log" |
| 123 | +#> [3] "/tmp/RtmpTB9vY3/rotor/mylogfile.3.log" |
| 124 | +#> [4] "/tmp/RtmpTB9vY3/rotor/mylogfile.4.log.zip" |
| 125 | +``` |
| 126 | + |
| 127 | +We can also use `prune_backups()` to delete old backups. Other than |
| 128 | +ensuring that no new backups is created, it works identically to using |
| 129 | +`backup()` with the `max_backups` parameter. By setting it to `0`, we |
| 130 | +delete all backups. |
| 131 | + |
| 132 | +``` r |
| 133 | +prune_backups(tf, max_backups = 0) |
| 134 | +``` |
| 135 | + |
| 136 | +## Timestamped backups |
| 137 | + |
| 138 | +**rotor** can also create timestamped backups. `backup_date()` creates |
| 139 | +uses a Date (`yyyy-mm-dd`) timestamp, `backup_time()` uses a full |
| 140 | +datetime-stamp by default (`yyyy-mm-dd--hh-mm-ss`). The format of the |
| 141 | +timestamp can be modified with a subset of the formatting tokens |
| 142 | +understood by `strftime()` (within certain restrictions). Backups |
| 143 | +created with both functions are compatible with each other (but not with |
| 144 | +those created with |
| 145 | +`backup_index()`). |
| 146 | + |
| 147 | +``` r |
| 148 | +# be default backup_date() only makes a backup if the last backups is younger |
| 149 | +# than 1 day, so we set `age` to -1 for this example |
| 150 | +backup_date(tf, age = -1) |
| 151 | +backup_date(tf, format = "%Y-%m", age = -1) |
| 152 | +backup_time(tf) |
| 153 | +backup_time(tf, format = "%Y-%m-%d_%H-%M-%S") # Python logging |
| 154 | +backup_time(tf, format = "%Y%m%dT%H%M%S") # ISO 8601 compatible |
| 155 | + |
| 156 | +backup_info(tf) |
| 157 | +#> path name |
| 158 | +#> 1 /tmp/RtmpTB9vY3/rotor/mylogfile.2020-01-02_10-05-52.log mylogfile |
| 159 | +#> 2 /tmp/RtmpTB9vY3/rotor/mylogfile.2020-01-02--10-05-52.log mylogfile |
| 160 | +#> 5 /tmp/RtmpTB9vY3/rotor/mylogfile.20200102T100552.log mylogfile |
| 161 | +#> 3 /tmp/RtmpTB9vY3/rotor/mylogfile.2020-01-02.log mylogfile |
| 162 | +#> 4 /tmp/RtmpTB9vY3/rotor/mylogfile.2020-01.log mylogfile |
| 163 | +#> sfx ext size isdir mode mtime |
| 164 | +#> 1 2020-01-02_10-05-52 log 26 FALSE 664 2020-01-02 10:05:52 |
| 165 | +#> 2 2020-01-02--10-05-52 log 26 FALSE 664 2020-01-02 10:05:52 |
| 166 | +#> 5 20200102T100552 log 26 FALSE 664 2020-01-02 10:05:52 |
| 167 | +#> 3 2020-01-02 log 26 FALSE 664 2020-01-02 10:05:52 |
| 168 | +#> 4 2020-01 log 26 FALSE 664 2020-01-02 10:05:52 |
| 169 | +#> ctime atime uid gid uname grname |
| 170 | +#> 1 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck |
| 171 | +#> 2 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck |
| 172 | +#> 5 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck |
| 173 | +#> 3 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck |
| 174 | +#> 4 2020-01-02 10:05:52 2020-01-02 10:05:52 1032 1032 fleck fleck |
| 175 | +#> timestamp |
| 176 | +#> 1 2020-01-02 10:05:52 |
| 177 | +#> 2 2020-01-02 10:05:52 |
| 178 | +#> 5 2020-01-02 10:05:52 |
| 179 | +#> 3 2020-01-02 00:00:00 |
| 180 | +#> 4 2020-01-01 00:00:00 |
| 181 | +``` |
| 182 | + |
| 183 | +If we examine the “timestamp” column in the example above, we see that |
| 184 | +missing date information is always interpreted as the start of the |
| 185 | +period; i.e. so `"2019-01"` is equivalent to `"2019-01-01--00--00--00"` |
| 186 | +for all intents and purposes. |
| 187 | + |
| 188 | +``` r |
| 189 | +prune_backups(tf, max_backups = 0) # cleanup |
| 190 | +list_backups(tf) |
| 191 | +#> character(0) |
| 192 | +``` |
| 193 | + |
| 194 | +Besides passing a total number of backups to keep, `max_backups` can |
| 195 | +also be a period or a date / datetime for timestamped backups. |
| 196 | + |
| 197 | +``` r |
| 198 | +# keep all backups younger than one year |
| 199 | +prune_backups(tf, "1 year") |
| 200 | + |
| 201 | +# keep all backups from April 4th, 2018 and onwards |
| 202 | +prune_backups(tf, "2018-04-01") |
| 203 | +``` |
| 204 | + |
| 205 | +# Dependencies |
| 206 | + |
| 207 | +**rotor**’s dependencies are intentionally kept slim. It only comes with |
| 208 | +two non-base dependencies: |
| 209 | + |
| 210 | + - [R6](https://github.com/r-lib/R6): A light weight system for |
| 211 | + encapsulated object-oriented programming. |
| 212 | + - [dint](https://github.com/s-fleck/dint): A toolkit for working |
| 213 | + year-quarter and year-month dates that I am also the author of. It |
| 214 | + is used by `rotate_date()` and `rotate_time()` to deal with calendar |
| 215 | + periods (such as weeks or months). |
| 216 | + |
| 217 | +Both packages have no transitive dependencies (i.e they do not depend on |
| 218 | +anything outside of base R) |
0 commit comments