nolog logger

Convenient and 'beautiful by default' logger for debugging your programs. Easy to use, you don't need to learn anything to start using it. Zero deps. No unsafe (by #![deny(unsafe_code)]).

nolog See this example code.

Using nolog

Cargo.toml

```toml [dependencies] nolog = { version = "1", features = [] }

[features] nolog_setup = []

example classic

nologsetup = ["nolog/showlvlheaderkv", "nolog/indentignoreall", "nolog/newlineignore", "nolog/locationstyleclassic", "nolog/sepcolon"]

example classic_plain

nologsetup = ["nolog/plain", "nolog/showlvlheaderkv", "nolog/indentignoreall", "nolog/newlineignore", "nolog/locationstyleclassic", "nolog/sepcolon"]

trace = ["nolog/trace", "nologsetup"] debug = ["nolog/debug", "nologsetup"] info = ["nolog/info", "nologsetup"] warn = ["nolog/warn", "nologsetup"] error = ["nolog/error", "nologsetup"] crit = ["nolog/crit", "nologsetup"]

logonly = ["nolog/logonly"] logcatch = ["nolog/logcatch"] logmod = ["nolog/logmod"] ```

main.rs

```rust

[macro_use]

extern crate nolog;

fn main() { trace!("linecount: {}", 42); debug!("linecount: {}", 42); info!("linecount: {}", 42); warn!("linecount: {}", 42); error!("linecount: {}", 42); crit!("linecount: {}", 42); } ```

Then:

sh cargo run --features trace

Or, for example

sh cargo run --features trace,logonly,logcatch,logmod

It's the same but noisier

sh cargo run --features nolog/trace,nolog/logonly,nolog/logcatch,nolog/logmod

Result (cargo run --features trace)

example

Tofile. Writing a log to a file

Cargo.toml

toml [dependencies] nolog = { version = "1", features = ["tofile"] }

main.rs

```rust 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.

rust // 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

toml [dependencies] nolog = { version = "1", features = ["tofile", "no_auto_flush"] }

Then use logflush!() to flush the log manually.

main.rs

``rust ... // Initialization // Don't use macros likedebug!("msg");` before initialization. logfile!(8000, file);

trace!("Hello from file!");
logflush!();

... ```

How to add a timestamp

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

```toml [dependencies] nolog = { version = "1", features = [] } chrono = "0.4"

[features] customleading = ["nolog/customleading"] customtrailing = ["nolog/customtrailing"] custombeforemsg = ["nolog/custombeforemsg"] customaftermsg = ["nolog/customaftermsg"]

nologsetup = ["customleading"] ```

We have 4 options here: - "customleading" - <TIMESTAMP>CRIT⧽ msg [5] src/main.rs - "customtrailing" - CRIT⧽ msg [5] src/main.rs<TIMESTAMP> - "custombeforemsg" - CRIT⧽ <TIMESTAMP>msg [5] src/main.rs - "customaftermsg" - 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

```rust

[macro_use]

extern crate nolog;

[macro_use]

pub mod loggersetup { #[macroexport] #[cfg(feature = "customleading")] macrorules! // ^^^^^^^^^^^^^^ custom_leading { // usual ( $level:tt, $indent:expr, $($msg:expr),* ) => { { //let time: DateTime = Local::now(); let time: chrono::DateTime = chrono::Utc::now(); format!("[{}] ", time) } }; // key-value ( $level:tt, $indent:expr, $($key:expr),* => $($value:expr),* ) => { { let time: chrono::DateTime = chrono::Local::now(); //let time: chrono::DateTime = chrono::Utc::now(); format!("[{}] ", time) } }; } }

mod other { pub fn fromothermod() -> () { crit!(->[0] "Other" => "Hello from other mod! This is key-value msg."); } }

fn main(){ crit!("Hello from main! This is usual msg."); other::fromothermod();
} ```

Output:

```sh [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:

sh [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]

Styles

Default

toml nolog_setup = []

classic

toml nolog_setup = ["nolog/show_lvl_header_kv", "nolog/indent_ignore_all", "nolog/newline_ignore", "nolog/location_style_classic", "nolog/sep_colon"]

classic_plain

toml nolog_setup = ["nolog/plain", "nolog/show_lvl_header_kv", "nolog/indent_ignore_all", "nolog/newline_ignore", "nolog/location_style_classic", "nolog/sep_colon"]

styles

Chaining

Messages in a chain should all be of the same type: usual or key-value

ususal

rust debug!( "Planet {name} thinks..."; "Planet {name} thinks..."; );

key-value

rust debug!( "{server}" => "{ip}"; "Status" => "{server_check_result}"; );

Logmod. Filtering by module name

Add it as early as possible in the code:

rust logmod!( [ ] main, [!=] crate::other2, );

Then

cargo run --features trace,logmod

Logonly. Display messages only from a selected section of code

This is useful for debugging to get messages from just a small piece of code.

rust 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:

rust logonly { ( $($a:tt)* ) => { $($a)* }; }

It simply writes down the code it received.

Logcatch. Smart logging

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.

rust // 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.

Quick disable and enable messages

You can disable individual messages without removing them from the code. A macro like debug!([_]; "msg") will expand into an empty tuple ().

```rust // 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.

rust 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.

rust 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.

Variables and expressions

If you need, you can control the display of messages using variables and expressions.

```rust let mylogenabled = true; crit!([mylogenabled]; "The planet {} has been destroyed.", self.name);

let status = "ok"; crit!([(status == "ok")]; "The planet {} has been destroyed.", self.name); // ^ ^ // Add parentheses

fn ismessageshowfn () -> bool { false } ... crit!([(ismessageshowfn())]; "The planet {} has been destroyed.", self.name); // ^ ^ // Add parentheses ```

Indentation and new lines

newline!(2); - It will simply write the passed number of new lines to the log.

Indents are of several types:

Base indent

Base indent will be added to every line. - Default for all: 6 indents. One indent equals one space.

You can change base indent with cargo features:

For example in Cargo.toml:

```toml nologsetup = ["nolog/indentbase_zero"]

trace = ["nolog/trace", "nolog_setup"] ```

The indent of the selected message

The 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");

All of these arguments are optional:

rust 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:

rust crit!(->[_,1] "msg"); crit!(->[_,_,2] "msg");

The same works for each message in the chain.

rust 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.

rust error!(->[0] "{name}" => "{}!! Oh, yeaaaah!", 2*3*7);

Or you can do it via Cargo.toml for all messages.

For example in Cargo.toml: ```toml nologsetup = ["nolog/indentkvdefaultzero"]

trace = ["nolog/trace", "nolog_setup"] ```

Key-values ​​have the additional ability to set indentation not only for the key, but also for the value.

rust debug!( "{server}" => "{ip}"; "Status" => ->[3] "{server_check_result}"; );

This allows you to get nice aligned output if you want.

indent

Indent variables

You can use variables to set the indentation and add blank lines.

rust for i in 0..2 { warn!(->[i,i,i] "msg"); }

Ignore all indents

Ignore all types of indentation.

toml nolog_setup = ["nolog/indent_ignore_all"]

indent

Ignore all newlines

toml nolog_setup = ["nolog/newline_ignore"]

Colors

nolog colored by default, use this feature for plain output:

toml nolog_setup = ["nolog/plain"]

Level headers

Show level name for key-value: sh CRIT: Key: value [src/main.rs 90:5]` ^^^^

It's disabled by default: sh Key: value [src/main.rs 90:5]

toml nolog_setup = ["nolog/show_lvl_header_kv"]

Don't disable logger in release build

toml nolog_setup = ["nolog/release"]

Location

Don't show location (like [src/main.rs 155:9])

toml nolog_setup = ["nolog/location_hide"]

Style like this: [src/main.rs 155:9]

toml nolog_setup = ["nolog/location_style_classic"]

location

Separator

Default = "⧽ "

toml nolog_setup = ["nolog/sep_colon"]

toml nolog_setup = ["nolog/sep_space"]

toml nolog_setup = ["nolog/sep_hide"]

Custom color scheme

You can create your own color scheme for the logger.

Cargo.toml

```toml [dependencies] nolog = { version = "1", features = [] }

[features] customcolors = ["nolog/customcolors"] nologsetup = ["customcolors"] ```

Here is an example:

main.rs

```rust

[macro_use]

extern crate nolog;

[macro_use]

pub mod loggersetup { #[macroexport] #[cfg(feature = "customcolors")] macrorules! // ^^^^^^^^^^^^^ 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 fromothermod() -> () { crit!(->[0] "Other" => "Hello from other mod! This is key-value msg."); } }

fn main(){ crit!("Hello from main! This is usual msg."); other::fromothermod();
} ```

Logging in tests

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.

sh cargo test --features trace -- --nocapture

The output of failed tests will be displayed anyway.

sh cargo test --features trace

Changelog