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.
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")
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)
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)
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")
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
rotor’s dependencies are intentionally kept slim. It only comes with two non-base dependencies:
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:
Cache
. Storage keys for cache files can
also be set manually, in which case no external dependencies are
required.Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.