Rust has flexible type system and metaprogramming capabilities, allowing to build both efficient and highly reusable log system. The idea is very similar to serde
and is introduced in a widely used log
, slog
and tracing
crates.
log
crate represents a single unified frontend interface (facade) which is used by all libraries at the same time, but is backed by one actual backend implementation on your choice. This allows to control all the logs (of application and its dependencies) from a single place and in a unified manner: opt-in and opt-out logs of libraries, separate logs by destinations, etc.
- Libraries should link only to the
log
crate, and use the provided macros to log whatever information will be useful to downstream consumers.- Executables should choose a logger implementation and initialize it early in the runtime of the program. Logging implementations will typically include a function to do this.
One interesting part is that log levels can be disabled at compile time, thus have no runtime performance impact at all, unless you're debugging.
For better understanding and familiarity with log
's design, concepts, usage, and features, read through the following articles:
For structured logging there is the excellent slog
crate in Rust ecosystem.
The ambition is to be The Logging Library for Rust.
slog
should accommodate a variety of logging features and requirements. If there is a feature that you need and standardlog
crate is missing,slog
should have it.
It's backward and forward compatible with log
crate, extending its ideas and is baked with an excellent performance.
For better understanding and familiarity with slog
's design, concepts, usage, and features, read through the following articles:
The famous tracing
crate is fabulous at both tracing and structured logging.
tracing
expands upon logging-style diagnostics by allowing libraries and applications to record structured events with additional information about temporality and causality — unlike a log message, a span intracing
has a beginning and end time, may be entered and exited by the flow of execution, and may exist within a nested tree of similar spans. In addition,tracing
spans are structured, with the ability to record typed data as well as textual messages.
Its "killer feature", undoubtedly, is spans functionality, so people tend to prefer it over slog
even for usual logging. It's also backward and forward compatible with log
crate.
Speaking of tracing, the tracing
crate has good integrations with OpenTelemetry-compatible distributed tracing systems (and similar ones). All this allows to reuse the same solution both for logging, tracing (like Jaeger, Zipkin), profiling (like coz, Tracy), error reporting (like Sentry), etc.
For better understanding and familiarity with tracing
's design, concepts, usage, and features, read through the following articles:
- Official
tracing
crate docs - Yoav Danieli: Guide to OpenTelemetry Distributed Tracing in Rust
- Tokio Blog: Diagnostics with Tracing
Estimated time: 1 day
Implement two loggers:
- Global main
app.log
logger which prints all its logs toSTDOUT
, butWARN
level (and higher) logs toSTDERR
. - Local
access.log
logger which writes all its logs toaccess.log
file.
All logs should be structured and logged in a JSON format, and have time field with nanoseconds (RFC 3339 formatted).
Examples:
{"lvl":"ERROR","file":"app.log","time":"2018-07-30T12:14:14.196483657Z","msg":"Error occurred"}
{"lvl":"INFO","file":"access.log","time":"2018-07-30T12:17:18.721127239Z","msg":"http","method":"POST","path":"/some"}
After completing everything above, you should be able to answer (and understand why) the following questions:
- How does
log
crate achieve its reusability over ecosystem? What are the ideas behind it? - Why logging is preferred over printing (
println!
usage)? When it's not? - What is structured logging? What benefits does it provide?
- Why
tracing
crate is good for logging? What makes it preferred overslog
andlog
crates? - What is tracing? Why is it beneficial for observability?