// Copyright 2018 The Grin Developers // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Logging wrapper to be used throughout all crates in the workspace use slog::{Discard, Drain, Duplicate, Level, LevelFilter, Logger}; use slog_async; use slog_term; use std::fs::OpenOptions; use std::ops::Deref; use std::sync::Mutex; use backtrace::Backtrace; use std::{panic, thread}; use types::{LogLevel, LoggingConfig}; fn convert_log_level(in_level: &LogLevel) -> Level { match *in_level { LogLevel::Info => Level::Info, LogLevel::Critical => Level::Critical, LogLevel::Warning => Level::Warning, LogLevel::Debug => Level::Debug, LogLevel::Trace => Level::Trace, LogLevel::Error => Level::Error, } } lazy_static! { /// Flag to observe whether logging was explicitly initialised (don't output otherwise) static ref WAS_INIT: Mutex = Mutex::new(false); /// Flag to observe whether tui is running, and we therefore don't want to attempt to write /// panics to stdout static ref TUI_RUNNING: Mutex = Mutex::new(false); /// Static Logging configuration, should only be set once, before first logging call static ref LOGGING_CONFIG: Mutex = Mutex::new(LoggingConfig::default()); /// And a static reference to the logger itself, accessible from all crates pub static ref LOGGER: Logger = { let was_init = WAS_INIT.lock().unwrap().clone(); let config = LOGGING_CONFIG.lock().unwrap(); let slog_level_stdout = convert_log_level(&config.stdout_log_level); let slog_level_file = convert_log_level(&config.file_log_level); if config.tui_running.is_some() && config.tui_running.unwrap() { let mut tui_running_ref = TUI_RUNNING.lock().unwrap(); *tui_running_ref = true; } //Terminal output drain let terminal_decorator = slog_term::TermDecorator::new().build(); let terminal_drain = slog_term::FullFormat::new(terminal_decorator).build().fuse(); let terminal_drain = LevelFilter::new(terminal_drain, slog_level_stdout).fuse(); let mut terminal_drain = slog_async::Async::new(terminal_drain).build().fuse(); if !config.log_to_stdout || !was_init { terminal_drain = slog_async::Async::new(Discard{}).build().fuse(); } if config.log_to_file && was_init { //File drain let file = OpenOptions::new() .create(true) .write(true) .append(config.log_file_append) .truncate(false) .open(&config.log_file_path) .unwrap(); let file_decorator = slog_term::PlainDecorator::new(file); let file_drain = slog_term::FullFormat::new(file_decorator).build().fuse(); let file_drain = LevelFilter::new(file_drain, slog_level_file).fuse(); let file_drain_final = slog_async::Async::new(file_drain).build().fuse(); let composite_drain = Duplicate::new(terminal_drain, file_drain_final).fuse(); Logger::root(composite_drain, o!()) } else { Logger::root(terminal_drain, o!()) } }; } /// Initialize the logger with the given configuration pub fn init_logger(config: Option) { if let Some(c) = config { let mut config_ref = LOGGING_CONFIG.lock().unwrap(); *config_ref = c.clone(); // Logger configuration successfully injected into LOGGING_CONFIG... let mut was_init_ref = WAS_INIT.lock().unwrap(); *was_init_ref = true; // .. allow logging, having ensured that paths etc are immutable } send_panic_to_log(); } /// Initializes the logger for unit and integration tests pub fn init_test_logger() { let mut was_init_ref = WAS_INIT.lock().unwrap(); if *was_init_ref.deref() { return; } let mut logger = LoggingConfig::default(); logger.log_to_file = false; logger.stdout_log_level = LogLevel::Debug; let mut config_ref = LOGGING_CONFIG.lock().unwrap(); *config_ref = logger; *was_init_ref = true; } /// hook to send panics to logs as well as stderr fn send_panic_to_log() { panic::set_hook(Box::new(|info| { let backtrace = Backtrace::new(); let thread = thread::current(); let thread = thread.name().unwrap_or("unnamed"); let msg = match info.payload().downcast_ref::<&'static str>() { Some(s) => *s, None => match info.payload().downcast_ref::() { Some(s) => &**s, None => "Box", }, }; match info.location() { Some(location) => { error!( LOGGER, "\nthread '{}' panicked at '{}': {}:{}{:?}\n\n", thread, msg, location.file(), location.line(), backtrace ); } None => error!( LOGGER, "thread '{}' panicked at '{}'{:?}", thread, msg, backtrace ), } //also print to stderr let tui_running = TUI_RUNNING.lock().unwrap().clone(); if !tui_running { eprintln!( "Thread '{}' panicked with message:\n\"{}\"\nSee grin.log for further details.", thread, msg ); } })); }