A Logger produces a LogEvent that contains a log message along with
metadata (timestamp, calling function, ...) and dispatches it to one or
more Appenders which are responsible for the output (console, file, ...)
of the event. lgr comes with a single pre-configured Logger called the
root Logger
that can be accessed via lgr$<...>
. Instantiation of new
Loggers is done with get_logger()
. It is advisable to instantiate a
separate Logger with a descriptive name for each package/script in which
you use lgr.
If you are a package developer you should define a new Logger for each package, but you do not need to configure it. The user of the package should decide how and where to output logging, usually by configuring the root Logger (new Appenders added/removed, Layouts modified, etc...).
lgr::Filterable
-> Logger
name
A character
scalar. The unique name of each logger,
which also includes the names of its ancestors (separated by /
).
threshold
integer
scalar. The threshold of the Logger
, or if it
NULL
the threshold it inherits from its closest ancestor with a
non-NULL
threshold
propagate
A TRUE
or FALSE
. The unique name of each logger,
which also includes the names of its ancestors (separated by /
).
ancestry
A named logical
vector of containing the propagate value
of each Logger upper the inheritance tree. The names are the names of
the appenders. ancestry
is an S3 class with a custom
format()
/print()
method, so if you want to use the plain logical
vector use unclass(lg$ancestry)
parent
a Logger
. The direct ancestor of the Logger
.
last_event
The last LogEvent produced by the current Logger
appenders
a list
of all Appenders of the Logger
inherited_appenders
A list
of all appenders that the Logger
inherits from its ancestors
exception_handler
a function
. See $set_exception_handler
and
$handle_exception
new()
Loggers should never be instantiated directly with Logger$new()
but
rather via get_logger("name")
. This way new Loggers are
registered in a global namespace which ensures uniqueness and
facilitates inheritance between Loggers. If "name"
does not exist, a
new Logger with that name will be created, otherwise the function returns
a Reference to the existing Logger.
name
is potentially a "/"
separated hierarchical value like
foo/bar/baz
. Loggers further down the hierarchy are descendants of the
loggers above and (by default) inherit threshold
and Appenders
from
their ancestors.
log()
Log an event.
If level
passes the Logger's threshold
a new LogEvent with level
,
msg
, timestamp
and caller
is created. If the new LogEvent also
passes the Loggers Filters, it is be dispatched to the
relevant Appenders.
Logger$log(level, msg, ..., timestamp = Sys.time(), caller = get_caller(-7))
level
a character
or integer
scalar. See log_levels.
msg
character
. A log message. If unnamed arguments are supplied
in ...
, msg
is passed on to base::sprintf()
(which means "%"
have
to be escaped), otherwise msg
is left as-is.
...
unnamed arguments in ...
must be character
scalars and
are passed to base::sprintf()
. Named arguments must have unique names
but can be arbitrary R objects that are passed to LogEvent$new()
and
will be turned into custom fields.
timestamp
POSIXct. Timestamp of the event.
caller
a character
scalar. The name of the calling function.
list_log()
list_log()
is a shortcut for do.call(Logger$log, x)
.
See https://github.com/s-fleck/joblog for an R package that
leverages this feature to create custom log event types for tracking
the status of cron jobs.
lg <- get_logger("test")
lg$list_log(list(level = 400, msg = "example"))
config()
Load a Logger configuration.
cfg
a special list
object with any or all of the the following elements:
appenders
, threshold
, filters
, propagate
, exception_handler
,
the path to a YAML
/JSON
config file,
a character
scalar containing YAML/JSON
,
NULL
(to reset the logger config to the default/unconfigured state)
file, text, list
can be used as an alternative to
cfg
that enforces that the supplied argument is of the specified
type. See logger_config for details.
add_appender()
Add an Appender to the Logger
appender
a single Appender
name
a character
scalar. Optional but recommended.
lg <- get_logger("test")
lg$add_appender(AppenderConsole$new(), name = "myconsole")
lg$appenders[[1]]
lg$appenders$myconsole
lg$remove_appender("myconsole")
lg$config(NULL) # reset config
remove_appender()
remove an appender
handle_exception()
To prevent errors in the logging logic from crashing the whole script,
Loggers pass errors they encounter to an exception handler. The default
behaviour is to demote errors to warnings. See also
set_exception_handler()
.
set_exception_handler()
Set the exception handler of a logger
fun
a function
with the single argument e
(an error condition)
lgr$info(stop("this produces a warning instead of an error"))
set_propagate()
Should a Logger propagate events to the Appenders of its ancestors?
x
TRUE
or FALSE
. Should LogEvents be passed on to the appenders
of the ancestral Loggers?
set_threshold()
Set the minimum log level of events that a Logger should process
level
character
or integer
scalar. The minimum
log level that triggers this Logger
set_appenders()
Set the Logger's Appenders
x
single Appender or a list
thereof. Appenders control the
output of a Logger. Be aware that a Logger also inherits the Appenders
of its ancestors (see vignette("lgr", package = "lgr")
for more info
about Logger inheritance).
spawn()
Spawn a child Logger.
This is very similar to using get_logger()
, but
can be useful in some cases where Loggers are created programmatically
# lgr::lgr is the root logger that is always available
lgr$info("Today is a good day")
#> INFO [20:04:32.282] Today is a good day
lgr$fatal("This is a serious error")
#> FATAL [20:04:32.285] This is a serious error
# Loggers use sprintf() for string formatting by default
lgr$info("Today is %s", Sys.Date() )
#> INFO [20:04:32.288] Today is 2023-03-04
# If no unnamed `...` are present, msg is not passed through sprintf()
lgr$fatal("100% bad") # so this works
#> FATAL [20:04:32.290] 100% bad
lgr$fatal("%s%% bad", 100) # if you use unnamed arguments, you must escape %
#> FATAL [20:04:32.293] 100% bad
# You can create new loggers with get_logger()
tf <- tempfile()
lg <- get_logger("mylogger")$set_appenders(AppenderFile$new(tf))
# The new logger passes the log message on to the appenders of its parent
# logger, which is by default the root logger. This is why the following
# writes not only the file 'tf', but also to the console.
lg$fatal("blubb")
#> FATAL [20:04:32.302] blubb
readLines(tf)
#> [1] "FATAL [2023-03-04 20:04:32.302] blubb"
# This logger's print() method depicts this relationship.
child <- get_logger("lg/child")
print(child)
#> <Logger> [info] lg/child
#>
#> inherited appenders:
#> console: <AppenderConsole> [all] -> console
print(child$name)
#> [1] "lg/child"
# use formatting strings and custom fields
tf2 <- tempfile()
lg$add_appender(AppenderFile$new(tf2, layout = LayoutJson$new()))
lg$info("Not all %s support custom fields", "appenders", type = "test")
#> INFO [20:04:32.320] Not all appenders support custom fields {type: `test`}
cat(readLines(tf), sep = "\n")
#> FATAL [2023-03-04 20:04:32.302] blubb
#> INFO [2023-03-04 20:04:32.320] Not all appenders support custom fields {"type":"test"}
cat(readLines(tf2), sep = "\n")
#> {"level":400,"timestamp":"2023-03-04 20:04:32","logger":"mylogger","caller":"eval","msg":"Not all appenders support custom fields","type":"test"}
# cleanup
unlink(c(tf, tf2))
lg$config(NULL) # reset logger config
#> <Logger> [info] mylogger
#>
#> inherited appenders:
#> console: <AppenderConsole> [all] -> console
# LoggerGlue
# You can also create a new logger that uses the awesome glue library for
# string formatting instead of sprintf
if (requireNamespace("glue")){
lg <- get_logger_glue("glue")
lg$fatal("blah ", "fizz is set to: {fizz}", foo = "bar", fizz = "buzz")
# prevent creation of custom fields with prefixing a dot
lg$fatal("blah ", "fizz is set to: {.fizz}", foo = "bar", .fizz = "buzz")
#' # completely reset 'glue' to an unconfigured vanilla Logger
get_logger("glue", reset = TRUE)
}
#> FATAL [20:04:32.340] blah fizz is set to: buzz {foo: `bar`, fizz: `buzz`}
#> FATAL [20:04:32.393] blah fizz is set to: buzz {foo: `bar`}
#> <Logger> [info] glue
#>
#> inherited appenders:
#> console: <AppenderConsole> [all] -> console
# Configuring a Logger
lg <- get_logger("test")
lg$config(NULL) # resets logger to unconfigured state
#> <Logger> [info] test
#>
#> inherited appenders:
#> console: <AppenderConsole> [all] -> console
# With setters
lg$
set_threshold("error")$
set_propagate(FALSE)$
set_appenders(AppenderConsole$new(threshold = "info"))
lg$config(NULL)
#> <Logger> [info] test
#>
#> inherited appenders:
#> console: <AppenderConsole> [all] -> console
# With a list
lg$config(list(
threshold = "error",
propagate = FALSE,
appenders = list(AppenderConsole$new(threshold = "info"))
))
#> <Logger> [error] test
#>
#> appenders:
#> [[1]]: <AppenderConsole> [info] -> console
lg$config(NULL) # resets logger to unconfigured state
#> <Logger> [info] test
#>
#> inherited appenders:
#> console: <AppenderConsole> [all] -> console
# Via YAML
cfg <- "
Logger:
threshold: error
propagate: false
appenders:
AppenderConsole:
threshold: info
"
lg$config(cfg)
#> <Logger> [error] test
#>
#> appenders:
#> AppenderConsole: <AppenderConsole> [info] -> console
lg$config(NULL)
#> <Logger> [info] test
#>
#> inherited appenders:
#> console: <AppenderConsole> [all] -> console
## ------------------------------------------------
## Method `Logger$list_log`
## ------------------------------------------------
lg <- get_logger("test")
lg$list_log(list(level = 400, msg = "example"))
#> INFO [20:04:32.428] example
## ------------------------------------------------
## Method `Logger$add_appender`
## ------------------------------------------------
lg <- get_logger("test")
lg$add_appender(AppenderConsole$new(), name = "myconsole")
lg$appenders[[1]]
#> <AppenderConsole> [all]
#> layout: <LayoutFormat> %L [%t] %m %f
#> destination: console
lg$appenders$myconsole
#> <AppenderConsole> [all]
#> layout: <LayoutFormat> %L [%t] %m %f
#> destination: console
lg$remove_appender("myconsole")
lg$config(NULL) # reset config
#> <Logger> [info] test
#>
#> inherited appenders:
#> console: <AppenderConsole> [all] -> console
## ------------------------------------------------
## Method `Logger$set_exception_handler`
## ------------------------------------------------
lgr$info(stop("this produces a warning instead of an error"))
#> Warning: [2023-03-04 20:04:32.458] root ~ error in `lgr$info(stop("this produces a warning instead of an error"))`: this produces a warning instead of an error