1#![cfg(feature = "agave-unstable-api")]
2use 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
34pub 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
45pub 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
53pub fn setup_with_default_filter() {
55 setup_with_default(DEFAULT_FILTER);
56}
57
58pub fn setup() {
60 setup_with_default("error");
61}
62
63#[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
108pub 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}