quicklog
is a fast single-threaded logging library. It supports standard logging macros such as trace!
, debug!
, info!
, warn!
and error!
, similar to the API exposed by the log
crate. One key difference is the ability to opt-in much faster, low-latency logging.
Add this to your Cargo.toml
:
[dependencies]
quicklog = "0.3"
quicklog
is built against Rust 1.72.0. Support for older Rust versions is not guaranteed, and it is encouraged that users stick to this or newer versions.
One of the goals of quicklog
is to provide basic logging functionality similar to log
or tracing
's API. The five core logging macros are supported: trace!
, debug!
, info!
, warn!
and error!
. Structured logging and named parameters are also supported:
use quicklog::{info, init};
fn main() {
// initialize required resources. by default, all logs are
// flushed to stdout.
init!();
// basic usage
info!("Simple format string without arguments");
info!("Format string with arguments: {:?} {}", "hello world", 123);
// structured fields -- follows similar rules to `tracing`.
info!(field_a = 123, field_b = "some text", "Structured fields: {:?}", 99);
info!(field_a = ?vec![1, 2, 3], field_b = %123, "Structured fields with sigils");
// named parameters
let some_var = 10;
info!("Explicit capture of some_var: {some_var}", some_var = some_var);
info!("Implicit capture of some_var: {some_var}");
// flushes everything in queue
while let Ok(()) = flush!() {}
}
As seen in the example, one key step is the need to call flush!
, which will output a single log entry to stdout by default. This is intentional to avoid potentially expensive I/O operations on performance-critical paths. Also note some differences with tracing
for basic logging usage.
quicklog
provides a Serialize
trait which is used to opt into fast logging. Applications looking to speed up logging should look to derive a Serialize
implementation for user-defined types, or provide a manual implementation (see the serialize example for a more comprehensively documented tutorial).
After implementing Serialize
for user-defined types, there are two ways to enable quicklog
to use them:
- Place the argument before the format string, as part of the structured fields (no prefix sigil is needed, unlike
?
and%
).quicklog
will automatically try to use theSerialize
implementation for an argument placed in this position. - Use the
{:^}
formatting specifier in the format string, similar to how{:?}
and{}
are used for arguments implementing theDebug
andDisplay
traits respectively.
use quicklog::{flush, info, init, Serialize};
// derive `Serialize` macro
#[derive(Serialize)]
struct Foo {
a: usize,
b: String,
c: Vec<&'static str>
}
fn ma
BF5E
in() {
let s = Foo {
a: 1000,
b: "hello".to_string(),
c: vec!["a", "b", "c"]
};
init!();
// fast -- uses `Serialize`
info!(s, "fast logging, using Serialize");
// structured field
info!(serialize_struct = s, "fast logging, using Serialize");
// format specifier
info!("fast logging, using Serialize: serialize_struct={:^}", s);
// `quicklog` provides default implementations of `Serialize` for
// certain common data types
info!(a = 1, b = 2, c = "hello world".to_string(), "fast logging, using default Serialize");
while let Ok(()) = flush!() {}
}
Due to some constraints, mixing of Serialize
and Debug
/Display
format specifiers in the format string is prohibited. For instance, this will fail to compile:
// mixing {:^} with {:?} or {} not allowed!
info!("hello world {:^} {:?} {}", 1, 2, 3);
However, one can mix-and-match these arguments in the structured fields, for example:
info!(debug = ?some_debug_struct, display = %some_display_struct, serialize = serialize_struct, "serialize args in fmt str: {:^} {:^}", 3, 5);
In general, for best performance, try to avoid mixing Serialize
and non-Serialize
arguments in each logging call. For instance, try to ensure that on performance-critical paths, every logging argument implements Serialize
:
info!(a = 1, b = "hello world", c = 930.123, "Some message: {:^}", some_serialize_struct);
More usage examples are available here. Some notable ones are:
macros
: More comprehensive example of the syntax accepted by our logging macros.serialize
: Example on implementingSerialize
, our core trait. Having a manualSerialize
implementation can be useful at times, usually when some information about the user-defined type can be exploited to squeeze out slightly more performance.
Some other potentially useful features supported include:
- Customizing log output location and format
- Compile-time log filtering
- JSON logging
- Deferred logging
- Configuration of max logging capacity
For these advanced features, please refer to the latest crate documentation for full details.
Benchmarks were run on an M1 Pro (2021), 16GB RAM setup.
quicklog::info!(a = 1u32, b = 2u32, c = 3u32, "Some data:")
Serialize/3x4B time: [9.5996 ns 9.6222 ns 9.6417 ns]
quicklog::info!(a = 1u32, b = 2u32, c = "The quick brown fox jumps over the lazy dog", "Some data:")
Serialize/2x4B + string time: [10.688 ns 10.701 ns 10.715 ns]
Serialize/64B time: [10.706 ns 10.717 ns 10.730 ns]
Serialize/128B time: [10.889 ns 10.919 ns 10.961 ns]
Serialize/256B time: [13.113 ns 13.171 ns 13.239 ns]
Serialize/512B time: [19.125 ns 19.509 ns 19.931 ns]
Serialize/1024B time: [29.335 ns 29.377 ns 29.414 ns]
Serialize/4KB: time: [96.089 ns 96.186 ns 96.316 ns]
tracing/4KB: time: [19.677 µs 19.727 µs 19.776 µs]
delog/4KB: time: [19.658 µs 19.693 µs 19.734 µs]
Full benchmarks can be found in the benchmarks folder.
quicklog
is still in heavy development and lacks many features supported by e.g. tracing
, arguably the de facto crate for logging. For instance, quicklog
currently lacks support for:
- Named targets within the logging macro, e.g.
info!(target: "my_context", ...)
. - Spans and logging in asynchronous contexts.
- Integration with certain third-party crates, e.g. through
tracing-subscriber
.
On the whole, it would be good to consider if the extra performance provided by quicklog
is worth missing out on these features. If these features are important to you, tracing
and other similar options would be great! Otherwise, quicklog
aims to still provide the basic logging functionality of these crates while providing the ability to vastly improve logging latency on an opt-in basis.
Please post your bug reports or feature requests on Github Issues.
- [] Multi-threaded implementation for logging in background thread
- [] Review uses of unsafe code
- [] Benchmark multi-threaded performance
We are open for contributions!
Zack Ng, Tien Dat Nguyen, Michiel van Slobbe, Dheeraj Oswal
Copyright 2023 Grasshopper Asia
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.