agave_logger/
lib.rs

1//! The `logger` module configures `env_logger`
2#![cfg(feature = "agave-unstable-api")]
3use std::{
4    env,
5    path::{Path, PathBuf},
6    sync::{Arc, LazyLock, RwLock},
7    thread::JoinHandle,
8};
9
10static LOGGER: LazyLock<Arc<RwLock<env_logger::Logger>>> =
11    LazyLock::new(|| Arc::new(RwLock::new(env_logger::Logger::from_default_env())));
12
13pub const DEFAULT_FILTER: &str = "solana=info,agave=info";
14
15struct LoggerShim {}
16
17impl log::Log for LoggerShim {
18    fn enabled(&self, metadata: &log::Metadata) -> bool {
19        LOGGER.read().unwrap().enabled(metadata)
20    }
21
22    fn log(&self, record: &log::Record) {
23        LOGGER.read().unwrap().log(record);
24    }
25
26    fn flush(&self) {}
27}
28
29fn replace_logger(logger: env_logger::Logger) {
30    log::set_max_level(logger.filter());
31    *LOGGER.write().unwrap() = logger;
32    let _ = log::set_boxed_logger(Box::new(LoggerShim {}));
33}
34
35// Configures logging with a specific filter overriding RUST_LOG.  _RUST_LOG is used instead
36// so if set it takes precedence.
37// May be called at any time to re-configure the log filter
38pub fn setup_with(filter: &str) {
39    let logger =
40        env_logger::Builder::from_env(env_logger::Env::new().filter_or("_RUST_LOG", filter))
41            .format_timestamp_nanos()
42            .build();
43    replace_logger(logger);
44}
45
46// Configures logging with a default filter if RUST_LOG is not set
47pub fn setup_with_default(filter: &str) {
48    let logger = env_logger::Builder::from_env(env_logger::Env::new().default_filter_or(filter))
49        .format_timestamp_nanos()
50        .build();
51    replace_logger(logger);
52}
53
54// Configures logging with the `DEFAULT_FILTER` if RUST_LOG is not set
55pub fn setup_with_default_filter() {
56    setup_with_default(DEFAULT_FILTER);
57}
58
59// Configures logging with the default filter "error" if RUST_LOG is not set
60pub fn setup() {
61    setup_with_default("error");
62}
63
64// Configures file logging with a default filter if RUST_LOG is not set
65pub fn setup_file_with_default(logfile: &Path, filter: &str) {
66    use std::fs::OpenOptions;
67    let file = OpenOptions::new()
68        .create(true)
69        .append(true)
70        .open(logfile)
71        .unwrap();
72    let logger = env_logger::Builder::from_env(env_logger::Env::new().default_filter_or(filter))
73        .format_timestamp_nanos()
74        .target(env_logger::Target::Pipe(Box::new(file)))
75        .build();
76    replace_logger(logger);
77}
78
79#[cfg(unix)]
80fn redirect_stderr(filename: &Path) {
81    use std::{fs::OpenOptions, os::unix::io::AsRawFd};
82    match OpenOptions::new().create(true).append(true).open(filename) {
83        Ok(file) => unsafe {
84            libc::dup2(file.as_raw_fd(), libc::STDERR_FILENO);
85        },
86        Err(err) => eprintln!("Unable to open {}: {err}", filename.display()),
87    }
88}
89
90// Redirect stderr to a file with support for logrotate by sending a SIGUSR1 to the process.
91//
92// Upon success, future `log` macros and `eprintln!()` can be found in the specified log file.
93pub fn redirect_stderr_to_file(logfile: Option<PathBuf>) -> Option<JoinHandle<()>> {
94    // Default to RUST_BACKTRACE=1 for more informative validator logs
95    if env::var_os("RUST_BACKTRACE").is_none() {
96        env::set_var("RUST_BACKTRACE", "1")
97    }
98
99    match logfile {
100        None => {
101            setup_with_default_filter();
102            None
103        }
104        Some(logfile) => {
105            #[cfg(unix)]
106            {
107                use log::info;
108                let mut signals =
109                    signal_hook::iterator::Signals::new([signal_hook::consts::SIGUSR1])
110                        .unwrap_or_else(|err| {
111                            eprintln!("Unable to register SIGUSR1 handler: {err:?}");
112                            std::process::exit(1);
113                        });
114
115                setup_with_default_filter();
116                redirect_stderr(&logfile);
117                Some(
118                    std::thread::Builder::new()
119                        .name("solSigUsr1".into())
120                        .spawn(move || {
121                            for signal in signals.forever() {
122                                info!(
123                                    "received SIGUSR1 ({signal}), reopening log file: {logfile:?}",
124                                );
125                                redirect_stderr(&logfile);
126                            }
127                        })
128                        .unwrap(),
129                )
130            }
131            #[cfg(not(unix))]
132            {
133                println!("logrotate is not supported on this platform");
134                setup_file_with_default(&logfile, DEFAULT_FILTER);
135                None
136            }
137        }
138    }
139}