1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! 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
//!
//! ```
//! #[macro_use] extern crate log;
//! #[macro_use] extern crate cli_log;
//! init_cli_log!();
//! ```
//!
//! Here's a complete application using cli-log (it can be found in examples):
//!
//! ```
//! #[macro_use] extern crate log;
//! #[macro_use] extern crate 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");
//!     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
//! 13:39:53.511 [INFO] cli_log: Starting small-app v0.1.0 with log level DEBUG
//! 13:39:53.511 [INFO] small_app: count is 42
//! 13:39:53.511 [DEBUG] small_app: data: AppData {
//!     count: 42,
//! }
//! 13:39:53.511 [WARN] small_app: this application does nothing
//! 13:39:53.511 [INFO] small_app: bye
//! ```
//!

#[macro_use] extern crate log;

mod file_logger;
mod time;

use {
    file_logger::FileLogger,
    log::{
        LevelFilter,
    },
    std::{
        env,
        fs::File,
        str::FromStr,
        sync::Mutex,
    },
};


/// configure the application log according to env variable.
pub fn init(app_name: &str, app_version: &str) {
    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;
    }
    if let Ok(level) = LevelFilter::from_str(&level) {
        let log_file_name = format!("{}.log", app_name);
        let file = File::create(&log_file_name)
            .expect("Log file can't be created");
        log::set_max_level(level);
        let logger = FileLogger {
            file: Mutex::new(file),
            level,
        };
        log::set_boxed_logger(Box::new(logger)).unwrap();
        info!(
            "Starting {} v{} with log level {}",
            app_name,
            app_version,
            level
        );
    }
}

/// configure the application log according to env variable
///
/// Example:
///
/// ```
/// #[macro_use] extern crate log;
/// #[macro_use] extern crate cli_log;
/// init_cli_log!();
/// ```
/// You may specify an altername application name instead
/// of your crate name:
///
/// ```
/// #[macro_use] extern crate log;
/// #[macro_use] extern crate 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 this macro 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"));
    };
}