Learn R Programming

rotor

rotor provides a cross platform R reimagination of logrotate. It is a companion package to the logging package lgr. In contrast to logrotate, rotor relies solely on information encoded in a suffixes of file names for conditionally creating backups (i.e. a timestamp or index). It therefore also works with backups created by other tools, as long as the filename has a format that rotor can understand.

rotate(), rotate_date(), and rotate_time() move a file and insert a suffix (either an integer or a timestamp) into the filename. In addition, they create an empty file in place of the original one. This is useful for log rotation. backup(), backup_date() and backup_time() do the same but keep the original file.

rotor also includes utility functions for finding and examining the backups of a file: list_backups(), backup_info(), n_backups, newest_backup(), oldest_backup(). See the function reference for details.

Installation

You can install the released version of rotor from CRAN with:

install.packages("rotor")

And the development version from GitHub with:

# install.packages("remotes")
remotes::install_github("s-fleck/rotor")

Example

First we create a temporary directory for the files created by the code examples

library(rotor)

# create a directory
td <- file.path(tempdir(), "rotor")
dir.create(td, recursive = TRUE)

# create an example logfile
tf <- file.path(td, "mylogfile.log")
writeLines("An important message", tf)

Indexed backups

backup() makes a copy of a file and inserts an index between the filename and the file extension. The file with the index 1 is always the most recently made backup.

backup(tf)

# backup and rotate also support compression
backup(tf, compression = TRUE) 

# display backups of a file
list_backups(tf)  
#> [1] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.1.log.zip"
#> [2] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.2.log"

rotate() also backs up a file, but replaces the original file with an empty one.

rotate(tf)
list_backups(tf)
#> [1] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.1.log"    
#> [2] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.2.log.zip"
#> [3] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.3.log"

# the original file is now empty
readLines(tf)
#> character(0)

# its content was moved to the first backup
readLines(list_backups(tf)[[1]])
#> [1] "An important message"

# we can now safely write to the original file
writeLines("another important message", tf)

The max_backups parameter limits the maximum number of backups rotor will keep of a file. Notice how the zipped backup we created above moves to index 4 as we create two new backups.

backup(tf, max_backups = 4)
backup(tf, max_backups = 4)

list_backups(tf)
#> [1] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.1.log"    
#> [2] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.2.log"    
#> [3] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.3.log"    
#> [4] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.4.log.zip"

We can also use prune_backups() to delete old backups. Other than ensuring that no new backups is created, it works identically to using backup() with the max_backups parameter. By setting it to 0, we delete all backups.

prune_backups(tf, max_backups = 0)

Timestamped backups

rotor can also create timestamped backups. backup_date() creates uses a Date (yyyy-mm-dd) timestamp, backup_time() uses a full datetime-stamp by default (yyyy-mm-dd--hh-mm-ss). The format of the timestamp can be modified with a subset of the formatting tokens understood by strftime() (within certain restrictions). Backups created with both functions are compatible with each other (but not with those created with backup_index()).

# be default backup_date() only makes a backup if the last backups is younger
# than 1 day, so we set `age` to -1 for this example
backup_date(tf, age = -1)  
backup_date(tf, format = "%Y-%m", age = -1)
backup_time(tf)
backup_time(tf, format = "%Y-%m-%d_%H-%M-%S")  # Python logging
backup_time(tf, format = "%Y%m%dT%H%M%S")  # ISO 8601 compatible

backup_info(tf)
#>                                                                                           path
#> 1 C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.2022-09-02--13-25-45.log
#> 3  C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.2022-09-02_13-25-45.log
#> 5      C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.20220902T132545.log
#> 2           C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.2022-09-02.log
#> 4              C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/rotor/mylogfile.2022-09.log
#>        name                  sfx ext size isdir mode               mtime
#> 1 mylogfile 2022-09-02--13-25-45 log   27 FALSE  666 2022-09-02 13:25:45
#> 3 mylogfile  2022-09-02_13-25-45 log   27 FALSE  666 2022-09-02 13:25:45
#> 5 mylogfile      20220902T132545 log   27 FALSE  666 2022-09-02 13:25:45
#> 2 mylogfile           2022-09-02 log   27 FALSE  666 2022-09-02 13:25:45
#> 4 mylogfile              2022-09 log   27 FALSE  666 2022-09-02 13:25:45
#>                 ctime               atime exe           timestamp
#> 1 2022-09-02 13:25:45 2022-09-02 13:25:45  no 2022-09-02 13:25:45
#> 3 2022-09-02 13:25:45 2022-09-02 13:25:45  no 2022-09-02 13:25:45
#> 5 2022-09-02 13:25:45 2022-09-02 13:25:45  no 2022-09-02 13:25:45
#> 2 2022-09-02 13:25:45 2022-09-02 13:25:45  no 2022-09-02 00:00:00
#> 4 2022-09-02 13:25:45 2022-09-02 13:25:45  no 2022-09-01 00:00:00

If we examine the “timestamp” column in the example above, we see that missing date information is always interpreted as the start of the period; i.e. so "2019-01" is equivalent to "2019-01-01--00--00--00" for all intents and purposes.

prune_backups(tf, max_backups = 0)  # cleanup
list_backups(tf)
#> character(0)

Besides passing a total number of backups to keep, max_backups can also be a period or a date / datetime for timestamped backups.

# keep all backups younger than one year
prune_backups(tf, "1 year") 
  
# keep all backups from April 4th, 2018 and onwards
prune_backups(tf, "2018-04-01")  

Cache

rotor also provides a simple on-disk key-value store that can be pruned by size, age or number of files.

cache <- Cache$new(file.path(tempdir(), "cache-test"), hashfun = digest::digest)
#> creating directory 'C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/cache-test'
key1 <- cache$push(iris)
key2 <- cache$push(cars)
key3 <- cache$push(mtcars)

cache$files$path
#> [1] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/cache-test/d3c5d071001b61a9f6131d3004fd0988"
#> [2] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/cache-test/f98a59010652c8e1ee062ed4c43f648e"
#> [3] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/cache-test/a63c70e73b58d0823ab3bcbd3b543d6f"

head(cache$read(key1))
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1          5.1         3.5          1.4         0.2  setosa
#> 2          4.9         3.0          1.4         0.2  setosa
#> 3          4.7         3.2          1.3         0.2  setosa
#> 4          4.6         3.1          1.5         0.2  setosa
#> 5          5.0         3.6          1.4         0.2  setosa
#> 6          5.4         3.9          1.7         0.4  setosa

cache$prune(max_files = 1)
cache$files$path
#> [1] "C:/Users/STEFAN~1.FLE/AppData/Local/Temp/RtmpYZSExE/cache-test/a63c70e73b58d0823ab3bcbd3b543d6f"
cache$purge()  # deletes all cached files
cache$destroy()  # deletes the cache directory

Dependencies

rotor’s dependencies are intentionally kept slim. It only comes with two non-base dependencies:

  • R6: A light weight system for encapsulated object-oriented programming.
  • dint: A toolkit for working year-quarter and year-month dates that I am also the author of. It is used by rotate_date() and rotate_time() to deal with calendar periods (such as weeks or months).

Both packages have no transitive dependencies (i.e they do not depend on anything outside of base R)

Optional dependencies:

  • digest,
    ulid, or uuid for generating hashes or UIDs when using Cache. Storage keys for cache files can also be set manually, in which case no external dependencies are required.
  • zip is supported as an alternative to the integrated zip function in R. Might work better on some systems and worse on others.
  • crayon for terminal colors

Copy Link

Version

Install

install.packages('rotor')

Monthly Downloads

565

Version

0.3.7

License

MIT + file LICENSE

Maintainer

Last Published

October 11th, 2022

Functions in rotor (0.3.7)

BackupQueue

An R6 Class for managing backups (abstract base class)
rotate_rds

Serialize R objects to disk (with backup)
rotor-package

rotor: Log Rotation and Conditional Backups
DirectoryQueue

An R6 class for managing persistent file-based queues (abstract base class)
Cache

An R6 class for managing a persistent file-based cache
backup_info

Discover existing backups
BackupQueueDate

An R6 class for managing datestamped backups
BackupQueueDateTime

An R6 class for managing timestamped backups
BackupQueueIndex

An R6 class for managing indexed backups
rotate

Rotate or backup files