1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
|
#' Manage loggers
#'
#' Provides functions for writing log messages and managing loggers. Typically
#' only the flog.[trace|debug|info|warn|error|fatal] functions need to be used
#' in conjunction with flog.threshold to interactively change the log level.
#'
#' @section Usage:
#' # Conditionally print a log statement at TRACE log level\cr
#' flog.trace(msg, ..., name=flog.namespace(), capture=FALSE)
#'
#' # Conditionally print a log statement at DEBUG log level\cr
#' flog.debug(msg, ..., name=flog.namespace(), capture=FALSE)
#'
#' # Conditionally print a log statement at INFO log level\cr
#' flog.info(msg, ..., name=flog.namespace(), capture=FALSE)
#'
#' # Conditionally print a log statement at WARN log level\cr
#' flog.warn(msg, ..., name=flog.namespace(), capture=FALSE)
#'
#' # Conditionally print a log statement at ERROR log level\cr
#' flog.error(msg, ..., name=flog.namespace(), capture=FALSE)
#'
#' # Print a log statement at FATAL log level\cr
#' flog.fatal(msg, ..., name=flog.namespace(), capture=FALSE)
#'
#' # Execute an expression and capture any warnings or errors
#' ftry(expr, error=stop, finally=NULL)
#'
#' @section Additional Usage:
#' These functions generally do not need to be called by an end user.
#'
#' # Get the ROOT logger\cr
#' flog.logger()
#'
#' # Get the logger with the specified name\cr
#' flog.logger(name)
#'
#' # Set options for the given logger\cr
#' flog.logger(name, threshold=NULL, appender=NULL, layout=NULL, carp=NULL)
#'
#' @section Details:
#' These functions represent the high level interface to futile.logger.
#'
#' The primary use case for futile.logger is to write out log messages. There
#' are log writers associated with all the predefined log levels: TRACE, DEBUG,
#' INFO, WARN, ERROR, FATAL. Log messages will only be written if the log level
#' is equal to or more urgent than the current threshold. By default the ROOT
#' logger is set to INFO.
#'
#' > flog.debug("This won't print") \cr
#' > flog.info("But this \%s", 'will') \cr
#' > flog.warn("As will \%s", 'this')
#'
#' Typically, the built in log level constants are used in the call, which
#' conform to the log4j levels (from least severe to most severe): TRACE,
#' DEBUG, INFO, WARN, ERROR, FATAL. It is not a strict requirement to use these
#' constants (any numeric value will work), though most users should find this
#' level of granularity sufficient.
#'
#' Loggers are hierarchical in the sense that any requested logger that is
#' undefined will fall back to its most immediate defined parent logger. The
#' absolute parent is ROOT, which is guaranteed to be defined for the system
#' and cannot be deleted. This means that you can specify a new logger
#' directly.
#'
#' > flog.info("This will fall back to 'my', then 'ROOT'", name='my.logger')
#'
#' You can also change the threshold or any other setting associated with a
#' logger. This will create an explicit logger where any unspecified options
#' are copied from the parent logger.
#'
#' > flog.appender(appender.file("foo.log"), name='my') \cr
#' > flog.threshold(ERROR, name='my.logger') \cr
#' > flog.info("This won't print", name='my.logger') \cr
#' > flog.error("This %s print to a file", 'will', name='my.logger') \cr
#'
#' If you define a logger that you later want to remove, use flog.remove.
#'
#' The option 'capture' allows you to print out more complicated data
#' structures without a lot of ceremony. This variant doesn't accept format
#' strings and instead appends the value to the next line of output. Consider
#'
#' > m <- matrix(rnorm(12), nrow=3) \cr
#' > flog.info("Matrix:",m, capture=TRUE)
#'
#' which preserves the formatting, whereas using capture=FALSE will have
#' a cluttered output due to recycling.
#'
#' @name flog.logger
#' @aliases flog.trace flog.debug flog.info flog.warn flog.error flog.fatal
#' @param msg The message to log
#' @param name The logger name to use
#' @param capture Capture print output of variables instead of interpolate
#' @param \dots Optional arguments to populate the format string
#' @param expr An expression to evaluate
#' @param finally An optional expression to evaluate at the end
#' @author Brian Lee Yung Rowe
#' @seealso \code{\link{flog.threshold}} \code{\link{flog.remove}}
#' \code{\link{flog.carp}} \code{\link{flog.appender}} \code{\link{flog.layout}}
#' @keywords data
#' @examples
#'
#' flog.threshold(DEBUG)
#' flog.debug("This debug message will print")
#'
#' flog.threshold(WARN)
#' flog.debug("This one won't")
#'
#' m <- matrix(rnorm(12), nrow=3)
#' flog.info("Matrix:",m, capture=TRUE)
#'
#' ftry(log(-1))
#'
#' \dontrun{
#' s <- c('FCX','AAPL','JPM','AMZN')
#' p <- TawnyPortfolio(s)
#'
#' flog.threshold(TRACE,'tawny')
#' ws <- optimizePortfolio(p, RandomMatrixDenoiser())
#' z <- getIndexComposition()
#'
#' flog.threshold(WARN,'tawny')
#' ws <- optimizePortfolio(p, RandomMatrixDenoiser())
#' z <- getIndexComposition()
#'
#' }
NULL
.log_level <- function(msg, ..., level, name, capture)
{
logger <- flog.logger(name)
if (level > logger$threshold && (is.null(logger$carp) || !logger$carp)) {
return(invisible())
}
appender <- flog.appender(name)
layout <- flog.layout(name)
if (capture) {
values <- paste(capture.output(print(...)), collapse='\n')
message <- c(layout(level, msg), "\n", values, "\n")
} else {
message <- layout(level, msg, ...)
}
if (level <= logger$threshold) appender(message)
invisible(message)
}
# Get the namespace that a function resides in. If no namespace exists, then
# return NULL.
# <environment: namespace:lambda.r>
flog.namespace <- function(where=1)
{
s <- capture.output(str(topenv(environment(sys.function(where))), give.attr=FALSE))
if (length(grep('lambda.r',s)) > 0)
s <- attr(sys.function(-5), 'topenv')
if (length(grep('namespace', s)) < 1) return('ROOT')
ns <- sub('.*namespace:([^>]+)>.*','\\1', s)
ifelse(is.null(ns), 'ROOT', ns)
}
flog.trace <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
.log_level(msg, ..., level=TRACE,name=name, capture=capture)
}
flog.debug <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
.log_level(msg, ..., level=DEBUG,name=name, capture=capture)
}
flog.info <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
.log_level(msg, ..., level=INFO,name=name, capture=capture)
}
flog.warn <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
.log_level(msg, ..., level=WARN,name=name, capture=capture)
}
flog.error <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
.log_level(msg, ..., level=ERROR,name=name, capture=capture)
}
flog.fatal <- function(msg, ..., name=flog.namespace(), capture=FALSE) {
.log_level(msg, ..., level=FATAL,name=name, capture=capture)
}
#' Wrap a try block in futile.logger
#'
#' This function integrates futile.logger with the error and warning system
#' so problems can be caught both in the standard R warning system, while
#' also being emitted via futile.logger.
#'
#' @name ftry
#' @param expr The expression to evaluate in a try block
#' @param error An error handler
#' @param finally Pass-through to tryCatch finally
#' @author Brian Lee Yung Rowe
#' @keywords data
#' @examples
#' ftry(log(-1))
ftry <- function(expr, error=stop, finally=NULL) {
w.handler <- function(e) flog.warn("%s", e)
e.handler <- function(e) { flog.error("%s", e); error(e) }
tryCatch(expr, warning=w.handler, error=e.handler, finally)
}
# By default, use the package namespace or use the 'ROOT' logger.
flog.logger() %as%
{
flog.logger(flog.namespace())
}
flog.logger(name) %as%
{
if (nchar(name) < 1) name <- 'ROOT'
#cat(sprintf("Searching for logger %s\n", name))
key <- paste("logger", name, sep='.')
# TODO: Search hierarchy
os <- logger.options(key)
if (! is.null(os)) return(os)
if (name == 'ROOT') {
logger <- list(name=name,
threshold=INFO,
appender=appender.console(),
layout=layout.simple)
logger.options(update=list(key, logger))
return(logger)
}
parts <- strsplit(name, '.', fixed=TRUE)[[1]]
parent <- paste(parts[1:length(parts)-1], collapse='.')
flog.logger(parent)
}
flog.logger(name, threshold=NULL, appender=NULL, layout=NULL, carp=NULL) %as%
{
logger <- flog.logger(name)
if (!is.null(threshold)) logger$threshold <- threshold
if (!is.null(appender)) logger$appender <- appender
if (!is.null(layout)) logger$layout <- layout
if (!is.null(carp)) logger$carp <- carp
key <- paste("logger", name, sep='.')
logger.options(update=list(key, logger))
invisible()
}
#' Remove a logger
#'
#' In the event that you no longer wish to have a logger registered,
#' use this function to remove it. Then any references to this
#' logger will inherit the next available logger in the hierarchy.
#'
#' @section Usage:
#' # Remove a logger\cr
#' flog.remove(name)
#'
#' @name flog.remove
#' @param name The logger name to use
#' @author Brian Lee Yung Rowe
#' @keywords data
#' @examples
#' flog.threshold(ERROR, name='my.logger')
#' flog.info("Won't print", name='my.logger')
#' flog.remove('my.logger')
#' flog.info("Will print", name='my.logger')
flog.remove('ROOT') %as% { invisible() }
flog.remove(name) %as%
{
key <- paste("logger", name, sep='.')
logger.options(update=list(key, NULL))
invisible()
}
#' Get and set the threshold for a logger
#'
#' The threshold affects the visibility of a given logger. When a log
#' statement is called, e.g. \code{flog.debug('foo')}, futile.logger
#' compares the threshold of the logger with the level implied in the
#' log command (in this case DEBUG). If the log level is at or higher
#' in priority than the logger threshold, a message will print.
#' Otherwise the command will silently return.
#'
#' @section Usage:
#' # Get the threshold for the given logger\cr
#' flog.threshold(name) \%::\% character : character \cr
#' flog.threshold(name=ROOT)
#'
#' # Set the threshold for the given logger\cr
#' flog.threshold(threshold, name=ROOT)
#'
#' @name flog.threshold
#' @param threshold integer The new threshold for the given logger
#' @param name character The name of the logger
#' @author Brian Lee Yung Rowe
#' @keywords data
#' @examples
#' flog.threshold(ERROR)
#' flog.info("Won't print")
#' flog.threshold(INFO)
#' flog.info("Will print")
# Set the threshold
flog.threshold('TRACE', name='ROOT') %as% flog.threshold(TRACE, name)
flog.threshold('trace', name='ROOT') %as% flog.threshold(TRACE, name)
flog.threshold('DEBUG', name='ROOT') %as% flog.threshold(DEBUG, name)
flog.threshold('debug', name='ROOT') %as% flog.threshold(DEBUG, name)
flog.threshold('INFO', name='ROOT') %as% flog.threshold(INFO, name)
flog.threshold('info', name='ROOT') %as% flog.threshold(INFO, name)
flog.threshold('WARN', name='ROOT') %as% flog.threshold(WARN, name)
flog.threshold('warn', name='ROOT') %as% flog.threshold(WARN, name)
flog.threshold('ERROR', name='ROOT') %as% flog.threshold(ERROR, name)
flog.threshold('error', name='ROOT') %as% flog.threshold(ERROR, name)
flog.threshold('FATAL', name='ROOT') %as% flog.threshold(FATAL, name)
flog.threshold('fatal', name='ROOT') %as% flog.threshold(FATAL, name)
flog.threshold(threshold, name='ROOT') %as%
{
flog.logger(name, threshold=threshold)
invisible()
}
# Get the threshold
flog.threshold(name) %::% character : character
flog.threshold(name='ROOT') %as%
{
logger <- flog.logger(name)
names(logger$threshold)
}
#' Always return the log message
#'
#' Indicate whether the logger will always return the log message
#' despite the threshold.
#'
#' This is a special option to allow the return value of the flog.*
#' logging functions to return the generated log message even if
#' the log level does not exceed the threshold. Note that this
#' minorly impacts performance when enabled. This functionality
#' is separate from the appender, which is still bound to the
#' value of the logger threshold.
#'
#' @section Usage:
#' # Indicate whether the given logger should carp\cr
#' flog.carp(name=ROOT)
#'
#' # Set whether the given logger should carp\cr
#' flog.carp(carp, name=ROOT)
#'
#' @name flog.carp
#' @param carp logical Whether to carp output or not
#' @param name character The name of the logger
#' @author Brian Lee Yung Rowe
#' @keywords data
#' @examples
#' flog.carp(TRUE)
#' x <- flog.debug("Returns this message but won't print")
#' flog.carp(FALSE)
#' y <- flog.debug("Returns nothing and prints nothing")
flog.carp(name) %::% character : logical
flog.carp(name='ROOT') %as%
{
logger <- flog.logger(name)
if (is.null(logger$carp)) FALSE
else logger$carp
}
# Set whether to carp
flog.carp(carp, name='ROOT') %as%
{
flog.logger(name, carp=carp)
invisible()
}
|