Skip to main content

agave_logger/
lib.rs

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