├── .gitignore ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── R ├── getx_setx.R ├── message.R ├── onload.R ├── printlogmessage.R ├── stop.R └── warning.R ├── README.md └── man └── rlogging.Rd /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.swp 3 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: rlogging 2 | Version: 1.1 3 | Date: 2014-03-28 4 | License: MIT + file LICENSE 5 | Description: A simple logging facility that extends R's message(), warning(), and stop() functions. 6 | Title: Simple logging in R 7 | Author: Maarten-Jan Kallen, Felix Jung 8 | Maintainer: Maarten-Jan Kallen 9 | URL: https://github.com/mjkallen/rlogging 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Maarten-Jan Kallen 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | export(GetLogFile) 2 | export(SetLogFile) 3 | export(GetLogLevel) 4 | export(SetLogLevel) 5 | export(GetTimeStampFormat) 6 | export(SetTimeStampFormat) 7 | export(GetFilenameSuffixes) 8 | export(SetFilenameSuffixes) 9 | export(message) 10 | export(warning) 11 | export(stop) 12 | -------------------------------------------------------------------------------- /R/getx_setx.R: -------------------------------------------------------------------------------- 1 | SetLogLevel <- function(level="INFO") { 2 | stopifnot(level %in% c("INFO", "WARN", "STOP")) 3 | assign("loglevel", level, envir=.rloggingOptions) 4 | } 5 | 6 | GetLogLevel <-function() { 7 | get("loglevel", envir=.rloggingOptions) 8 | } 9 | 10 | SetTimeStampFormat <- function(ts.format="[%Y-%m-%d %H:%M:%S]") { 11 | assign("ts.format", ts.format, envir=.rloggingOptions) 12 | } 13 | 14 | GetTimeStampFormat <- function() { 15 | get("ts.format", envir=.rloggingOptions) 16 | } 17 | 18 | SetFilenameSuffixes <- function(file.name.suffixes=list(INFO="message", 19 | WARN="warning", 20 | STOP="stop")) { 21 | # Check that a list of length=3 is passed to this function: 22 | if (!is.list(file.name.suffixes) | length(file.name.suffixes) != 3) { 23 | stop("argument file.name.suffixes must must be a list of length=3.") 24 | } 25 | 26 | # Check if the list of suffixes contains INFO, WARN, and STOP. If not, 27 | # Return a message indicating the missing elements of file.name.suffixes 28 | missing.elements <- setdiff(c("INFO", "WARN", "STOP"), 29 | names(file.name.suffixes)) 30 | 31 | if (length(missing.elements)) { 32 | stop("argument file.name.suffixes is missing element(s): ", 33 | paste(missing.elements, sep="", collapse=", ")) 34 | } 35 | 36 | assign("file.name.suffixes", file.name.suffixes, envir=.rloggingOptions) 37 | } 38 | 39 | GetFilenameSuffixes <- function() { 40 | get("file.name.suffixes", envir=.rloggingOptions) 41 | } 42 | 43 | SetLogFile <- function(base.file="rlogging.log", folder=getwd(), 44 | split.files=FALSE) { 45 | assign("split.files", split.files, envir=.rloggingOptions) 46 | 47 | if (is.null(base.file)) { 48 | assign("logfile.base", NULL, envir=.rloggingOptions) 49 | } else { 50 | assign("logfile.base", file.path(folder, base.file), 51 | envir=.rloggingOptions) 52 | } 53 | } 54 | 55 | GetLogFile <- function(level) { 56 | base.file <- get("logfile.base", envir=.rloggingOptions) 57 | split.files <- get("split.files", envir=.rloggingOptions) 58 | if (!missing(level)) { 59 | if (!split.files) { 60 | warning("level=", level, "provided to GetLogFile(), but log files 61 | are not split. Ignoring parameter.") 62 | base.file 63 | } else { 64 | file.name.suffix <- get(level, GetFilenameSuffixes()) 65 | replacement <- paste("\\1", "_", file.name.suffix, "\\2", sep="") 66 | sub("(.+?)(\\.[^.]*$|$)", replacement, base.file) 67 | } 68 | } else { 69 | if (split.files) { 70 | stop("log files are split, but no level parameter provided to 71 | GetLogFile().") 72 | } else { 73 | base.file 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /R/message.R: -------------------------------------------------------------------------------- 1 | message <- function(..., domain=NULL, appendLF=TRUE) { 2 | 3 | args <- list(...) 4 | is.condition <- length(args) == 1L && inherits(args[[1L]], "condition") 5 | if (is.condition) { 6 | # bypass the logger if a condition is supplied or if loglevel is set to "NONE" 7 | base::message(..., domain=domain, appendLF=appendLF) 8 | } else { 9 | # if loglevel is set to INFO, then print log message, else do nothing 10 | loglevel <- GetLogLevel() 11 | split.files <- get("split.files", envir=.rloggingOptions) 12 | if (loglevel == "INFO") { 13 | if (split.files) { 14 | PrintLogMessage(..., level="INFO") 15 | } else { 16 | PrintLogMessage("[INFO] ", ...) 17 | } 18 | } 19 | } 20 | invisible() 21 | 22 | } 23 | -------------------------------------------------------------------------------- /R/onload.R: -------------------------------------------------------------------------------- 1 | .rloggingOptions <- new.env() 2 | 3 | .onLoad <- function(libname, pkgname) { 4 | SetTimeStampFormat() 5 | SetFilenameSuffixes() 6 | SetLogFile() 7 | SetLogLevel() 8 | lockEnvironment(.rloggingOptions) 9 | } 10 | 11 | .onAttach <- function(libname, pkgname) { 12 | pkgversion <- read.dcf(system.file("DESCRIPTION", package=pkgname), 13 | fields="Version") 14 | msg <- paste("Package", pkgname, "version", pkgversion, 15 | "\nNOTE: - Logging level is set to", GetLogLevel(), 16 | "\n - Output file is", GetLogFile(), 17 | "\n - See 'package?rlogging' for help.") 18 | packageStartupMessage(msg) 19 | } 20 | -------------------------------------------------------------------------------- /R/printlogmessage.R: -------------------------------------------------------------------------------- 1 | PrintLogMessage <- function(..., domain=NULL, level) { 2 | 3 | timestamp <- format(Sys.time(), format=GetTimeStampFormat()) 4 | base::message(timestamp, ..., domain=domain) 5 | #cat(timestamp, ..., "\n", sep="") 6 | 7 | # print log message to file if this one is not set to NULL 8 | if (missing(level)) { 9 | logfile <- GetLogFile() 10 | } else { 11 | logfile <- GetLogFile(level=level) 12 | } 13 | 14 | if (!is.null(logfile)) { 15 | cat(timestamp, ..., "\n", file=logfile, sep="", append=TRUE) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /R/stop.R: -------------------------------------------------------------------------------- 1 | stop <- function(..., call.=TRUE, domain=NULL) { 2 | 3 | args <- list(...) 4 | is.condition <- length(args) == 1L && inherits(args[[1L]], "condition") 5 | if (!is.condition) { 6 | split.files <- get("split.files", envir=.rloggingOptions) 7 | # error messages are always printed (i.e. for levels INFO, WARN and STOP) 8 | if (split.files) { 9 | PrintLogMessage(..., level="STOP") 10 | } else { 11 | PrintLogMessage("[STOP] ", ...) 12 | } 13 | } 14 | base::stop(..., call.=call., domain=domain) 15 | invisible() 16 | 17 | } 18 | -------------------------------------------------------------------------------- /R/warning.R: -------------------------------------------------------------------------------- 1 | warning <- function(..., call.=TRUE, immediate.=FALSE, domain=NULL) { 2 | 3 | args <- list(...) 4 | is.condition <- length(args) == 1L && inherits(args[[1L]], "condition") 5 | if (!is.condition) { 6 | # if loglevel is set to INFO or WARN, then print log message 7 | loglevel <- GetLogLevel() 8 | split.files <- get("split.files", envir=.rloggingOptions) 9 | if (loglevel %in% c("INFO", "WARN")) { 10 | if (split.files) { 11 | PrintLogMessage(..., level="WARN") 12 | } else { 13 | PrintLogMessage("[WARN] ", ...) 14 | } 15 | } 16 | # always collect warnings when printing log messages 17 | immediate. <- FALSE 18 | } 19 | # always call the base warning function to collect warnings 20 | base::warning(..., call.=call., immediate.=immediate., domain=domain) 21 | invisible() 22 | 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A simple logging facility for R 2 | =============================== 3 | 4 | This package takes a different approach compared to other R packages like the 5 | [log4r](http://cran.r-project.org/package=log4r), 6 | [futile.logger](http://cran.r-project.org/package=futile.logger) and 7 | [logging](http://cran.r-project.org/package=logging) packages. Whereas these are 8 | inspired by Java's [log4j](http://logging.apache.org/log4j/) or Python's 9 | [logging module](http://docs.python.org/2/library/logging.html), *rlogging* masks 10 | R's `message()`, `warning()` and `stop()` functions to provide the option to 11 | write uniform log messages to the console and/or a text file. 12 | 13 | Benefits 14 | -------- 15 | 16 | This simple approach provides a number of benefits: 17 | 18 | * it's simple 19 | * no changes to existing code required 20 | * no dependencies on other R packages except R's base package 21 | 22 | Functionality 23 | ------------- 24 | 25 | In essence, logging is the act of displaying diagnostic and informative messages 26 | during the execution of a software program. Log messages can be used to debug 27 | software, to inform the user of the progress of long calculations, to document 28 | scripts, or to support the auditing of software and the results of complicated 29 | calculations. 30 | 31 | R has a number of functions which can be used for logging purposes. Most people 32 | will be familiar with the `cat()` command, which concatenates its arguments and 33 | prints the result to the console or to a text file. In practice, you will want 34 | a little more control over the level of detail of logging messages. Also, you 35 | will need to put a `cat()` statement before each `warning()` and `stop()` 36 | command to ensure that these events will show up in the logs. Because these two 37 | commands and the `message()` command write to 38 | [stderr](http://en.wikipedia.org/wiki/Standard_streams), 39 | the messages may get lost when running R scripts in batch mode. 40 | 41 | To provide a slightly more advanced logging facility compared to using only the 42 | `cat()` command, I have created the *rlogging* package. The idea is to use three 43 | existing commands in R's base package, namely `message()`, `warning()`, and 44 | `stop()` to provide three logging levels: 'INFO', 'WARN', and 'STOP' 45 | respectively. The package provides a wrapper around these commands (i.e. it 46 | 'masks' them) to output time-stamped log messages to the console and a text 47 | file. For example, the `message()` command prints a log message if the logging 48 | level is set to 'INFO': 49 | 50 | > message("Hello world!") 51 | [2013-09-20 07:56:07] [INFO] Hello world! 52 | > 53 | 54 | Each message starts with the timestamp and the logging level so it's easy to 55 | find (e.g. using *grep*) different types of log messages in the log file. 56 | 57 | ### Warnings and errors 58 | 59 | So you can use the `message()` command to output informative or debugging 60 | messages. The `warning()` command also (immediately) writes a time-stamped 61 | message to the console and log file, but it also retains its original 62 | behaviour. This means that, by default, warning messages are collected and 63 | shown only after the program is finished. These can be recalled with the 64 | `warnings()` function and suppressed by setting `options(warn=-1)`. Note that 65 | the latter does not suppress the log messages. See the next section on logging 66 | levels which allow you to control this behaviour. Here's a session which 67 | demonstrates the use of `warning()`: 68 | 69 | > GetLogLevel() 70 | [1] "INFO" 71 | > warning("Be careful!") 72 | [2013-09-20 08:56:32] [WARN] Be careful! 73 | Warning message: 74 | In warning("Be careful!") : Be careful! 75 | > warnings() 76 | Warning message: 77 | In warning("Be careful!") : Be careful! 78 | > options(warn=-1) 79 | > warning("Be careful!") 80 | [2013-09-20 08:56:56] [WARN] Be careful! 81 | > 82 | 83 | As you can see, the first `warning()` prints the same message twice: the first 84 | one is the time-stamped log message (which is also written to the log file) and 85 | the second one is the message written to `stderr` by the `warning()` command in 86 | R's base package. The second `warning()` only prints the time-stamped log 87 | message because the printing of warnings has been suppressed. 88 | 89 | Like the `warning()` function, the `stop()` function also retains its original 90 | behaviour, i.e. it prints the error message and stops the execution of the 91 | program. The difference is that a log message is also created so there is no 92 | need to add an extra `cat()` statement right before the call to `stop()`. This 93 | is especially useful when running R scripts in batch mode and messages to 94 | `stderr` may be lost. Here again, the same message is printed to the console 95 | twice, but this can not be suppressed. Note that the behaviour of the 96 | `stopifnot()` command is not changed by the package, therefore this command 97 | does not write a log message. 98 | 99 | ### Logging levels 100 | 101 | Each of the three functions is linked to one of three logging levels: 'INFO', 102 | 'WARN', and 'STOP'. The following table summarizes when messages are printed: 103 | 104 | Level message() warning() stop() 105 | INFO yes yes yes 106 | WARN no yes yes 107 | STOP no no yes 108 | 109 | Error messages are always printed, but information messages and warnings can be 110 | suppressed. 111 | 112 | ### Log file 113 | 114 | By default, log messages are appended to a file *rlogging.log* in the current 115 | working directory. You can change the name and location of this file with the 116 | `SetLogFile(file, folder)` command provided by the package: 117 | 118 | SetLogFile("mylogfile.txt") # use 'mylogfile.txt' in the current working directory 119 | SetLogFile(folder="~") # save 'mylogfile.txt' in my home directory 120 | SetLogFile(file="mylogfile.txt", folder="~") # same as both commands above 121 | 122 | If you do not want to write to a file, then you set the file to `NULL` as in 123 | the following example: 124 | 125 | SetLogFile(NULL) 126 | 127 | Installation 128 | ------------ 129 | 130 | 1. Use the `install_github()` command from the [devtools](https://github.com/hadley/devtools) package. 131 | 2. Download and install manually: 132 | 133 | git clone https://github.com/mjkallen/rlogging.git 134 | R CMD INSTALL rlogging 135 | 136 | License 137 | ------- 138 | 139 | MIT license. See the LICENSE file for details. 140 | -------------------------------------------------------------------------------- /man/rlogging.Rd: -------------------------------------------------------------------------------- 1 | \name{rlogging} 2 | \alias{rlogging-package} 3 | \alias{SetLogFile} 4 | \alias{GetLogFile} 5 | \alias{SetLogLevel} 6 | \alias{GetLogLevel} 7 | \alias{message} 8 | \alias{warning} 9 | \alias{stop} 10 | \title{Simple logging in R} 11 | \description{ 12 | Provides a wrapper around R's base functions \command{message}, 13 | \command{warning}, and \command{stop} to provide a simple, but effective, 14 | logging facility: output log messages with timestamps, direct output to the 15 | console and/or to a text file of your choosing, and control the level of 16 | logging. 17 | } 18 | \usage{ 19 | SetLogFile(base.file="rlogging.log", folder=getwd(), split.files=FALSE) 20 | GetLogFile(level) 21 | 22 | SetLogLevel(level="INFO") 23 | GetLogLevel() 24 | 25 | SetTimeStampFormat(ts.format="[\%Y-\%m-\%d \%H:\%M:\%S]") 26 | GetTimeStampFormat() 27 | 28 | SetFilenameSuffixes(file.name.suffixes=list(INFO="message", 29 | WARN="warning", 30 | STOP="stop")) 31 | GetFilenameSuffixes() 32 | 33 | message(\dots, domain=NULL, appendLF=TRUE) 34 | warning(\dots, call.=TRUE, immediate.=FALSE, domain=NULL) 35 | stop(\dots, call.=TRUE, domain=NULL) 36 | } 37 | \arguments{ 38 | \item{base.file}{Base name of the log file(s) as a string (default is 39 | \file{rlogging.log}). Set to \code{NULL} if no file output is desired.} 40 | \item{folder}{Root folder where the log file should be written to (default 41 | is the current working directory).} 42 | \item{split.files}{Boolean indicating whether messages, warnings, and 43 | errors should be split into several files. If set to \code{FALSE}, all 44 | logging output will be written into the file provided by the 45 | \code{base.file} parameter. The prefixes \sQuote{INFO}, \sQuote{WARN}, 46 | and \sQuote{STOP} will be used to differentiate the different logging 47 | levels in the log. If set to \code{TRUE}, each level will be logged into 48 | its own log file using respective suffixes for the log filenames. The 49 | suffixes are settable through the \command{SetFilenameSuffixes} function.} 50 | \item{level}{Logging level (default is \sQuote{INFO}, others are 51 | \sQuote{WARN} and \sQuote{STOP}).} 52 | \item{ts.format}{A string providing the format used for timestamps in log 53 | files. See \command{base::strptime} for a documentation of formatting 54 | options.} 55 | \item{file.name.suffixes}{Named \code{list} of filename suffixes to be 56 | used when \code{split.files} is set to \code{TRUE}. The list must contain 57 | the three elements \sQuote{INFO}, \sQuote{WARN}, 58 | and \sQuote{STOP}.} 59 | \item{\dots}{R objects to be converted into a log message. Passed on to 60 | \command{base::message()}, \command{base::warning()}, and \command{base::stop()}.} 61 | \item{domain}{Passed on to \command{base::message()}, \command{base::warning()}, and 62 | \command{base::stop()}.} 63 | \item{appendLF}{Passed on to \command{base::message()}.} 64 | \item{call.}{Passed on to \command{base::warning()} and \command{base::stop()}.} 65 | \item{immediate.}{Passed on to \command{base::warning()}.} 66 | } 67 | \details{ 68 | This package provides simple logging functionality on top of R's 69 | \command{message()}, \command{warning()}, and \command{stop()} functions. These functions 70 | are matched to the three logging levels \sQuote{INFO}, \sQuote{WARN}, and 71 | \sQuote{STOP} respectively. 72 | When loading the package, the level is set to \sQuote{INFO} and messages will be 73 | printed to the console as well as a text file \file{rlogging.log} in the working 74 | directory. The name and location of the log file can be changed with 75 | \command{SetLogFile}. If you do not want to write to a file, do 76 | \command{SetLogFile(NULL)}. If the file already exists, then new messages will 77 | be appended to the existing messages. If you wish to split messages, warnings, 78 | and errors accross multiple files, you can do so by setting the 79 | \code{split.files} parameter to the \command{SetLogFile} function to 80 | \code{TRUE}. 81 | 82 | Log messages start with a timestamp (i.e. the result of 83 | \command{format(Sys.time())}) surrounded by square brackets, followed by the 84 | logging level, also surrounded in square brackets, and finally the message 85 | itself. For example, \command{message("Hello world!")} will print 86 | \preformatted{[2013-09-12 17:14:46] [INFO] Hello world!} 87 | to the console and append the same message to the log file if this one is not 88 | set to \code{NULL}. If \code{split.files} is set to \code{TRUE}, the logging 89 | level is not printed to file, but is still printed to console. The format of 90 | the timestamp can be changed through the function \command{SetTimeStampFormat}. 91 | 92 | If the log level is set to \sQuote{INFO}, then all three functions will print a 93 | message. If set to \sQuote{WARN} then only \command{warning()} and 94 | \command{stop()} will print log messages. Finally, if set to \sQuote{STOP}, then 95 | a log message is only printed in case of an error. Note that in case of 96 | \command{warning()} and \command{stop()} the message is always forwarded to the 97 | base function to retain their usual behaviour (i.e. saving warning messages 98 | until later and stopping the execution of a program after an error has 99 | occurred). 100 | } 101 | \note{ 102 | The behaviour of the \command{stopifnot()} command in R's base package is not 103 | changed, therefore no log messages will be printed or written to the log file. 104 | } 105 | \seealso{ 106 | See the functions \link[base]{message}, \link[base]{warning}, and 107 | \link[base]{stop} in R's base package for their usage. 108 | } 109 | \author{Maarten-Jan Kallen \email{sayhi@maartenjan.org} 110 | 111 | Felix Jung \email{felix@jung.fm} 112 | } 113 | \value{ 114 | \command{GetLogFile} returns a string with the full path to the log 115 | file, possibly specified through the \code{level} parameter. 116 | 117 | \command{GetLogLevel} returns a string with the current log level. 118 | 119 | \command{GetTimeStampFormat} returns a \code{string} containing the 120 | timestamp format. 121 | 122 | \command{GetFilenameSuffixes}{Returns the named \code{list} of filename 123 | suffixes.} 124 | } 125 | --------------------------------------------------------------------------------