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