nitro_cli/common/
logger.rs1#![deny(missing_docs)]
4#![deny(warnings)]
5
6use chrono::offset::{Local, Utc};
7use chrono::DateTime;
8use flexi_logger::writers::LogWriter;
9use flexi_logger::{DeferredNow, Record};
10use nix::unistd::Uid;
11use std::env;
12use std::fs::{File, OpenOptions, Permissions};
13use std::io::{Error, Result, Write};
14use std::ops::{Deref, DerefMut};
15use std::os::unix::fs::{MetadataExt, PermissionsExt};
16use std::path::{Path, PathBuf};
17use std::sync::{Arc, Mutex};
18
19use crate::common::{NitroCliErrorEnum, NitroCliFailure, NitroCliResult};
20use crate::new_nitro_cli_failure;
21
22const DEFAULT_LOG_LEVEL: &str = "info";
24
25const LOGS_DIR_PATH_ENV_VAR: &str = "NITRO_CLI_LOGS_PATH";
27
28const LOGS_DIR_PATH: &str = "/var/log/nitro_enclaves";
30
31const LOG_FILE_NAME: &str = "nitro_enclaves.log";
33
34#[derive(Clone)]
39pub struct EnclaveProcLogWriter {
40 out_file: Arc<Mutex<File>>,
41 logger_id: Arc<Mutex<String>>,
42}
43
44impl EnclaveProcLogWriter {
45 pub fn new() -> NitroCliResult<Self> {
47 Ok(EnclaveProcLogWriter {
49 out_file: Arc::new(Mutex::new(
50 open_log_file(&get_log_file_path())
51 .map_err(|e| e.add_subaction("Failed to open log file".to_string()))?,
52 )),
53 logger_id: Arc::new(Mutex::new(String::new())),
54 })
55 }
56
57 fn safe_open_log_file(&self) -> NitroCliResult<()> {
59 let log_path = &get_log_file_path();
60 if !log_path.exists() {
61 let new_file = open_log_file(log_path)
62 .map_err(|e| e.add_subaction(String::from("Failed to open log file")))?;
63 let mut file_ref = self.out_file.lock().map_err(|e| {
64 new_nitro_cli_failure!(
65 &format!("Failed to acquire lock: {e:?}"),
66 NitroCliErrorEnum::LockAcquireFailure
67 )
68 })?;
69 *file_ref.deref_mut() = new_file;
70 }
71
72 Ok(())
73 }
74
75 pub fn update_logger_id(&self, new_id: &str) -> NitroCliResult<()> {
77 let mut old_id = self.logger_id.lock().map_err(|e| {
78 new_nitro_cli_failure!(
79 &format!("Failed to acquire logger ID lock: {e:?}"),
80 NitroCliErrorEnum::LockAcquireFailure
81 )
82 })?;
83 old_id.deref_mut().clear();
84 old_id.deref_mut().push_str(new_id);
85
86 Ok(())
87 }
88
89 fn create_msg(&self, now: &DateTime<Local>, record: &Record) -> NitroCliResult<String> {
91 let timestamp = DateTime::<Utc>::from_naive_utc_and_offset(now.naive_utc(), Utc)
93 .to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
94 let logger_id = self.logger_id.lock().map_err(|e| {
95 new_nitro_cli_failure!(
96 &format!("Failed to acquire logger ID lock: {e:?}"),
97 NitroCliErrorEnum::LockAcquireFailure
98 )
99 })?;
100 Ok(format!(
101 "[{}][{}][{}][{}:{}] {}\n",
102 logger_id.deref(),
103 record.level(),
104 timestamp,
105 record.file().unwrap_or("?"),
106 record.line().unwrap_or(0),
107 record.args()
108 ))
109 }
110}
111
112impl LogWriter for EnclaveProcLogWriter {
113 fn write(&self, now: &mut DeferredNow, record: &Record) -> Result<()> {
114 if self.safe_open_log_file().is_err() {
115 return Err(Error::other("Failed to safely open log file for writing"));
116 }
117
118 if let Ok(record_str) = self.create_msg(now.now(), record) {
119 if let Ok(mut out_file) = self.out_file.lock() {
120 out_file.deref_mut().write_all(record_str.as_bytes())?;
121
122 return Ok(());
123 }
124
125 return Err(Error::other("Failed to lock log file"));
126 }
127
128 Err(Error::other("Failed to create logger message"))
129 }
130
131 fn flush(&self) -> Result<()> {
132 Ok(())
133 }
134
135 fn max_log_level(&self) -> log::LevelFilter {
136 let level = std::env::var("RUST_LOG").unwrap_or_else(|_| DEFAULT_LOG_LEVEL.to_string());
138
139 match level.to_lowercase().as_ref() {
140 "info" => log::LevelFilter::Info,
141 "debug" => log::LevelFilter::Debug,
142 "warn" => log::LevelFilter::Warn,
143 "error" => log::LevelFilter::Error,
144 "trace" => log::LevelFilter::Trace,
145 _ => log::LevelFilter::Info,
146 }
147 }
148}
149
150pub fn get_log_file_base_path() -> String {
152 match env::var(LOGS_DIR_PATH_ENV_VAR) {
153 Ok(env_path) => env_path,
154 Err(_) => LOGS_DIR_PATH.to_string(),
155 }
156}
157
158fn get_log_file_path() -> PathBuf {
160 Path::new(&get_log_file_base_path()).join(LOG_FILE_NAME)
161}
162
163fn open_log_file(file_path: &Path) -> NitroCliResult<File> {
165 let file = OpenOptions::new()
166 .create(true)
167 .append(true)
168 .read(false)
169 .open(file_path)
170 .map_err(|e| {
171 new_nitro_cli_failure!(
172 &format!("Failed to open log file: {e:?}"),
173 NitroCliErrorEnum::FileOperationFailure
174 )
175 .add_info(vec![
176 file_path
177 .to_str()
178 .unwrap_or("Invalid unicode log file name"),
179 "Open",
180 ])
181 })?;
182
183 let log_file_uid = Uid::from_raw(
184 file.metadata()
185 .map_err(|e| {
186 new_nitro_cli_failure!(
187 &format!("Failed to get log file metadata: {e:?}"),
188 NitroCliErrorEnum::FileOperationFailure
189 )
190 .add_info(vec![
191 file_path
192 .to_str()
193 .unwrap_or("Invalid unicode log file name"),
194 "Get metadata",
195 ])
196 })?
197 .uid(),
198 );
199
200 if log_file_uid == Uid::current() {
204 let perms = Permissions::from_mode(0o766);
205 file.set_permissions(perms).map_err(|e| {
206 new_nitro_cli_failure!(
207 &format!("Failed to change log file permissions: {e:?}"),
208 NitroCliErrorEnum::FilePermissionsError
209 )
210 })?;
211 }
212
213 Ok(file)
214}
215
216pub fn init_logger() -> NitroCliResult<EnclaveProcLogWriter> {
218 let log_writer = EnclaveProcLogWriter::new()?;
220
221 flexi_logger::Logger::try_with_env_or_str(DEFAULT_LOG_LEVEL)
223 .map_err(|e| {
224 new_nitro_cli_failure!(
225 &format!("Failed to initialize enclave process logger: {e:?}"),
226 NitroCliErrorEnum::LoggerError
227 )
228 })?
229 .log_to_writer(Box::new(log_writer.clone()))
230 .start()
231 .map_err(|e| {
232 new_nitro_cli_failure!(
233 &format!("Failed to initialize enclave process logger: {e:?}"),
234 NitroCliErrorEnum::LoggerError
235 )
236 })?;
237
238 Ok(log_writer)
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 use std::fs;
247 use std::os::unix::fs::PermissionsExt;
248
249 use tempfile::NamedTempFile;
250
251 #[test]
254 fn test_open_log_file() {
255 let file0 = NamedTempFile::new();
256
257 if let Ok(file0) = file0 {
258 let test_file_path = file0.path();
259
260 let f = open_log_file(test_file_path).unwrap();
261 let metadata = f.metadata();
262 assert!(metadata.is_ok());
263
264 if let Ok(metadata) = metadata {
265 assert!(metadata.is_file());
266 let permissions = metadata.permissions();
267 let mode = permissions.mode();
268
269 assert_eq!(mode & 0o777, 0o766);
270 }
271 }
272 }
273
274 #[test]
276 fn test_init_logger() {
277 let tmp_log_dir: &str = "./.tmp_logs_init_logger";
278
279 let old_log_path = env::var(LOGS_DIR_PATH_ENV_VAR);
281 let path_existed = Path::new(tmp_log_dir).exists();
282
283 env::set_var(LOGS_DIR_PATH_ENV_VAR, tmp_log_dir);
285 let _ = fs::create_dir(tmp_log_dir);
286
287 let log_writer = EnclaveProcLogWriter::new().unwrap();
288 let lock_result = log_writer.logger_id.lock();
289
290 assert!(lock_result.unwrap().is_empty());
291
292 if !path_existed {
293 let _ = fs::remove_dir_all(tmp_log_dir);
295 } else {
296 let _ = fs::remove_file(format!("{}/{}", tmp_log_dir, &LOG_FILE_NAME));
298 }
299
300 if let Ok(old_log_path) = old_log_path {
302 env::set_var(LOGS_DIR_PATH_ENV_VAR, old_log_path);
303 }
304 }
305
306 #[test]
309 fn test_update_logger_id() {
310 let tmp_log_dir: &str = "./.tmp_logs_update_logger_id";
311
312 let old_log_path = env::var(LOGS_DIR_PATH_ENV_VAR);
314 let path_existed = Path::new(tmp_log_dir).exists();
315
316 env::set_var(LOGS_DIR_PATH_ENV_VAR, tmp_log_dir);
318 let _ = fs::create_dir(tmp_log_dir);
319
320 let log_writer = EnclaveProcLogWriter::new().unwrap();
321 log_writer.update_logger_id("new-logger-id").unwrap();
322 let lock_result = log_writer.logger_id.lock();
323
324 assert!(lock_result.unwrap().eq("new-logger-id"));
325
326 log_writer.update_logger_id("").unwrap();
327
328 if !path_existed {
329 let _ = fs::remove_dir_all(tmp_log_dir);
331 } else {
332 let _ = fs::remove_file(format!("{}/{}", tmp_log_dir, &LOG_FILE_NAME));
334 }
335
336 if let Ok(old_log_path) = old_log_path {
338 env::set_var(LOGS_DIR_PATH_ENV_VAR, old_log_path);
339 }
340 }
341}