Crates.io | nolog |
lib.rs | nolog |
version | 1.0.19 |
source | src |
created_at | 2022-07-08 11:00:03.589853 |
updated_at | 2023-04-10 18:57:23.690088 |
description | Pretty (by default) and easy to use logger. Compile-time level filtering, zero deps. |
homepage | |
repository | https://github.com/vglinka/nolog |
max_upload_size | |
id | 621744 |
size | 109,448 |
Convenient and 'beautiful by default' logger for debugging your programs.
Easy to use, complete documentation is provided on this page. Zero deps.
No unsafe (by #![deny(unsafe_code)]
).
nolog
uses a std::format_args!()
(that avoids heap
allocations) and compile-time level filtering by Cargo features.
If you use the default setup without additional features (like logmod
,
logonly
, logcatch
, tofile
), then in most cases nolog
will only
work on compile-time macros without using functions, methods, if
or loops. In fact it will expand into
the string eprintln!("{}", format_args!("{}{}{}{}"," ", "CRIT⧽", "msg", "[34] src/main.rs"));
.
Nothing extra.logmod
feature).logonly
feature).Х
messages
if an error
or crit
level message was triggered (logcatch
feature).info!("{line_count} lines.");
.key => value
syntax: info!("{server}" => "{ip}");
cargo run --release
.
If you want the log to be enabled in the release build, then use
release
feature: nolog = {version = "*", features = ["release"]}
.debug
level also
turns on the levels above it: info
, warn
, error
, crit
.
Level can be enabled using the console:
cargo run --features debug
or in Cargo.toml
:
nolog = {version = "*", features = ["debug"]}
.
To enable all levels: cargo run --features trace
.stderr
. You can log to a file with
tofile
feature. You may set the buffer size. Automatic flush after
each message will be used. If you want wait for the buffer to fill
or to do it manually with logflush!()
then use no_auto_flush
feature.file
and to stderr
at the same time.[2022-07-10 06:49:33.646361181 UTC]
using a third party library you like. How to add a timestamp.usual
or key-value
):info!(
"{server}" => "{ip}";
"Status" => "{server_check_result}";
);
--features
Cargo.toml
[dependencies]
nolog = { version = "1", features = ["trace"] }
main.rs
#[macro_use]
extern crate nolog;
fn main() {
trace!("line_count: {}", 42);
debug!("line_count: {}", 42);
info!("line_count: {}", 42);
warn!("line_count: {}", 42);
error!("line_count: {}", 42);
crit!("line_count: {}", 42);
}
cargo run
Result:
You can enable more output filtering features in cargo.toml
:
trace
level.Х
messages if an error
or crit
level message was triggered. Allows you to understand what preceded the error.Cargo.toml
[dependencies]
nolog = { version = "1", features = [
"debug",
"logonly",
"logcatch",
"logmod",
]}
In addition, you can customize the appearance settings.
Appearance settings classic
:
Cargo.toml
[dependencies]
nolog = { version = "1", features = [
"debug",
"show_lvl_header_kv",
"indent_ignore_all",
"newline_ignore",
"location_style_classic",
"sep_colon",
]}
Appearance settings classic_plain
:
Cargo.toml
[dependencies]
nolog = { version = "1", features = [
"debug",
"plain",
"show_lvl_header_kv",
"indent_ignore_all",
"newline_ignore",
"location_style_classic",
"sep_colon",
]}
Indentation.
You can specify indentation in the following way:
crit!(->[X,Y,Z] "msg");
X
- Indents.Y
- Add Y
blank lines before message.Z
- Add Z
blank lines after message.All of these arguments are optional:
crit!("msg");
// X
crit!(->[1] "msg");
// X Y
crit!(->[6,1] "msg");
// X Y Z
crit!(->[1,2,3] "msg");
If you want to add blank lines and leave the default indentation:
// X Y
crit!(->[_,1] "msg");
// X Y Z
crit!(->[_,_,2] "msg");
The same works for each message in the chain.
debug!(
->[2] "msg 1";
->[_,1] "msg 2";
"msg 3";
);
Key-values have the additional ability to set indentation not only for the key, but also for the value.
debug!(->[_,1] "The simulation server started successfully.");
debug!(
"{server}" => "{ip}";
"Status" => ->[3] "{server_check_result}";
);
crit!(->[_,1] "The Universe was created with a lifetime of {} days.", universe.len());
This allows you to get nice aligned output if you want.
Read more about indentation.
--features
You need to completely copy the contents of this file to your cargo.toml
.
Then you can write cargo run --features trace,logonly
instead of
cargo run --features nolog/trace,nolog/logonly
See example on GitHub.
Cargo.toml
[dependencies]
nolog = { version = "1", features = [] }
[features]
nolog_setup = []
# example `classic`
#nolog_setup = ["nolog/show_lvl_header_kv", "nolog/indent_ignore_all", "nolog/newline_ignore", "nolog/location_style_classic", "nolog/sep_colon"]
# example `classic_plain`
#nolog_setup = ["nolog/plain", "nolog/show_lvl_header_kv", "nolog/indent_ignore_all", "nolog/newline_ignore", "nolog/location_style_classic", "nolog/sep_colon"]
trace = ["nolog/trace", "nolog_setup"]
debug = ["nolog/debug", "nolog_setup"]
info = ["nolog/info", "nolog_setup"]
warn = ["nolog/warn", "nolog_setup"]
error = ["nolog/error", "nolog_setup"]
crit = ["nolog/crit", "nolog_setup"]
logonly = ["nolog/logonly"]
logcatch = ["nolog/logcatch"]
logmod = ["nolog/logmod"]
The commented lines nolog_setup = ["nolog/show_lvl_h...]
are
the appearance settings (indents, color scheme, styles, etc).
Uncomment one of them to see what happens (don't forget
to remove nolog_setup = []
).
Appearance settings are selected using conditional compilation, so they have a zero cost.
main.rs
#[macro_use]
extern crate nolog;
fn main() {
trace!("line_count: {}", 42);
debug!("line_count: {}", 42);
info!("line_count: {}", 42);
warn!("line_count: {}", 42);
error!("line_count: {}", 42);
crit!("line_count: {}", 42);
}
nolog
has the same syntax as most loggers based on the log
crate.
nolog
extends the log
crate syntax by adding new features.
However, nolog
is not based on log
crate, it just has the same
macro names. This also results in the nolog
having 0 dependencies.
Therefore, switching to nolog
will require minimal changes in the code.
Ok. Now we can use the following command:
cargo run --features trace
Or, for example
# The output will be empty because there are no logonly
# blocks, etc. in the code.
# This is just to demonstrate the use of several features.
cargo run --features trace,logonly,logcatch,logmod
It's the same but noisier
# The output will be empty because there are no logonly
# blocks, etc. in the code.
# This is just to demonstrate the use of several features.
cargo run --features nolog/trace,nolog/logonly,nolog/logcatch,nolog/logmod
Result:
Cargo.toml
#...
[dependencies]
nolog = { version = "1", features = ["tofile"] }
#...
main.rs
use std::fs::OpenOptions;
use std::io::{self, Read};
use std::path::PathBuf;
#[macro_use]
extern crate nolog;
fn main() -> io::Result<()> {
let path = PathBuf::from("log.txt");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
//^^^^^^^ truncate the file to 0 length if it already exists.
//.append(true)
.open(&path)?;
// Initialization
// Don't use macros like `debug!("msg");` before initialization.
logfile!(file);
trace!("Hello from file!");
let mut file = OpenOptions::new()
.read(true)
.open(&path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
println!("\n -- In {path:?} --");
println!("{contents}");
Ok(())
}
Optionally, you can set the buffer size.
// Buffer `std::io::BufWriter` with capacity: 8000 bytes.
logfile!(8000, file);
The default is to automatically flush after each message.
If you want wait for the buffer to fill or to do it manually
with logflush!()
then use no_auto_flush
feature.
Cargo.toml
#...
[dependencies]
nolog = { version = "1", features = ["tofile", "no_auto_flush"] }
#...
Then use logflush!()
to flush the log manually.
main.rs
...
// Initialization
// Don't use macros like `debug!("msg");` before initialization.
logfile!(8000, file);
trace!("Hello from file!");
logflush!();
...
You can add a timestamp like [2022-07-10 06:49:33.646361181 UTC]
using a third party library you like.
For this example, we will use chrono
crate.
Cargo.toml
#...
[dependencies]
nolog = { version = "1", features = [] }
chrono = "0.4"
[features]
custom_leading = ["nolog/custom_leading"]
custom_trailing = ["nolog/custom_trailing"]
custom_before_msg = ["nolog/custom_before_msg"]
custom_after_msg = ["nolog/custom_after_msg"]
nolog_setup = ["custom_leading"]
#...
We have 4 options here:
<TIMESTAMP>CRIT⧽ msg [5] src/main.rs
CRIT⧽ msg [5] src/main.rs<TIMESTAMP>
CRIT⧽ <TIMESTAMP>msg [5] src/main.rs
CRIT⧽ msg<TIMESTAMP> [5] src/main.rs
Log entry structure:
usual:
<indents><custom_leading><lvlheader><sep><custom_before_msg><msg><custom_after_msg><location><custom_trailing>
key-value:
<indents><custom_leading><lvlheader><sep_kv><custom_before_msg><key><sep_key><value_indent><value><custom_after_msg><location><custom_trailing>
Here is an example:
main.rs
#[macro_use]
extern crate nolog;
#[macro_use]
pub mod logger_setup {
#[macro_export]
#[cfg(feature = "custom_leading")] macro_rules!
// ^^^^^^^^^^^^^^
custom_leading {
// usual
( $level:tt, $indent:expr, $($msg:expr),* ) => {
format_args!("[{}] ", chrono::Utc::now())
};
// key-value
( $level:tt, $indent:expr, $($key:expr),* => $($value:expr),* ) => {
format_args!("[{}] ", chrono::Local::now())
};
}
}
mod other {
pub fn from_other_mod() -> () {
crit!(->[0] "Other" => "Hello from other mod! This is key-value msg.");
}
}
fn main() {
crit!("Hello from main! This is usual msg.");
other::from_other_mod();
}
Output:
[2022-09-07 09:22:09.150921578 UTC] CRIT⧽ Hello from main! This is usual msg. [34] src/main.rs
[2022-09-07 12:22:09.150973037 +03:00] Other⧽ Hello from other mod! This is key-value msg. [29] src/main.rs
With classic
style:
[2022-09-07 09:29:45.859185734 UTC] CRIT: Hello from main! This is usual msg. [src/main.rs 34:5]
[2022-09-07 12:29:45.859225186 +03:00] CRIT: Other: Hello from other mod! This is key-value msg. [src/main.rs 29:9]
nolog_setup = []
classic
nolog_setup = ["nolog/show_lvl_header_kv", "nolog/indent_ignore_all", "nolog/newline_ignore", "nolog/location_style_classic", "nolog/sep_colon"]
classic_plain
nolog_setup = ["nolog/plain", "nolog/show_lvl_header_kv", "nolog/indent_ignore_all", "nolog/newline_ignore", "nolog/location_style_classic", "nolog/sep_colon"]
Messages in a chain should all be of the same type: usual
or key-value
ususal
debug!(
"Planet {name} thinks...";
"Planet {name} thinks...";
);
key-value
debug!(
"{server}" => "{ip}";
"Status" => "{server_check_result}";
);
Add it as early as possible in the code:
logmod!(
[ ] main,
[!=] crate::other2,
);
[]
- Include a module and all its submodules.[=]
- the same (Include a module and all its submodules).[!]
- Exclude a module and all its submodules.[==]
- Include only this module without submodules.[!=]
- Exclude only this module without submodules.Then
cargo run --features trace,logmod
This is useful for debugging to get messages from just a small piece of code.
logonly!(
let universe = [0;3];
crit!("The Universe was created with a lifetime of {} days.", universe.len());
error!("Uncontrolled evolutionary processes have begun on the planet {planet_name}.");
);
Then
cargo run --features trace,logonly
You can use any brackets
logonly!()
, logonly!{}
, logonly![]
.
You can use multiple logonly!()
blocks. Messages will be displayed from all.
It won't break your code when the logger turns off in release build. So you can leave these blocks in the code.
When disabled, the definition of this macro will be replaced with the following:
logonly { ( $($a:tt)* ) => { $($a)* }; }
It simply writes down the code it received.
Hide all messages, show the previous Х
messages if an error
or crit
level message was triggered.
By default X=10
. You can change this anywhere in the code.
// This will take effect for the code below.
logcatch!(2); // now X=2
To enable this feature, use:
cargo run --features trace,logcatch
Each new line created with newline!()
or ->[_,1,1]
(about
what it will be below) counts as a separate message.
You can disable individual messages without removing them from the code.
A macro like debug!([_]; "msg")
will expand into an empty tuple ()
.
// on
info!([#]; "New {name} on planet {planet_name}.");
// off
info!([_]; "{repr}" => "{name} says: {speech}");
You can use any options you like:
On:
[#]
, [x]
, [v]
, [+]
, [on]
, [true]
, [your_var]
Off:
[ ]
, [_]
, [-]
, [off]
, [false]
, [your_var]
your_var
should be bool
.
To change states, you need to change only one character:
[_]
--> [#]
.
This also works with chained messages, but disables the entire chain. You can't turn off a single message in a chain.
crit!([_];
"The answer is {answer}.";
"Planet {planet_name} started watching TV.";
);
You can turn off the action of block logonly
. This will not affect
the code, the effect is as if macro logonly
was not in this place.
logonly!{[_];
crit!("The answer is {answer}.");
let x = 42;
}
This way you can leave logonly!()
in the code and if it is required
in the future just enable it.
If necessary, you can control messages using variables and expressions.
let my_log_enabled = true;
crit!([my_log_enabled]; "The planet {} has been destroyed.", self.name);
let status = "ok";
crit!([(status == "ok")]; "The planet {} has been destroyed.", self.name);
// ^ ^
// Add parentheses
fn is_message_show_fn () -> bool { false }
...
crit!([(is_message_show_fn())]; "The planet {} has been destroyed.", self.name);
// ^ ^
// Add parentheses
newline!(2);
- It will simply write the passed number of new lines to the log.
Indents are of several types:
Base indent will be added to every line.
6
indents. One indent equals one space.You can change base indent
with cargo features:
indent_base_zero
indent_base_one
indent_base_two
indent_base_three
indent_base_four
indent_base_five
indent_base_seven
indent_base_eight
indent_base_nine
indent_base_ten
For example in Cargo.toml
:
nolog_setup = ["nolog/indent_base_zero"]
trace = ["nolog/trace", "nolog_setup"]
usual
: 0key-value
: 6The default indentation is used if no value has been provided by the user.
You can specify indentation in the following way:
crit!(->[X,Y,Z] "msg");
X
- Indents.Y
- Add Y
blank lines before message (same effect as newline!(Y)
).Z
- Add Z
blank lines after message.All of these arguments are optional:
crit!("msg");
crit!(->[1] "msg");
crit!(->[6,1] "msg");
crit!(->[1,2,3] "msg");
crit!([#]; ->[1,2,3] "msg");
If you want to add blank lines and leave the default indentation:
crit!(->[_,1] "msg");
crit!(->[_,_,2] "msg");
The same works for each message in the chain.
debug!(
->[2] "Planet {name} thinks...";
->[_,1] "Planet {name} thinks...";
"Planet {name} thinks...";
);
key => value
have an indentation of 6
by default, but you can reset
it by setting it to zero.
error!(->[0] "{name}" => "{}!! Oh, yeaaaah!", 2*3*7);
Or you can do it via Cargo.toml
for all messages.
indent_kv_default_zero
indent_kv_default_one
indent_kv_default_two
indent_kv_default_three
indent_kv_default_four
indent_kv_default_five
indent_kv_default_seven
indent_kv_default_eight
indent_kv_default_nine
indent_kv_default_ten
For example in Cargo.toml
:
nolog_setup = ["nolog/indent_kv_default_zero"]
trace = ["nolog/trace", "nolog_setup"]
Key-values have the additional ability to set indentation not only for the key, but also for the value.
debug!(
"{server}" => "{ip}";
"Status" => ->[3] "{server_check_result}";
);
This allows you to get nice aligned output if you want.
You can use variables to set the indentation and add blank lines.
for i in 0..2 {
warn!(->[i,i,i] "msg");
}
Ignore all types of indentation.
nolog_setup = ["nolog/indent_ignore_all"]
nolog_setup = ["nolog/newline_ignore"]
nolog
colored by default, use this feature for plain output:
nolog_setup = ["nolog/plain"]
nolog_setup = ["nolog/show_lvl_header_kv"]
Show level name for key-value:
CRIT: Key: value [src/main.rs 90:5]`
^^^^
It's disabled by default:
Key: value [src/main.rs 90:5]
nolog = { version = "1", features = ["release"] }
Don't show location (like [src/main.rs 155:9]
)
nolog_setup = ["nolog/location_hide"]
Style like this: [src/main.rs 155:9]
nolog_setup = ["nolog/location_style_classic"]
Default = "⧽ "
nolog_setup = ["nolog/sep_colon"]
nolog_setup = ["nolog/sep_space"]
nolog_setup = ["nolog/sep_hide"]
You can create your own color scheme for the logger.
Cargo.toml
#...
[dependencies]
nolog = { version = "1", features = [] }
[features]
custom_colors = ["nolog/custom_colors"]
nolog_setup = ["custom_colors"]
#...
Here is an example:
main.rs
#[macro_use]
extern crate nolog;
#[macro_use]
pub mod logger_setup {
#[macro_export]
#[cfg(feature = "custom_colors")] macro_rules!
// ^^^^^^^^^^^^^
color {
( [trace] ) => { "\x1B[34m" };
( [debug] ) => { "\x1B[36m" };
( [info] ) => { "\x1B[32m" };
( [warn] ) => { "\x1B[33m" };
( [error] ) => { "\x1B[31m" };
( [crit] ) => { "\x1B[35m" };
( [sep] ) => { "\x1B[1m\x1B[2m" }; // +bold +dim
( [msg] ) => { "" }; // default term font color
( [from] ) => { "\x1B[90m\x1B[3m" }; // `[src/main.rs 101:5]` in `location_style_classic`
( [sep2] ) => { "\x1B[90m\x1B[2m" }; // sep2 in default style
( [sep3] ) => { "\x1B[90m\x1B[2m" }; // sep3 in default style
( [line] ) => { "\x1B[38;5;67m\x1B[1m\x1B[2m" }; // line number in default style
( [key] ) => { "\x1B[3m\x1B[1m" }; // +italic +bold
( [value] ) => { "\x1B[3m" }; // +italic
( [rm] ) => { "\x1B[0m" }; // remove previous colors
}
}
mod other {
pub fn from_other_mod() -> () {
crit!(->[0] "Other" => "Hello from other mod! This is key-value msg.");
}
}
fn main(){
crit!("Hello from main! This is usual msg.");
other::from_other_mod();
}
It is possible to redirect output. For example, we will redirect output to stderr and to a file at the same time. The limitation is that output to stderr will be the same as to a file, it will not be colorized.
Cargo.toml
#...
[dependencies]
nolog = { version = "1", features = [] }
[features]
nolog_setup = [
"custom_writelog_inner",
"nolog/tofile"
]
custom_writelog_inner = ["nolog/custom_writelog_inner"]
#...
Here is an example:
main.rs
use std::fs::OpenOptions;
use std::io::{self, Read};
use std::path::PathBuf;
#[macro_use]
extern crate nolog;
// use `cargo run --features trace`
#[macro_use]
pub mod logger_setup {
#[macro_export]
#[cfg(feature = "custom_writelog_inner")] macro_rules!
writelog_inner { ( $msg:expr ) => {
eprintln!("{}", $msg); // write to stderr
tofile_writelog_inner_helper!($msg); // write to a file
}
}
}
mod other {
pub fn from_other_mod() -> () {
crit!(->[0] "Other" => "Hello from other mod! This is key-value msg.");
}
}
fn main() -> io::Result<()> {
let path = PathBuf::from("log.txt");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
//^^^^^^^ truncate the file to 0 length if it already exists.
//.append(true)
.open(&path)?;
// Initialization
// Don't use macros like `debug!("msg");` before initialization.
logfile!(file);
eprintln!("\n-- From eprintln: --");
crit!("Hello from main! This is usual msg.");
other::from_other_mod();
let mut file = OpenOptions::new()
.read(true)
.open(&path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
println!("\n-- In {path:?} --");
println!("{contents}");
Ok(())
}
Output:
-- From eprintln: --
CRIT: Hello from main! This is usual msg. [main.rs 54:5]
CRIT: Other: Hello from other mod! This is key-value msg. [main.rs 34:9]
-- In "log.txt" --
CRIT: Hello from main! This is usual msg. [main.rs 54:5]
CRIT: Other: Hello from other mod! This is key-value msg. [main.rs 34:9]
nolog
has other customization options not described here, since
it is unlikely that they will be in demand by a wide range of users.
Their use is similar to that described above.
You can see the full up-to-date list in
Cargo.toml.
Logging in tests works exactly the same, except that Rust test programs hide standard output of successful tests.
Use the following code to see the output of successful tests.
cargo test --features trace -- --nocapture
The output of failed tests will be displayed anyway.
cargo test --features trace