dpelisek
dpelisek

Reputation: 1041

futile.logger warn to stderr

How could I send all logs with level "warn" to stderr instead of stdout but still keep all "info" and "debug" logs in stdout? My setting looks like this:

library(futile.logger)
options(warn=1)

logFileName <- paste0(outdir, "/", projectID, ".log")
flog.appender(appender.tee(logFileName))
flog.threshold(DEBUG)
flog.carp(TRUE)

Upvotes: 1

Views: 367

Answers (1)

Mikko Marttila
Mikko Marttila

Reputation: 11908

The appender function is responsible for sending the log line to an output destination; You’ll need to define a custom appender.

In futile.logger, the appender only receives the log line, not the log level associated with it. You could try to infer the log level based on the line, and choose the destination based on that. The exact logic to do that will depend on your layout function. With the default option (layout.simple()) that logic could look something like this:

appender.custom <- function(file) {
  function(line) {
    level <- regmatches(line, regexpr("\\w+(?= \\[)", line, perl = TRUE))
    dest <- if (get(level) > WARN) stdout() else stderr()
    cat(line, file = dest, sep = "")
    cat(line, file = file, append = TRUE, sep = "")
  }
}

Then you just swap this custom appender for appender.tee():

library(futile.logger)

logFileName <- tempfile(fileext = ".log")
flog.appender(appender.custom(logFileName))
#> NULL
flog.threshold(DEBUG)
#> NULL

local({
  flog.debug("Debug message.")
  flog.info("Info message.")
  flog.warn("Warning message.")
  flog.error("Error message.")
})
#> DEBUG [2020-11-17 16:52:31] Debug message.
#> INFO [2020-11-17 16:52:31] Info message.

cat(readLines(logFileName), sep = "\n")
#> DEBUG [2020-11-17 16:52:31] Debug message.
#> INFO [2020-11-17 16:52:31] Info message.
#> WARN [2020-11-17 16:52:31] Warning message.
#> ERROR [2020-11-17 16:52:31] Error message.
unlink(logFileName)

Above, the captured output doesn’t include stderr, but here’s a screenshot of what the output looks like in the ineractive console:

debug and info as plain text, warn and err as highlighted messages

Longer term, the way to approach this would probably be a feature request to the package authors to also pass the log level of the message to the appender function.

Alternatively, if you’re not commited to sticking with futile.logger, you could use log4r which does give the level to the appender. You’ll still need a custom appender, but there’s no need to try to parse the level:

library(log4r)
#> 
#> Attaching package: 'log4r'
#> The following object is masked from 'package:base':
#> 
#>     debug

custom_appender <- function(layout = log4r::default_log_layout()) {
  WARN <- log4r::as.loglevel("WARN")
  function(level, ...) {
    msg <- layout(level, ...)
    level <- log4r::as.loglevel(level)
    dest <- if (level >= WARN) stderr() else stdout()
    cat(msg, file = dest, sep = "")
  }
}

log_file <- tempfile(fileext = ".log")
appenders <- list(file_appender(log_file), custom_appender())
logger <- logger("DEBUG", appenders = appenders)

local({
  debug(logger, "Debug message.")
  info(logger, "Info message.")
  warn(logger, "Warning message.")
  error(logger, "Error message.")
})
#> DEBUG [2020-11-17 16:52:31] Debug message.
#> INFO  [2020-11-17 16:52:31] Info message.

cat(readLines(log_file), sep = "\n")
#> DEBUG [2020-11-17 16:52:31] Debug message.
#> INFO  [2020-11-17 16:52:31] Info message.
#> WARN  [2020-11-17 16:52:31] Warning message.
#> ERROR [2020-11-17 16:52:31] Error message.
unlink(log_file)

Upvotes: 2

Related Questions