# casual_logger
There are only features that are used **for self-study** of programming examples.
Not for production, but better than not logging anything.
Interested:
* **Rotate** log by date.
* **Delete** old log files. (semi-automatic)
* Write the log as a **TOML table**.
* There is **no configuration file**.
Not interested:
* Only write to 1 file on working directory.
* The file path **cannot** be set.
## (Suggest a solution) Are you in trouble with this?
(Case-1)
I wanted to self-learn short programming with a logger, but setting up a logger is **difficult than that short programming**.
For example, For beginners who want to write a tic-tac-toe program now, setting up a logger is a hassle.
(Case-2)
Writing a log **parser** is tedious.
### This logger solves the problem in this way.
(Case-1 solution)
All features are trial in **one copy and paste**. See "At second, Overall" view below.
(Case-2 solution)
Write the log as a **TOML table**. A human-readable format that can be analyzed by a computer.
## At first, Disclaim
* It **differs** from the standard Rust log interface.
* **Ignore performance** for ease of use and ease of explanation.
* You can break the toml format. **Do not validate**.
* Depending on the version of this program, the log writing order may be **unstable**. Check the serial "Seq" number.
* If the log export fails, the **error is ignored** or print stderr, and it continues.
* There is a waiting time of **1 second or more** before the logger ends.
* **Don't forget** wait for logging to complete at **end of program**.
## At second, Overall view
Your code:
```rust
//! All features are described in one copy and paste.
use casual_logger::{Level, Log, Table, LOGGER};
fn main() {
let remove_num = if let Ok(mut logger) = LOGGER.lock() {
// Do not call 'Log::xxxxx()' in this code block.
//
// Set file name.
//
// All: 'tic-tac-toe-2020-07-11.log.toml'
// Prefix: 'tic-tac-toe'
// StartDate: '-2020-07-11' automatically.
// Suffix: '.log' - To be safe, include a word that
// clearly states that you can delete the file.
// Extention: '.toml'
//
// If you don't like the .toml extension, leave the
// suffix empty and the .log extension.
logger.set_file_name("tic-tac-toe", ".log", ".toml");
// The higher this level, the more will be omitted.
//
// |<-- Low Level --------------------- High level -->|
// |<-- High priority --------------- Low priority -->|
// |Fatal< Error < Warn < Notice < Info < Debug <Trace|
logger.level = Level::Trace;
// Wait for seconds logging to complete when fatal.
// By default it's set to 30 seconds,
// so you probably don't need to set it.
logger.fatal_timeout_secs = 30;
// Remove old log files. This is determined by the
// StartDate in the filename.
logger.retention_days = 2;
logger.remove_old_logs()
} else {
// Setup failed. Continue with the default settings.
0
};
Log::noticeln(&format!("Remove {} files.", remove_num));
// Multi-line string.
// The suffix "ln" adds a newline at the end.
Log::infoln(
"Hello, world!!
こんにちわ、世界!!",
);
// After explicitly checking the level.
if Log::enabled(Level::Info) {
let x = 100; // Time-consuming preparation, here.
Log::infoln(&format!("x is {}.", x));
}
// The level is implicitly confirmed.
Log::trace("( 1)TRACE");
Log::traceln("( 2)trace-line");
Log::debug("( 3)DEBUG");
Log::debugln("( 4)debug-line");
Log::info("( 5)INFO");
Log::infoln("( 6)info-line");
Log::notice("( 7)NOTICE");
Log::noticeln("( 8)notice-line");
Log::warn("( 9)WARN");
Log::warnln("(10)warn-line");
Log::error("(11)ERROR");
Log::errorln("(12)error-line");
Log::fatal("(13)FATAL");
Log::fatalln("(14)fatal-line");
// Fatal is Panic! Can be used as the first argument of.
// panic!(Log::fatal(&format!("Invalid number=|{}|", 99)));
// Suffix '_t'. TOML say a table. So-called map.
Log::infoln_t(
"The sky is from top to bottom!!
上から下まで空です!!",
Table::default()
.str(
// Do not include spaces in your key.
"ABird",
"fly in the sky.",
)
// Not enclose this value in quotation marks.
.literal("NumberOfSwimmingFish", "2")
.str(
"ThreeMonkeys",
"climb
a tall
tree.",
),
);
if let Ok(mut logger) = LOGGER.lock() {
// |Fatal< Error < Warn < Notice < Info < Debug <Trace|
// | *****|
logger.level = Level::Trace;
}
Log::trace("(7)Trace on (7)Trace.");
Log::debug("(6)Debug on (7)Trace.");
Log::info("(5)Info on (7)Trace.");
Log::notice("(4)Notice on (7)Trace.");
Log::warn("(3)Warn on (7)Trace.");
Log::error("(2)Error on (7)Trace.");
Log::fatal("(1)Fatal on (7)Trace.");
if let Ok(mut logger) = LOGGER.lock() {
// |Fatal< Error < Warn < Notice < Info < Debug <Trace|
// | ***** |
logger.level = Level::Debug;
}
Log::trace("(7)Trace on (6)debug. Skip!");
Log::debug("(6)Debug on (6)debug.");
Log::info("(5)Info on (6)debug.");
Log::notice("(4)Notice on (6)debug.");
Log::warn("(3)Warn on (6)debug.");
Log::error("(2)Error on (6)debug.");
Log::fatal("(1)Fatal on (6)debug.");
if let Ok(mut logger) = LOGGER.lock() {
// |Fatal< Error < Warn < Notice < Info < Debug <Trace|
// | ***** |
logger.level = Level::Info;
}
Log::trace("(7)Trace on (5)Info. Skip!");
Log::debug("(6)Debug on (5)Info. Skip!");
Log::info("(5)Info on (5)Info.");
Log::notice("(4)Notice on (5)Info.");
Log::warn("(3)Warn on (5)Info.");
Log::error("(2)Error on (5)Info.");
Log::fatal("(1)Fatal on (5)Info.");
if let Ok(mut logger) = LOGGER.lock() {
// |Fatal< Error < Warn < Notice < Info < Debug <Trace|
// | ****** |
logger.level = Level::Notice;
}
Log::trace("(7)Trace on (4)Notice. Skip!");
Log::debug("(6)Debug on (4)Notice. Skip!");
Log::info("(5)Info on (4)Notice. Skip!");
Log::notice("(4)Notice on (4)Notice.");
Log::warn("(3)Warn on (4)Notice.");
Log::error("(2)Error on (4)Notice.");
Log::fatal("(1)Fatal on (4)Notice.");
if let Ok(mut logger) = LOGGER.lock() {
// |Fatal< Error < Warn < Notice < Info < Debug <Trace|
// | **** |
logger.level = Level::Warn;
}
Log::trace("(7)Trace on (3)Warn. Skip!");
Log::debug("(6)Debug on (3)Warn. Skip!");
Log::info("(5)Info on (3)Warn. Skip!");
Log::notice("(4)Notice on (3)Warn. Skip!");
Log::warn("(3)Warn on (3)Warn.");
Log::error("(2)Error on (3)Warn.");
Log::fatal("(1)Fatal on (3)Warn.");
if let Ok(mut logger) = LOGGER.lock() {
// |Fatal< Error < Warn < Notice < Info < Debug <Trace|
// | ***** |
logger.level = Level::Error;
}
Log::trace("(7)Trace on (2)Error. Skip!");
Log::debug("(6)Debug on (2)Error. Skip!");
Log::info("(5)Info on (2)Error. Skip!");
Log::notice("(4)Notice on (2)Error. Skip!");
Log::warn("(3)Warn on (2)Error. Skip!");
Log::error("(2)Error on (2)Error.");
Log::fatal("(1)Fatal on (2)Error.");
if let Ok(mut logger) = LOGGER.lock() {
// |Fatal< Error < Warn < Notice < Info < Debug <Trace|
// |***** |
logger.level = Level::Fatal;
}
Log::trace("(7)Trace on (1)Fatal. Skip!");
Log::debug("(6)Debug on (1)Fatal. Skip!");
Log::info("(5)Info on (1)Fatal. Skip!");
Log::notice("(4)Notice on (1)Fatal. Skip!");
Log::warn("(3)Warn on (1)Fatal. Skip!");
Log::error("(2)Error on (1)Fatal. Skip!");
Log::fatal("(1)Fatal on (1)Fatal.");
// Wait for logging to complete. Time out 30 seconds.
Log::wait_for_logging_to_complete(30, |secs, message| {
// Do not call 'Log::xxxxx()' in this code block.
println!("{} sec(s). {}", secs, message);
});
}
```
Output `./default-2020-07-14.log.toml` auto generated:
```toml
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=1"]
Notice = "Remove 0 files.\r\n"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=2"]
Info = """
Hello, world!!
こんにちわ、世界!!\r\n
"""
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=3"]
Info = "x is 100.\r\n"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=4"]
Trace = "( 1)TRACE"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=5"]
Trace = "( 2)trace-line\r\n"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=6"]
Debug = "( 3)DEBUG"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=7"]
Debug = "( 4)debug-line\r\n"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=8"]
Info = "( 5)INFO"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=9"]
Info = "( 6)info-line\r\n"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=10"]
Notice = "( 7)NOTICE"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=11"]
Notice = "( 8)notice-line\r\n"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=12"]
Warn = "( 9)WARN"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=13"]
Warn = "(10)warn-line\r\n"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=14"]
Error = "(11)ERROR"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=15"]
Error = "(12)error-line\r\n"
["Now=2020-07-14 00:16:26&Pid=10928&Thr=ThreadId(1)&Seq=16"]
Fatal = "(13)FATAL"
["Now=2020-07-14 00:16:27&Pid=10928&Thr=ThreadId(1)&Seq=17"]
Fatal = "(14)fatal-line\r\n"
["Now=2020-07-14 00:16:28&Pid=10928&Thr=ThreadId(1)&Seq=18"]
Info = """
The sky is from top to bottom!!
上から下まで空です!!\r\n
"""
ABird = "fly in the sky."
NumberOfSwimmingFish = 2
ThreeMonkeys = """
climb
a tall
tree.
"""
["Now=2020-07-14 00:16:28&Pid=10928&Thr=ThreadId(1)&Seq=19"]
Trace = "(7)Trace on (7)Trace."
["Now=2020-07-14 00:16:28&Pid=10928&Thr=ThreadId(1)&Seq=20"]
Debug = "(6)Debug on (7)Trace."
["Now=2020-07-14 00:16:28&Pid=10928&Thr=ThreadId(1)&Seq=21"]
Info = "(5)Info on (7)Trace."
["Now=2020-07-14 00:16:28&Pid=10928&Thr=ThreadId(1)&Seq=22"]
Notice = "(4)Notice on (7)Trace."
["Now=2020-07-14 00:16:29&Pid=10928&Thr=ThreadId(1)&Seq=23"]
Warn = "(3)Warn on (7)Trace."
["Now=2020-07-14 00:16:29&Pid=10928&Thr=ThreadId(1)&Seq=24"]
Error = "(2)Error on (7)Trace."
["Now=2020-07-14 00:16:29&Pid=10928&Thr=ThreadId(1)&Seq=25"]
Fatal = "(1)Fatal on (7)Trace."
["Now=2020-07-14 00:16:30&Pid=10928&Thr=ThreadId(1)&Seq=26"]
Debug = "(6)Debug on (6)debug."
["Now=2020-07-14 00:16:30&Pid=10928&Thr=ThreadId(1)&Seq=27"]
Info = "(5)Info on (6)debug."
["Now=2020-07-14 00:16:30&Pid=10928&Thr=ThreadId(1)&Seq=28"]
Notice = "(4)Notice on (6)debug."
["Now=2020-07-14 00:16:30&Pid=10928&Thr=ThreadId(1)&Seq=29"]
Warn = "(3)Warn on (6)debug."
["Now=2020-07-14 00:16:30&Pid=10928&Thr=ThreadId(1)&Seq=30"]
Error = "(2)Error on (6)debug."
["Now=2020-07-14 00:16:30&Pid=10928&Thr=ThreadId(1)&Seq=31"]
Fatal = "(1)Fatal on (6)debug."
["Now=2020-07-14 00:16:31&Pid=10928&Thr=ThreadId(1)&Seq=32"]
Info = "(5)Info on (5)Info."
["Now=2020-07-14 00:16:31&Pid=10928&Thr=ThreadId(1)&Seq=33"]
Notice = "(4)Notice on (5)Info."
["Now=2020-07-14 00:16:31&Pid=10928&Thr=ThreadId(1)&Seq=34"]
Warn = "(3)Warn on (5)Info."
["Now=2020-07-14 00:16:31&Pid=10928&Thr=ThreadId(1)&Seq=35"]
Error = "(2)Error on (5)Info."
["Now=2020-07-14 00:16:31&Pid=10928&Thr=ThreadId(1)&Seq=36"]
Fatal = "(1)Fatal on (5)Info."
["Now=2020-07-14 00:16:32&Pid=10928&Thr=ThreadId(1)&Seq=37"]
Notice = "(4)Notice on (4)Notice."
["Now=2020-07-14 00:16:32&Pid=10928&Thr=ThreadId(1)&Seq=38"]
Warn = "(3)Warn on (4)Notice."
["Now=2020-07-14 00:16:32&Pid=10928&Thr=ThreadId(1)&Seq=39"]
Error = "(2)Error on (4)Notice."
["Now=2020-07-14 00:16:32&Pid=10928&Thr=ThreadId(1)&Seq=40"]
Fatal = "(1)Fatal on (4)Notice."
["Now=2020-07-14 00:16:33&Pid=10928&Thr=ThreadId(1)&Seq=41"]
Warn = "(3)Warn on (3)Warn."
["Now=2020-07-14 00:16:33&Pid=10928&Thr=ThreadId(1)&Seq=42"]
Error = "(2)Error on (3)Warn."
["Now=2020-07-14 00:16:33&Pid=10928&Thr=ThreadId(1)&Seq=43"]
Fatal = "(1)Fatal on (3)Warn."
["Now=2020-07-14 00:16:34&Pid=10928&Thr=ThreadId(1)&Seq=44"]
Error = "(2)Error on (2)Error."
["Now=2020-07-14 00:16:34&Pid=10928&Thr=ThreadId(1)&Seq=45"]
Fatal = "(1)Fatal on (2)Error."
["Now=2020-07-14 00:16:35&Pid=10928&Thr=ThreadId(1)&Seq=46"]
Fatal = "(1)Fatal on (1)Fatal."
```
Output to terminal:
```plain
casual_logger fatal: 0 sec(s). 16 table(s) left.
casual_logger fatalln: 0 sec(s). Wait for 1 thread(s).
casual_logger fatal: 0 sec(s). Wait for 2 thread(s).
casual_logger fatal: 1 sec(s). 3 table(s) left.
casual_logger fatal: 0 sec(s). Wait for 3 thread(s).
casual_logger fatal: 0 sec(s). Wait for 5 thread(s).
casual_logger fatal: 0 sec(s). Wait for 4 thread(s).
casual_logger fatal: 0 sec(s). Wait for 3 thread(s).
casual_logger fatal: 0 sec(s). Wait for 2 thread(s).
casual_logger fatal: 0 sec(s). Wait for 1 thread(s).
```
It is designed to use `Log::fatal()` as the first argument for `panic!()`. It is the abnormal termination of the program. There is a waiting time.
## At third, Description
Code:
```rust
use casual_logger::{Level, Log, Table, LOGGER};
```
At the timing of the first writing, a file with a
time stamp in its name is automatically generated.
For example: `./tic-tac-toe-2020-07-12.log.toml`
Description:
| `./` | file path | Working directory | |
| | | only. | |
| `tic-tac-toe` | Prefix | Editable. | `default` |
| `-2020-07-12` | StartDate | Auto generated. | |
| `.log` | Suffix | Editable. | `.log` |
| `.toml` | Extension | Editable. | `.toml` |
It is difficult to explain the **file path** for beginners.
Therefore, it does not move.
Excite yourself with a **prefix**.
**StartDate** is basically today.
If the rotation fails, it is the start date.
**Suffix** to be safe, include a word that
clearly states that you can delete the file.
If you don't like the .toml **extension**, leave
the suffix empty and the .log extension.
Set up, Code:
```rust
fn main() {
if let Ok(mut logger) = LOGGER.lock() {
logger.set_file_name("tic-tac-toe", ".log", ".toml");
logger.level = Level::Trace;
}
// ...
}
```
Log rotation, Code:
```rust
let remove_num = if let Ok(mut logger) = LOGGER.lock() {
logger.retention_days = 2;
logger.remove_old_logs()
} else {
0
};
Log::noticeln(&format!("Remove {} files.", remove_num));
```
### Logger Properties
| `retention_days` | After this number of days, | `7` |
| | the file will be deleted. | |
| `level` | Used to switch between | `Trace` |
| | write and non-write. | |
Example of **retention_days**:
* `retention_days` is 2.
* Today is 2020-07-12.
* The `./default-2020-07-09.log.toml` file will be deleted.
* The `./default-2020-07-10.log.toml` remains.
* Delete old files by date in filename.
Example of **level**:
* There are 7 log levels.
* `|Fatal< Error < Warn < Notice < Info < Debug <Trace|`
* `|<-- Small ------------------------------ Large -->|`
* `|<-- Concise -------------------------- Verbose -->|`
* `|<-- Low Level --------------------- High level -->|`
* `|<-- High priority --------------- Low priority -->|`
| `Fatal` | If the program cannot continue. |
| `Error` | I didn't get the expected result, |
| | so I'll continue with the other method. |
| `Warn` | It will be abnormal soon, |
| | but there is no problem and you can ignore it. |
| | For example: |
| | (1) He reported that it took longer to access |
| | than expected. |
| | (2) Report that capacity is approaching the limit. |
| `Notice` | It must be enabled in the server production |
| | environment. |
| | Record of passing important points correctly. |
| | We are monitoring that it is working properly. |
| `Info` | Report highlights. |
| | Everything that needs to be reported regularly in |
| | the production environment. |
| `Debug` | It should be in a place with many accidents. |
| | This level is disabled in production environments. |
| | Leave it in the source and enable it for |
| | troubleshooting. |
| | Often, this is the production level of a desktop |
| | operating environment. |
| `Trace` | Not included in the distribution. |
| | Remove this level from the source after using it |
| | for debugging. |
| | If you want to find a bug in the program, |
| | write a lot. |
Code:
```rust
// Multi-line string.
// The suffix "ln" adds a newline at the end.
Log::infoln(
"Hello, world!!
こんにちわ、世界!!",
);
// After explicitly checking the level.
if Log::enabled(Level::Info) {
let x = 100; // Time-consuming preparation, here.
Log::infoln(&format!("x is {}.", x));
}
// The level is implicitly confirmed.
Log::trace("( 1)TRACE");
Log::traceln("( 2)trace-line");
Log::debug("( 3)DEBUG");
Log::debugln("( 4)debug-line");
Log::info("( 5)INFO");
Log::infoln("( 6)info-line");
Log::notice("( 7)NOTICE");
Log::noticeln("( 8)notice-line");
Log::warn("( 9)WARN");
Log::warnln("(10)warn-line");
Log::error("(11)ERROR");
Log::errorln("(12)error-line");
Log::fatal("(13)FATAL");
Log::fatalln("(14)fatal-line");
```
A piece of advice.
```rust
// Fatal is Panic! Can be used as the first argument of.
panic!(Log::fatal(&format!("Invalid number=|{}|", 99)));
```
Fatal returns a string so you can try to record a panic message.
However, the last log may not be written if the program exits first.
So there is a **fatal_timeout_secs** parameter.
Code:
```rust
if let Ok(mut logger) = LOGGER.lock() {
// Wait for seconds logging to complete when fatal.
// By default it's set to 30 seconds,
// so you probably don't need to set it.
logger.fatal_timeout_secs = 30;
}
```
### Usage of Table
| `::default()` | Create a instance. |
| `.str(key, value)` | Insert a string. |
| | Multi-line string are |
| | output with multiple lines. |
| `.literal(key, value)` | Not enclose this value in |
| | quotation marks. |
| | You can break the toml format. |
| | Do not validate. |
Do not include spaces in the **key**. TOML collapses.
It is difficult to explain to beginners how to use TOML.
If you make a TOML that cannot be parsed **literal**ly,
please correct it.
Code:
```rust
// Suffix '_t'. TOML say a table. So-called map.
Log::infoln_t(
"The sky is from top to bottom!!
上から下まで空です!!",
Table::default()
.str(
// Do not include spaces in your key.
"ABird",
"fly in the sky.",
)
// Not enclose this value in quotation marks.
.literal("NumberOfSwimmingFish", "2")
.str(
"ThreeMonkeys",
"climb
a tall
tree.",
),
);
```
Output:
```toml
["Now=2020-07-12 18:35:23&Pid=20872&Thr=ThreadId(1)&Seq=18"]
Info = """
The sky is from top to bottom!!
上から下まで空です!!\r\n
"""
ABird = "fly in the sky."
NumberOfSwimmingFish = 2
ThreeMonkeys = """
climb
a tall
tree.
"""
```
### Don't forget wait for logging to complete at end of program
Code:
```rust
// Wait for logging to complete. Time out 30 seconds.
Log::wait_for_logging_to_complete(30, |secs, message| {
// Do not call 'Log::xxxxx()' in this code block.
println!("{} sec(s). {}", secs, message);
});
```
If you do not wait,
the program will exit before writing all the logs.
## TODO
* [ ] Dogfooding.
## Tested environment
* OS: `Windows 10`.
* Editor: `Visual studio code`.
## Appendix
### Customize method
Code: main.rs
```rust
use casual_logger::Log;
pub trait LogExt {
fn println(s: &str);
}
impl LogExt for Log {
/// Info level logging and add print to stdout.
fn println(s: &str) {
println!("{}", s);
Log::infoln(s);
}
}
```
Usage: other.rs
```rust
use crate::LogExt;
pub fn test() {
Log::println("Hello, world!!");
}
```