1 unstable release
new 0.1.0 | Feb 18, 2025 |
---|
#71 in Value formatting
31KB
449 lines
Trivial
This is intended to be a no-bloat implementation for log. It includes simple defaults while still providing good flexibility for more advanced use cases.
Motivation
The original motivation was logging while running valgrind tests that treat "possibly leaked" as errors.
Unlike many other implementations, this crate intends to have no possible memory leaks (even 'static).
All examples should have All heap blocks were freed -- no leaks are possible
.
Examples
stdout/stderr
One line to get started with sane defaults for std logs (warn and below to stdout, errors to stderr).
fn main() {
trivial_log::init_std(LevelFilter::Trace).unwrap();
error!("An error has occurred, please help!");
}
This will cause messages like these to appear:
[E] - 17 Feb 2025 22:27:20.956 UTC - ThreadId(1) - An error has occurred, please help!
File
A more advanced configuration that includes both stdout and logging to a file
- Info to Error is logged to "mylog.log"
- Trace to Error is logged to stdout.
fn main() {
trivial_log::builder()
.default_format(|builder| {
builder
//
.appender_range(Level::Info, Level::Error, Path::new("mylog.log"))
.appender_range(Level::Trace, Level::Error, |msg: &String| print!("{}", msg))
})
.init()
.unwrap();
error!("An error has occurred, please help!");
}
Stdout+JSON
An "advanced" configuration that shows how to log normal human-readable messages to stdout but shows how to prepare a json object for "processing" (eg to a log server).
/// This serves as an example of how your json entity may look like.
/// You may design this struct in any ways you see fit.
#[derive(Serialize, Deserialize)]
struct LogEntity {
message: String,
level: String,
thread: String,
timestamp: u128,
}
fn main() {
trivial_log::builder()
.default_format(|builder| builder.appender_range(Level::Trace, Level::Error, |msg: &String| print!("{}", msg)))
.format(|now: std::time::SystemTime, record: &log::Record| {
Some(LogEntity {
message: record.args().to_string(),
level: record.level().to_string(),
thread: format!("{:?}", std::thread::current().id()),
timestamp: now.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis(),
})
}, |builder| builder.appender_range(Level::Trace, Level::Error, |msg: &LogEntity| print!("{}", serde_json::to_string(msg).unwrap())))
.init()
.unwrap();
}
Database
An "advanced" configuration, showing how to implement a custom Appender Implementation, by logging into a SQLite database.
Colors
On ANSI terminals you can write colors. This examples uses the ansi_term crate, but you can also create the ansi escape codes manually without any dependencies.
fn main() {
use ansi_term::Color;
trivial_log::builder()
.format(
|_time, rec| {
let prefix = match rec.level() {
Level::Error => Color::Red.paint("E"),
Level::Warn => Color::Yellow.paint("W"),
Level::Info => Color::Green.paint("I"),
Level::Debug => Color::Purple.paint("D"),
Level::Trace => Color::White.paint("T"),
};
Some(format!(
"{}{}{} {}\n",
Color::Blue.paint("["),
prefix,
Color::Blue.paint("]"),
rec.args()
))
},
|builder| builder.appender_filter(LevelFilter::Trace, |msg: &String| print!("{}", msg)),
)
.init()
.ok();
}
Architecture
The logging is split into 2 parts.
- The format function, which processes the log::Record along with a timestamp into an arbitrary struct of your choosing. The default format outputs String.
- The appender, which writes the output of the format function to somewhere (stdout/file/...) if the level of the record matches the level of the appender.
There is no limit for how many format functions or appenders you can use.
Each log always processes all formats and appenders when the log level matches. When a format has multiple appenders, the format fn only gets called once.
Default Appender Implementations
std::io::BufWriter<T> where T: Write + Send
- io errors are ignoredstd::path::Path
- inability to open or create the file will panic! Other io errors are ignored.std::fs::File
- io errors are ignoredstd::sync::mpsc::Sender<T> where T: Send+Clone
- if the receiver dies then this appender becomes a noop.std::sync::mpsc::SyncSender<T> where T: Send+Clone
- if the receiver dies then this appender becomes a noop. The appender only uses the send method to send data
Implementation details
- The appender's and formats can be reconfigured at any time during the application.
- trivial_log does NOT prevent recursive calls inside the appender.
- It's the responsibility of the appender to prevent calls to
log!
from inside the appender that can lead to a stack overflow.
- It's the responsibility of the appender to prevent calls to
- trivial_log does NOT catch panics that occur in the appender.
- Panics are propangated to caller of the
log!
function. Either use panic=abort, or prevent/catch panics in the appender impl as the caller oflog!
is unlikely to expect it to panic.
- Panics are propangated to caller of the
- trivial_log does NOT start any threads.
- If an appender can take a very long time (e.g. logging over a network), it may be a good idea to use a background thread. See the async example for a starting point.
- trivial_log does NOT do any synchronization
- The appender impl has to synchronize to prevent concurrent access to mutable resources (such as a file/stream). This crate uses no unsafe, so the rust compiler will prevent misuse. See Database for an example.
- Your appender will be called concurrently if multiple concurrent threads call log at the same.
It is up to the appender implementation on what to do in this case.
- The default impl for file will acquire an ordinary Mutex in the appender
- The default impl for stdout/stderr will call print! and eprint! macros which guarantee synchronization.
- For no "possibly leaked" in valgrind, you must call
trivial_log::free()
before the process exits. It is memory safe (no UB) to not call this.- as documented in the trivial_log::free() fns documentation, calling this fn will not cause any problems when after it you "accidentally" still call log!. You just won't see those log messages.
- The default format (which you can easily customize) will always output UTC time.
- I understand this may be inconvenient to people that work with only one time zone but for people that have to compare logs from servers in several different time zones this is a godsend!
Dependencies
~1.5MB
~19K SLoC