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)]
50pub struct LogRawFile {
51 pub level: Level,
53
54 pub format: LogFormat,
55
56 pub file_path: Box<Path>,
58}
59
60impl LogRawFile {
61 pub fn new<P1, P2>(dir: P1, file_name: P2, level: Level, format: LogFormat) -> Self
67 where
68 P1: Into<PathBuf>,
69 P2: Into<PathBuf>,
70 {
71 let dir_path: PathBuf = dir.into();
72 if !dir_path.exists() {
73 std::fs::create_dir(&dir_path).expect("create dir for log");
74 }
75 let file_path = dir_path.join(file_name.into()).into_boxed_path();
76 Self { level, format, file_path }
77 }
78}
79
80impl SinkConfigTrait for LogRawFile {
81 fn get_level(&self) -> Level {
82 self.level
83 }
84
85 fn get_file_path(&self) -> Option<Box<Path>> {
86 Some(self.file_path.clone())
87 }
88
89 fn write_hash(&self, hasher: &mut Box<dyn Hasher>) {
90 self.hash(hasher);
91 hasher.write(b"LogRawFile");
92 }
93
94 fn build(&self) -> LogSink {
95 LogSink::File(LogSinkFile::new(self))
96 }
97}
98
99pub(crate) struct LogSinkFile {
100 max_level: Level,
101 path: Box<Path>,
102 f: ArcSwapOption<std::fs::File>,
104 formatter: LogFormat,
105}
106
107pub(crate) fn open_file(path: &Path) -> std::io::Result<std::fs::File> {
108 OpenOptions::new().append(true).create(true).open(path)
109}
110
111impl LogSinkFile {
112 pub fn new(config: &LogRawFile) -> Self {
113 Self {
114 path: config.file_path.clone(),
115 max_level: config.level,
116 formatter: config.format.clone(),
117 f: ArcSwapOption::new(None),
118 }
119 }
120}
121
122impl LogSinkTrait for LogSinkFile {
123 fn reopen(&self) -> std::io::Result<()> {
124 match open_file(&self.path) {
125 Ok(f) => {
126 self.f.store(Some(Arc::new(f)));
127 Ok(())
128 }
129 Err(e) => {
130 eprintln!("open logfile {:#?} failed: {:?}", &self.path, e);
131 Err(e)
132 }
133 }
134 }
135
136 #[inline(always)]
137 fn log(&self, now: &Timer, r: &Record) {
138 if r.level() <= self.max_level {
139 if let Some(file) = self.f.load_full() {
142 let buf = self.formatter.process(now, r);
145 unsafe {
146 let _ = libc::write(
147 file.as_raw_fd() as libc::c_int,
148 buf.as_ptr() as *const libc::c_void,
149 buf.len(),
150 );
151 }
152 }
153 }
154 }
155
156 #[inline(always)]
157 fn flush(&self) {}
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::recipe;
164
165 #[test]
166 fn test_raw_file() {
167 let _file_sink = LogRawFile::new("/tmp", "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
168 let dir_path = Path::new("/tmp/test_dir");
169 if dir_path.is_dir() {
170 std::fs::remove_dir(&dir_path).expect("ok");
171 }
172 let _file_sink =
173 LogRawFile::new(&dir_path, "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
174 assert!(dir_path.is_dir());
175 std::fs::remove_dir(&dir_path).expect("ok");
176 }
177}