captains_log/
file_impl.rs1use crate::{
2 config::{LogFormat, SinkConfigTrait},
3 log_impl::{LogSink, LogSinkTrait},
4 time::Timer,
5};
6use log::{Level, Record};
7use std::hash::{Hash, Hasher};
8use std::path::{Path, PathBuf};
9use std::{fs::OpenOptions, os::unix::prelude::*, sync::Arc};
10
11use arc_swap::ArcSwapOption;
12
13#[derive(Hash)]
39pub struct LogRawFile {
40 pub level: Level,
42
43 pub format: LogFormat,
44
45 pub file_path: Box<Path>,
47}
48
49impl LogRawFile {
50 pub fn new<P1, P2>(dir: P1, file_name: P2, level: Level, format: LogFormat) -> Self
56 where
57 P1: Into<PathBuf>,
58 P2: Into<PathBuf>,
59 {
60 let dir_path: PathBuf = dir.into();
61 if !dir_path.exists() {
62 std::fs::create_dir(&dir_path).expect("create dir for log");
63 }
64 let file_path = dir_path.join(file_name.into()).into_boxed_path();
65 Self { level, format, file_path }
66 }
67}
68
69impl SinkConfigTrait for LogRawFile {
70 fn get_level(&self) -> Level {
71 self.level
72 }
73
74 fn get_file_path(&self) -> Option<Box<Path>> {
75 Some(self.file_path.clone())
76 }
77
78 fn write_hash(&self, hasher: &mut Box<dyn Hasher>) {
79 self.hash(hasher);
80 hasher.write(b"LogRawFile");
81 }
82
83 fn build(&self) -> LogSink {
84 LogSink::File(LogSinkFile::new(self))
85 }
86}
87
88pub(crate) struct LogSinkFile {
89 max_level: Level,
90 path: Box<Path>,
91 f: ArcSwapOption<std::fs::File>,
93 formatter: LogFormat,
94}
95
96pub(crate) fn open_file(path: &Path) -> std::io::Result<std::fs::File> {
97 OpenOptions::new().append(true).create(true).open(path)
98}
99
100impl LogSinkFile {
101 fn new(config: &LogRawFile) -> Self {
102 Self {
103 path: config.file_path.clone(),
104 max_level: config.level,
105 formatter: config.format.clone(),
106 f: ArcSwapOption::new(None),
107 }
108 }
109}
110
111impl LogSinkTrait for LogSinkFile {
112 fn reopen(&self) -> std::io::Result<()> {
113 match open_file(&self.path) {
114 Ok(f) => {
115 self.f.store(Some(Arc::new(f)));
116 Ok(())
117 }
118 Err(e) => {
119 eprintln!("open logfile {:#?} failed: {:?}", &self.path, e);
120 Err(e)
121 }
122 }
123 }
124
125 #[inline(always)]
126 fn log(&self, now: &Timer, r: &Record) {
127 if r.level() <= self.max_level {
128 if let Some(file) = self.f.load_full() {
131 let buf = self.formatter.process(now, r);
134 unsafe {
135 let _ = libc::write(
136 file.as_raw_fd() as libc::c_int,
137 buf.as_ptr() as *const libc::c_void,
138 buf.len(),
139 );
140 }
141 }
142 }
143 }
144
145 #[inline(always)]
146 fn flush(&self) {}
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use crate::recipe;
153
154 #[test]
155 fn test_raw_file() {
156 let _file_sink = LogRawFile::new("/tmp", "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
157 let dir_path = Path::new("/tmp/test_dir");
158 if dir_path.is_dir() {
159 std::fs::remove_dir(&dir_path).expect("ok");
160 }
161 let _file_sink =
162 LogRawFile::new(&dir_path, "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
163 assert!(dir_path.is_dir());
164 std::fs::remove_dir(&dir_path).expect("ok");
165 }
166}