cli-log-2.1.0/.cargo_vcs_info.json0000644000000001360000000000100123620ustar { "git": { "sha1": "3f793fc69456a8fc64758f51ae903080015b4739" }, "path_in_vcs": "" }cli-log-2.1.0/.gitignore000064400000000000000000000005001046102023000131350ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk cli-log-2.1.0/Cargo.toml0000644000000023520000000000100103620ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "cli-log" version = "2.1.0" authors = ["dystroy "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "a simple logging and timing facility configured with an env variable" readme = "README.md" keywords = [ "log", "terminal", "file", "benchmark", ] categories = [] license = "MIT" repository = "https://github.com/Canop/cli-log" [lib] name = "cli_log" path = "src/lib.rs" [dependencies.chrono] version = "0.4" [dependencies.file-size] version = "1.0.3" optional = true [dependencies.log] version = "0.4" features = ["std"] [dependencies.proc-status] version = "0.1" optional = true [features] default = ["mem"] mem = [ "proc-status", "file-size", ] cli-log-2.1.0/Cargo.toml.orig000064400000000000000000000011201046102023000140330ustar 00000000000000[package] name = "cli-log" version = "2.1.0" authors = ["dystroy "] repository = "https://github.com/Canop/cli-log" description = "a simple logging and timing facility configured with an env variable" edition = "2018" keywords = ["log", "terminal", "file", "benchmark"] license = "MIT" categories = [] readme = "README.md" [features] default = ["mem"] mem = ["proc-status", "file-size"] [dependencies] chrono = "0.4" log = { version = "0.4", features = ["std"] } proc-status = { version = "0.1", optional = true } file-size = { version = "1.0.3", optional = true } cli-log-2.1.0/LICENSE000064400000000000000000000020461046102023000121610ustar 00000000000000MIT License Copyright (c) 2020 Canop Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cli-log-2.1.0/README.md000064400000000000000000000054261046102023000124400ustar 00000000000000[![MIT][s2]][l2] [![Latest Version][s1]][l1] [![docs][s3]][l3] [![Chat on Miaou][s4]][l4] [s1]: https://img.shields.io/crates/v/cli-log.svg [l1]: https://crates.io/crates/cli-log [s2]: https://img.shields.io/badge/license-MIT-blue.svg [l2]: LICENSE [s3]: https://docs.rs/cli-log/badge.svg [l3]: https://docs.rs/cli-log/ [s4]: https://miaou.dystroy.org/static/shields/room.svg [l4]: https://miaou.dystroy.org/3 # cli-log The boilerplate to have some file logging with a level given by an environment variable, and a facility to log execution durations according to the relevant log level. It's especially convenient for terminal applications because you don't want to mix log with stdout or stderr. The use of an env variable makes it possible to distribute the application and have users generate some logs without recompilation or configuration. The names of the log file and the env variable are computed from the name of the application. So log initialization is just ``` use cli_log::*; // also import logging macros init_cli_log!(); ``` If you prefer not having to declare cli_log import for all the log and cli-log logging macros, you may use the old `#[macro_use]` import in your main.rs file: ``` #[macro_use] extern crate cli_log; init_cli_log!(); ``` With the `"mem"` feature (enabled by default), when the OS is compatible (unix like), you may dump the current and peak memory usage with the `log_mem` function. Here's a complete application using cli-log (it can be found in examples): ``` use cli_log::*; #[derive(Debug)] struct AppData { count: usize, } impl AppData { fn compute(&mut self) { self.count += 7; } } fn main() { init_cli_log!(); let mut app_data = AppData { count: 35 }; time!(Debug, app_data.compute()); info!("count is {}", app_data.count); debug!("data: {:#?}", &app_data); warn!("this application does nothing"); log_mem(Level::Info); info!("bye"); } ``` If you don't set any `SMALL_APP_LOG` env variable, there won't be any log. A convenient way to set the env variable is to launch the app as ```cli SMALL_APP_LOG=debug small_app ``` or, during development, ```cli SMALL_APP_LOG=debug cargo run ``` This creates a `small_app.log` file containing information like the level, app version, and of course the log operations you did with time precise to the ms and the logging module (target): ```log 21:03:24.081 [INFO] cli_log::init: Starting small-app v1.0.1 with log level DEBUG 21:03:24.081 [DEBUG] small_app: app_data.compute() took 312ns 21:03:24.081 [INFO] small_app: count is 42 21:03:24.081 [DEBUG] small_app: data: AppData { count: 42, } 21:03:24.081 [WARN] small_app: this application does nothing 21:03:24.081 [INFO] cli_log::mem: Physical mem usage: current=938K, peak=3.3M 21:03:24.082 [INFO] small_app: bye ``` cli-log-2.1.0/bacon.toml000064400000000000000000000064661046102023000131450ustar 00000000000000# This is a configuration file for the bacon tool # # Bacon repository: https://github.com/Canop/bacon # Complete help on configuration: https://dystroy.org/bacon/config/ # You can also check bacon's own bacon.toml file # as an example: https://github.com/Canop/bacon/blob/main/bacon.toml default_job = "check" [jobs.check] command = ["cargo", "check", "--color", "always"] need_stdout = false [jobs.check-all] command = ["cargo", "check", "--all-targets", "--color", "always"] need_stdout = false # Run clippy on the default target [jobs.clippy] command = [ "cargo", "clippy", "--color", "always", ] need_stdout = false # Run clippy on all targets # To disable some lints, you may change the job this way: # [jobs.clippy-all] # command = [ # "cargo", "clippy", # "--all-targets", # "--color", "always", # "--", # "-A", "clippy::bool_to_int_with_if", # "-A", "clippy::collapsible_if", # "-A", "clippy::derive_partial_eq_without_eq", # ] # need_stdout = false [jobs.clippy-all] command = [ "cargo", "clippy", "--all-targets", "--color", "always", "--", "-A", "clippy::needless_doctest_main", ] need_stdout = false # This job lets you run # - all tests: bacon test # - a specific test: bacon test -- config::test_default_files # - the tests of a package: bacon test -- -- -p config [jobs.test] command = [ "cargo", "test", "--color", "always", "--", "--color", "always", # see https://github.com/Canop/bacon/issues/124 ] need_stdout = true [jobs.nextest] command = ["cargo", "nextest", "run", "--color", "always", "--hide-progress-bar", "--failure-output", "final"] need_stdout = true analyzer = "nextest" [jobs.doc] command = ["cargo", "doc", "--color", "always", "--no-deps"] need_stdout = false # If the doc compiles, then it opens in your browser and bacon switches # to the previous job [jobs.doc-open] command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"] need_stdout = false on_success = "back" # so that we don't open the browser at each change # You can run your application and have the result displayed in bacon, # *if* it makes sense for this crate. # Don't forget the `--color always` part or the errors won't be # properly parsed. # If your program never stops (eg a server), you may set `background` # to false to have the cargo run output immediately displayed instead # of waiting for program's end. If you prefer to have it restarted at # every change, then uncomment the 'on_change_strategy' line. [jobs.run] command = [ "cargo", "run", "--color", "always", # put launch parameters for your program behind a `--` separator ] need_stdout = true allow_warnings = true background = true #on_change_strategy = "kill_then_restart" # This parameterized job runs the example of your choice, as soon # as the code compiles. # Call it as # bacon ex -- my-example [jobs.ex] command = ["cargo", "run", "--color", "always", "--example"] need_stdout = true allow_warnings = true # You may define here keybindings that would be specific to # a project, for example a shortcut to launch a specific job. # Shortcuts to internal functions (scrolling, toggling, etc.) # should go in your personal global prefs.toml file instead. [keybindings] # alt-m = "job:my-job" c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target cli-log-2.1.0/fmt.sh000075500000000000000000000000231046102023000122720ustar 00000000000000cargo +nightly fmt cli-log-2.1.0/rustfmt.toml000064400000000000000000000001761046102023000135570ustar 00000000000000edition = "2021" style_edition = "2024" imports_granularity = "one" imports_layout = "Vertical" fn_params_layout = "Vertical" cli-log-2.1.0/src/file_logger.rs000064400000000000000000000017321046102023000145700ustar 00000000000000use { log::{ LevelFilter, Log, Metadata, Record, }, std::{ fs::File, io::Write, sync::Mutex, }, }; static TIME_FORMAT: &str = "%T%.3f"; pub(crate) struct FileLogger { pub file: Mutex, pub level: LevelFilter, } impl Log for FileLogger { fn enabled( &self, metadata: &Metadata<'_>, ) -> bool { metadata.level() <= self.level } fn log( &self, record: &Record<'_>, ) { if self.enabled(record.metadata()) { let mut w = self.file.lock().unwrap(); let _ = writeln!( w, "{} [{}] {}: {}", chrono::Local::now().format(TIME_FORMAT), record.level(), record.target(), record.args(), ); // we ignore errors here } } fn flush(&self) { let _ = self.file.lock().unwrap().flush(); } } cli-log-2.1.0/src/init.rs000064400000000000000000000075261046102023000132640ustar 00000000000000use { crate::file_logger::FileLogger, log::LevelFilter, std::{ env, fs::File, str::FromStr, sync::Mutex, }, }; /// Configure the application log according to env variable, without failing /// in case of io error. /// /// If the log file cannot be created, the error is printed to stderr, then /// the application proceeds without logging. pub fn init( app_name: &str, app_version: &str, ) { if let Err(e) = try_init(app_name, app_version) { eprintln!("Failed to initialize log: {}", e); } } /// Configure the application log according to env variable. /// /// If an io error occurs, it is returned, and all logging is disabled /// (but there won't be any panic or error on log calls). /// /// The caller can decide to print the error and not, to continue or not. pub fn try_init( app_name: &str, app_version: &str, ) -> std::io::Result<()> { let env_var_name = format!("{}_LOG", app_name.to_ascii_uppercase().replace('-', "_"),); let level = env::var(env_var_name).unwrap_or_else(|_| "off".to_string()); if level == "off" { return Ok(()); } if let Ok(level) = LevelFilter::from_str(&level) { let log_file_name = format!("{}.log", app_name); let file = File::create(log_file_name)?; log::set_max_level(level); let logger = FileLogger { file: Mutex::new(file), level, }; log::set_boxed_logger(Box::new(logger)).unwrap(); log::info!( "Starting {} v{} with log level {}", app_name, app_version, level ); } Ok(()) } /// Configure the application log according to env variable, without failing /// in case of io error. /// /// If the log file cannot be created, the error is printed to stderr, then /// the application proceeds without logging. /// /// Example: /// /// ``` /// cli_log::init_cli_log!(); /// ``` /// You may specify an altername application name instead /// of your crate name: /// /// ``` /// cli_log::init_cli_log!("my-app"); /// ``` /// /// The application name will also be used to derive the /// env variable name giving the log level, for example /// `MY_APP_LOG=info` for an application named `my-app`. /// /// The point of using this macro instead of the init function is to ensure /// `env!(CARGO_PKG_NAME)` and `env!(CARGO_PKG_VERSION)` are expanded for /// the outer package, not for cli-log #[macro_export] macro_rules! init_cli_log { () => { cli_log::init(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); }; ($app_name: expr) => { cli_log::init($app_name, env!("CARGO_PKG_VERSION")); }; } /// Configure the application log according to env variable. /// /// If an io error occurs, it is returned, and all logging is disabled /// (but there won't be any panic or error on log calls). /// /// The caller can decide to print the error and not, to continue or not. /// /// Example: /// /// ``` /// cli_log::try_init_cli_log!().expect("Failed to initialize log"); /// ``` /// You may specify an altername application name instead /// of your crate name: /// /// ``` /// if Err(e) = cli_log::try_init_cli_log!("my-app") { /// eprintln!("Running without log because of error: {}", e); /// } /// ``` /// /// The application name will also be used to derive the /// env variable name giving the log level, for example /// `MY_APP_LOG=info` for an application named `my-app`. /// /// The point of using this macro instead of the init function is to ensure /// `env!(CARGO_PKG_NAME)` and `env!(CARGO_PKG_VERSION)` are expanded for /// the outer package, not for cli-log #[macro_export] macro_rules! try_init_cli_log { () => { cli_log::try_init(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); }; ($app_name: expr) => { cli_log::try_init($app_name, env!("CARGO_PKG_VERSION")); }; } cli-log-2.1.0/src/lib.rs000064400000000000000000000055351046102023000130650ustar 00000000000000//! The boilerplate to have some file logging with a level given by an environment variable, //! and a facility to log execution durations according to the relevant log level. //! //! It's especially convenient for terminal applications //! because you don't want to mix log with stdout or stderr. //! //! The use of an env variable makes it possible to distribute //! the application and have users generate some logs without //! recompilation or configuration. //! //! The names of the log file and the env variable are //! computed from the name of the application. //! //! So log initialization is just //! //! ``` //! use cli_log::*; // also import logging macros //! init_cli_log!(); //! ``` //! //! If you prefer not having to declare cli_log import for //! all the log and cli-log logging macros, you may use the //! old `#[macro_use]` import in your main.rs file: //! //! ``` //! #[macro_use] extern crate cli_log; //! init_cli_log!(); //! ``` //! //! With the `"mem"` feature (enabled by default), when the OS is compatible //! (unix like), you may dump the current and peak memory usage with //! the `log_mem` function. //! //! //! Here's a complete application using cli-log (it can be found in examples): //! //! ``` //! use cli_log::*; //! //! #[derive(Debug)] //! struct AppData { //! count: usize, //! } //! impl AppData { //! fn compute(&mut self) { //! self.count += 7; //! } //! } //! //! fn main() { //! init_cli_log!(); //! let mut app_data = AppData { count: 35 }; //! time!(Debug, app_data.compute()); //! info!("count is {}", app_data.count); //! debug!("data: {:#?}", &app_data); //! warn!("this application does nothing"); //! log_mem(Level::Info); //! info!("bye"); //! } //! ``` //! //! If you don't set any `SMALL_APP_LOG` env variable, there won't be any log. //! //! A convenient way to set the env variable is to launch the app as //! //! ```cli //! SMALL_APP_LOG=debug small_app //! ``` //! //! or, during development, //! //! ```cli //! SMALL_APP_LOG=debug cargo run //! ``` //! //! This creates a `small_app.log` file containing information like the level, //! app version, and of course the log operations you did with time precise to //! the ms and the logging module (target): //! //! ```log //! 21:03:24.081 [INFO] cli_log::init: Starting small-app v1.0.1 with log level DEBUG //! 21:03:24.081 [DEBUG] small_app: app_data.compute() took 312ns //! 21:03:24.081 [INFO] small_app: count is 42 //! 21:03:24.081 [DEBUG] small_app: data: AppData { //! count: 42, //! } //! 21:03:24.081 [WARN] small_app: this application does nothing //! 21:03:24.081 [INFO] cli_log::mem: Physical mem usage: current=938K, peak=3.3M //! 21:03:24.082 [INFO] small_app: bye //! ``` mod file_logger; mod init; mod time; pub use { init::*, log::*, }; #[cfg(feature = "mem")] mod mem; #[cfg(feature = "mem")] pub use mem::log_mem; cli-log-2.1.0/src/mem.rs000064400000000000000000000011421046102023000130630ustar 00000000000000use log::*; /// log the current and peak physical memory used by /// the current process, if the given log level is /// reached /// /// This function is only available when the feature /// "mem" is enabled and when the OS supports it /// (unix-like systems). pub fn log_mem(level: Level) { if log_enabled!(level) { if let Ok(mem) = proc_status::mem_usage() { log!( level, "Physical mem usage: current={}, peak={}", file_size::fit_4(mem.current as u64), file_size::fit_4(mem.peak as u64), ); } } } cli-log-2.1.0/src/time.rs000064400000000000000000000072741046102023000132570ustar 00000000000000/// print the time that executing some expression took /// but only when relevant according to log level. /// /// The goal of this macro is to avoid doing useless /// `Instant::now`. /// /// Arguments: /// - log level, optional (default is `Debug`) /// - a category, optional (only if name is set) /// - a name, optional (stringified expression is used by default) /// - the expression whose duration we want to log depending on the level /// /// Examples: /// /// ``` /// # use log::*; /// # use cli_log::*; /// # fn do_stuff(arg: usize) -> Result { /// # Ok(arg) /// # } /// # fn main() -> Result<(), String> { /// let result = time!(do_stuff(4)); /// let result = time!(Debug, do_stuff(3))?; /// let result = time!("World creation", do_stuff(7)); /// let sum = time!(Debug, "summing", 2 + 2); /// let sum = time!(Debug, "summing", 2 + 2); /// let mult = time!("operations", "mult 4", 3 * 4); /// let mult = time!(Info, "operations", "mult 4", 3 * 4); /// # Ok(()) /// # } /// ``` #[macro_export] macro_rules! time { ($timed: expr $(,)?) => {{ use cli_log::{ Level::*, *, }; if log_enabled!(Debug) { let start = std::time::Instant::now(); match $timed { value => { log!(Debug, "{} took {:?}", stringify!($timed), start.elapsed()); value } } } else { $timed } }}; ($level: ident, $timed: expr $(,)?) => {{ use cli_log::{ Level::*, *, }; if log_enabled!($level) { let start = std::time::Instant::now(); match $timed { value => { log!($level, "{} took {:?}", stringify!($timed), start.elapsed()); value } } } else { $timed } }}; ($name: expr, $timed: expr $(,)?) => {{ use cli_log::{ Level::*, *, }; if log_enabled!(Debug) { let start = std::time::Instant::now(); match $timed { value => { log!(Debug, "{} took {:?}", $name, start.elapsed()); value } } } else { $timed } }}; ($level: ident, $name: expr, $timed: expr $(,)?) => {{ use cli_log::{ Level::*, *, }; if log_enabled!($level) { let start = std::time::Instant::now(); match $timed { value => { log!($level, "{} took {:?}", $name, start.elapsed()); value } } } else { $timed } }}; ($cat: expr, $name :expr, $timed: expr $(,)?) => {{ use cli_log::{ Level::*, *, }; if log_enabled!(Debug) { let start = std::time::Instant::now(); match $timed { value => { log!(Debug, "{} on {:?} took {:?}", $cat, $name, start.elapsed()); value } } } else { $timed } }}; ($level: ident, $cat: expr, $name :expr, $timed: expr $(,)?) => {{ use cli_log::{ Level::*, *, }; if log_enabled!($level) { let start = std::time::Instant::now(); match $timed { value => { log!($level, "{} on {:?} took {:?}", $cat, $name, start.elapsed()); value } } } else { $timed } }}; }