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)]
16pub struct LogRawFile {
17 pub level: Level,
19
20 pub format: LogFormat,
21
22 pub file_path: Box<Path>,
24}
25
26impl LogRawFile {
27 pub fn new<P1, P2>(dir: P1, file_name: P2, level: Level, format: LogFormat) -> Self
33 where
34 P1: Into<PathBuf>,
35 P2: Into<PathBuf>,
36 {
37 let dir_path: PathBuf = dir.into();
38 if !dir_path.exists() {
39 std::fs::create_dir(&dir_path).expect("create dir for log");
40 }
41 let file_path = dir_path.join(file_name.into()).into_boxed_path();
42 Self { level, format, file_path }
43 }
44}
45
46impl SinkConfigTrait for LogRawFile {
47 fn get_level(&self) -> Level {
48 self.level
49 }
50
51 fn get_file_path(&self) -> Option<Box<Path>> {
52 Some(self.file_path.clone())
53 }
54
55 fn write_hash(&self, hasher: &mut Box<dyn Hasher>) {
56 self.hash(hasher);
57 hasher.write(b"LogRawFile");
58 }
59
60 fn build(&self) -> LogSink {
61 LogSink::File(LogSinkFile::new(self))
62 }
63}
64
65pub(crate) struct LogSinkFile {
66 max_level: Level,
67 path: Box<Path>,
68 f: ArcSwapOption<std::fs::File>,
70 formatter: LogFormat,
71}
72
73pub(crate) fn open_file(path: &Path) -> std::io::Result<std::fs::File> {
74 OpenOptions::new().append(true).create(true).open(path)
75}
76
77impl LogSinkFile {
78 pub fn new(config: &LogRawFile) -> Self {
79 Self {
80 path: config.file_path.clone(),
81 max_level: config.level,
82 formatter: config.format.clone(),
83 f: ArcSwapOption::new(None),
84 }
85 }
86}
87
88impl LogSinkTrait for LogSinkFile {
89 fn reopen(&self) -> std::io::Result<()> {
90 match open_file(&self.path) {
91 Ok(f) => {
92 self.f.store(Some(Arc::new(f)));
93 Ok(())
94 }
95 Err(e) => {
96 eprintln!("open logfile {:#?} failed: {:?}", &self.path, e);
97 Err(e)
98 }
99 }
100 }
101
102 #[inline(always)]
103 fn log(&self, now: &Timer, r: &Record) {
104 if r.level() <= self.max_level {
105 if let Some(file) = self.f.load_full() {
108 let buf = self.formatter.process(now, r);
111 unsafe {
112 let _ = libc::write(
113 file.as_raw_fd() as libc::c_int,
114 buf.as_ptr() as *const libc::c_void,
115 buf.len(),
116 );
117 }
118 }
119 }
120 }
121
122 #[inline(always)]
123 fn flush(&self) {}
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::recipe;
130
131 #[test]
132 fn test_raw_file() {
133 let _file_sink = LogRawFile::new("/tmp", "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
134 let dir_path = Path::new("/tmp/test_dir");
135 if dir_path.is_dir() {
136 std::fs::remove_dir(&dir_path).expect("ok");
137 }
138 let _file_sink =
139 LogRawFile::new(&dir_path, "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
140 assert!(dir_path.is_dir());
141 std::fs::remove_dir(&dir_path).expect("ok");
142 }
143}