Expand description
An asynchronous single-threaded logger where formatting and I/O are deferred at callsite.
Overview
Quicklog
is provides a framework for logging where it allows for deferred
deferred formatting and deferred I/O of logging, which should in turn provide
more performant logging with low callsite latency.
Deferred Formatting
Why?
Formatting a struct
into a String
requires the overhead of serialization.
Deferring the serialization of a struct can be avoided by cloning / copying
the struct at a point in time, and saving that onto a queue.
Later at the flush site, this struct is serialized into a string when I/O is going to be performed.
Deferred I/O
Why?
Deferring the I/O of formatting would allow for low callsite latency and allow a user to implement their own flush site, possibly on a separate thread
Usage
init!()
macro needs to be called to initialize the logger before we can
start logging, probably near the entry point of your application.
Example Usage
fn main() {
init!();
// log some stuff
info!("hello world! {}", "some argument");
// flush on separate thread
thread::spawn(|| {
loop {
flush!();
}
});
}
Macros
Shorthand Macros
Quicklog allows a number of macros with 5 different levels of verbosity. These
wrap around log!
with the corresponding levels.
Internally, these shorthands call try_log!
with their respective levels.
Setup Macros
Quicklog allows a user specified Clock
or Flush
to be implemented by
the user. This can be passed in through these macros, as long as the
underlying struct implements the correct traits
with_clock!
: Specify the Clock Quicklog useswith_flush!
: Specify the Flusher Quicklog useswith_flush_into_file
: Specify path to flush log lines into
Macro prefix for partial serialization
To speed things up, if you are logging a large struct, there could be some small things
you might not want to log. This functionality can be done through implementing the
Serialize
trait, where you can implement how to copy which parts of the struct.
This could additionally be helpful if you already have the struct inside a buffer in byte form, as you could simply pass the buffer directly into the decode fn, eliminiating any need to copy.
struct SomeStruct {
num: i64
}
impl Serialize for SomeStruct {
fn encode(&self, write_buf: &'static mut [u8]) -> Store { /* some impl */ }
fn buffer_size_required(&self) -> usize { /* some impl */ }
}
fn main() {
let s = SomeStruct { num: 1_000_000 };
info!("some struct: {}", ^s);
}
Macro prefix for eager evaluation
There are two prefixes you can use for variables, %
and ?
. This works the same
way as tracing
, where %
eagerly evaluates an object that implements Display
and ?
eagerly evaluates an object that implements Debug
.
info!("eager display {}; eager debug {}", %impl_display, ?impl_debug);
// logically expands into:
// info!(
// "eager display {}; eager debug {}",
// format!("{}", impl_display),
// format!("{:?}", impl_debug)
// );
Structured fields
Structured fields in log lines can be specified using field_name = field_value
syntax. field_name
can be a literal or a bunch of idents. This can also
be used in combination with %
and ?
prefix on args to eagerly evaluate
expressions into format strings.
info!("hello world {} {} {}", question.tricky = true, question.answer = ?value, question.val = &value);
// output: "hello world question.tricky=true question.answer=10 question.val=10"
Environment variables
There are two environment variables you can set:
QUICKLOG_MAX_LOGGER_CAPACITY
- sets the size of the spsc ring buffer used for logging
QUICKLOG_MAX_SERIALIZE_BUFFER_CAPACITY
- sets the size of the byte buffer used for static serialization
- this can be increased when you run into issues out of memory in debug when conducting load testing
Components
quicklog-clock
Clock
is the trait for a clock that can be used with Quicklog
. Clocks can
be swapped out at runtime with a different implementation.
This swap should be done at the init stage of your application to ensure that timings are consistent.
Example
struct SomeClock;
impl Clock for SomeClock { /* impl methods */ }
fn main() {
init!();
with_clock!(SomeClock::new());
// logger now uses SomeClock for timestamping
info!("Hello, world!");
flush!();
}
quicklog-flush
Flush
is the trait that defines how the log messages would be flushed.
These logs can be printed through using the pre-defined StdoutFlusher
or
saved to a file through the pre-defined FileFlusher
to a specified
location through the string passed in.
Example
fn main() {
init!();
with_flush!(StdoutFlusher);
// uses the StdoutFlusher passed in for flushing
flush!();
}
Modules
constants.rs
is generated frombuild.rs
, should not be modified manually- contains logging levels and filters Defines the levels of verbosity available for logging, as well as LevelFilter.
- contains macros
- contains trait for serialization and pre-generated impl for common types and buffer
Macros
- Debug level log
- Error level log
- Allows flushing onto an implementor of
Flush
, which can be modified withwith_flush!
macro and unwraps and ignores errors fromtry_flush
- Info level log
- Initializes Quicklog by calling
Quicklog::init()
Should only be called once in the application - Calls
try_log
and discards any errors - Trace level log
- Allows flushing onto an implementor of
Flush
, which can be modified withwith_flush!
macro and returnsRecvResult
- Runs log and returns a Result, matches either a literal / or a literal with some arguments which are matched recursively.
- Warn level log
- Used to amend which
Clock
is currently attached toQuicklog
An implementation can be passed in at runtime as long as it adheres to theClock
trait inquicklog-clock
- Used to amend which
Flush
is currently attached toQuicklog
An implementation can be passed in at runtime as long as it adheres to theFlush
trait inquicklog-flush
- Flushes log lines into the file path specified
Structs
- Quicklog implements the Log trait, to provide logging
Enums
- Errors that can be presented when flushing
Constants
- Sets max capacity of logging queue, can be set through env var
QUICKLOG_MAX_LOGGER_CAPACITY
. - Sets max capacity of byte buffer used for serialization with
^
prefix in logging, can be set throughQUICKLOG_MAX_SERIALIZE_BUFFER_CAPACITY
.
Traits
- Log is the base trait that Quicklog will implement. Flushing and formatting is deferred while logging.
Type Definitions
- Consumer side of queue
- Result from trying to pop from logging queue
- Result from pushing onto queue
- Producer side of queue