- Notifications
You must be signed in to change notification settings - Fork95
Introduction to structured logging with slog
slog
separates concepts of creating logging information (eg.info!(log, "aborting operation")
) from actually handling log records ("writing them down somewhere" - eg. to a file). For log record creationslog
providesLogger
s that can form hierarchies. For log record handling the concept ofDrain
s (logging "outputs") is introduced.
Since usually only the end application itself, knows the details on which logs, when, and where should be stored, rootDrain
is usually created somewhere early in themain
function.
slog
's Drains are simplystructs
implementing aDrain
trait.
Let's take a look at the simplestDrain
: aslog::Discard
. It's a drain that simply drops any logging records that are send to it. Because of that, it can never return any error:type Error = Never
.
A more interestingDrain
is aslog::LevelFilter
. It's a generic struct that wraps another drain (D : Drain
), and forwards to it only loggingRecord
s that have logging level equal or higher than a given value. Because the underlying drain can potentially return an error,LevelFilter
will return such error (type Error = D::Error
).
LevelFilter
shows another benefit ofslog
Drain
s - they are composable. Due to Rust generic system, multiple drains can be combined together, and compiled to very efficient code, with endless possibilities for writing newDrain
s providing new features.
A rootDrain
is a drain that never returns any Errors (type Error = Never
). It's an important distinction sinceLogger
s can only be build on top ofDrain
s that can't return an error. Depending on the application requirements, aslog::Fuse
orslog::IgnoreErr
can typically be used to either panic on errors, or ignore them completely. More sophisticated custom strategies like fallback, etc. can easily be implemented, with most useful ones possibly added to standardslog
or one of associated crates.
Libraries typically should not build their own drains, and instead rely onLogger
s provided by the library user. A typicallog
crate-backward-compatibile approach has been documented inslog
-using example library.
Let's take a look at the example drain hierarchy:
let file =File::create("/tmp/myloggingfile").unwrap();let stream = slog_stream::stream(file, slog_json::new().build());let syslog = slog_syslog::unix_3164(slog_syslog::Facility::LOG_DAEMON);let root =Logger::root(Duplicate::new(LevelFilter::new(stream,Level::Info),LevelFilter::new(syslog,Level::Warning),).fuse(),o!());
from andrain-graph.rs example
The graph illustrating the drain-hierarchy created:
Inslog
any logging statement requires aLogger
object.
The firstLogger
created will always have to be a rootLogger
usingslog::Logger::root
. Any otherLogger
objects can be build from the existing ones as it's child, usingslog::Logger::new
.
EachLogger
has a list of key-value pairs associated with it. Each time a childLogger
is created it inherits all the pairs from its parent. These allows building a contextual information, that corresponds to the logical execution-level structure of the application, as opposed to the structure of the code itself.
Eg. An application has it's own context data like:
- time it was build and revision of the code used
- time when it was started
Then inside application there might be different components like:
- web server with information on:
- the ip and port it is listening on
- (multiple) job processing threads each with:
- directory it's working on
A web server handles request from multiple peers, each described by:
- user id
- IP
A job processing handles different jobs described by:
- user id
- file
- type
For each of the above components, typically a newLogger
object would be created, adding more information to the parent-component. So then, when an error case is handled, logging can be just:
error!(job.logger, "write failed"; 'error' => error);
And that would cause application to log a message containing, both the error itself, and information about it's logical runtime context: which file, what type of a job, for which user, and so on.
TBD