1use crate::Result;
3use rev_buf_reader::RevBufReader;
4use sos_core::{Paths, UtcDateTime};
5use std::{
6 fs::File,
7 io::BufRead,
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11use time::OffsetDateTime;
12use tracing_appender::rolling::{RollingFileAppender, Rotation};
13use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
14
15#[doc(hidden)]
17pub const LOG_FILE_NAME: &str = "saveoursecrets.log";
18
19const DEFAULT_LOG_LEVEL: &str =
20 "sos=info,sos_net=debug,sos_bindings=debug,sos_backend=debug,sos_database=debug,sos_protocol=debug,sos_app=debug,sos_database_upgrader=debug";
21
22pub struct LogFileStatus {
24 pub current: PathBuf,
26 pub current_size: u64,
28 pub log_files: Vec<PathBuf>,
30 pub total_size: u64,
32}
33
34pub struct Logger {
40 logs_dir: PathBuf,
41 name: String,
42}
43
44impl Default for Logger {
45 fn default() -> Self {
46 Self::new(None)
47 }
48}
49
50impl Logger {
51 pub fn new(name: Option<String>) -> Self {
57 Self::new_paths(Paths::new_client(Paths::data_dir().unwrap()), name)
58 }
59
60 pub fn new_paths(paths: Arc<Paths>, name: Option<String>) -> Self {
62 let logs_dir = paths.logs_dir().to_owned();
63 Self {
64 logs_dir,
65 name: name.unwrap_or(LOG_FILE_NAME.to_string()),
66 }
67 }
68
69 pub fn new_dir(logs_dir: PathBuf, name: String) -> Self {
71 Self { logs_dir, name }
72 }
73
74 #[cfg(debug_assertions)]
76 pub fn init_subscriber(
77 &self,
78 default_log_level: Option<String>,
79 ) -> Result<()> {
80 let logs_dir = &self.logs_dir;
81
82 let logfile =
83 RollingFileAppender::new(Rotation::DAILY, logs_dir, &self.name);
84 let default_log_level =
85 default_log_level.unwrap_or_else(|| DEFAULT_LOG_LEVEL.to_owned());
86 let env_layer = tracing_subscriber::EnvFilter::new(
87 std::env::var("RUST_LOG").unwrap_or(default_log_level),
88 );
89
90 let file_layer = tracing_subscriber::fmt::layer()
91 .with_file(false)
92 .with_line_number(false)
93 .with_ansi(false)
94 .json()
95 .with_writer(logfile);
96
97 let fmt_layer = tracing_subscriber::fmt::layer()
98 .with_file(false)
99 .with_line_number(false)
100 .with_writer(std::io::stderr)
101 .with_target(false);
102
103 let _ = tracing_subscriber::registry()
106 .with(env_layer)
107 .with(fmt_layer)
108 .with(file_layer)
109 .try_init();
110
111 Ok(())
112 }
113
114 pub fn init_file_subscriber(
116 &self,
117 default_log_level: Option<String>,
118 ) -> Result<()> {
119 let logfile = RollingFileAppender::new(
120 Rotation::DAILY,
121 &self.logs_dir,
122 &self.name,
123 );
124 let default_log_level =
125 default_log_level.unwrap_or_else(|| DEFAULT_LOG_LEVEL.to_owned());
126 let env_layer = tracing_subscriber::EnvFilter::new(
127 std::env::var("RUST_LOG").unwrap_or(default_log_level),
128 );
129
130 let file_layer = tracing_subscriber::fmt::layer()
131 .with_file(false)
132 .with_line_number(false)
133 .with_ansi(false)
134 .json()
135 .with_writer(logfile);
136
137 let _ = tracing_subscriber::registry()
140 .with(env_layer)
141 .with(file_layer)
142 .try_init();
143
144 Ok(())
145 }
146
147 #[cfg(not(debug_assertions))]
149 pub fn init_subscriber(
150 &self,
151 default_log_level: Option<String>,
152 ) -> Result<()> {
153 self.init_file_subscriber(default_log_level)
154 }
155
156 pub fn status(&self) -> Result<LogFileStatus> {
158 let current = self.current_log_file()?;
159 let current_size = std::fs::metadata(¤t)?.len();
160 let log_files = self.log_file_paths()?;
161 let mut total_size = 0;
162 for path in &log_files {
163 total_size += std::fs::metadata(path)?.len();
164 }
165 Ok(LogFileStatus {
166 current,
167 current_size,
168 log_files,
169 total_size,
170 })
171 }
172
173 pub fn tail(&self, num_lines: Option<usize>) -> Result<Vec<String>> {
175 self.tail_file(num_lines, self.current_log_file()?)
176 }
177
178 pub fn tail_file(
180 &self,
181 num_lines: Option<usize>,
182 path: impl AsRef<Path>,
183 ) -> Result<Vec<String>> {
184 let num_lines = num_lines.unwrap_or(100);
185 let file = File::open(path.as_ref())?;
186 let buf = RevBufReader::new(file);
187 let logs: Vec<String> =
188 buf.lines().take(num_lines).filter_map(|l| l.ok()).collect();
189 Ok(logs)
190 }
191
192 pub fn delete_logs(&self) -> Result<()> {
194 let current = self.current_log_file()?;
195 let log_files = self.log_file_paths()?;
196 for path in log_files {
197 if path != current {
198 std::fs::remove_file(path)?;
199 }
200 }
201 let _ = std::fs::OpenOptions::new()
204 .write(true)
205 .truncate(true)
206 .open(¤t);
207 Ok(())
208 }
209
210 fn log_file_paths(&self) -> Result<Vec<PathBuf>> {
212 let mut files = Vec::new();
213 for entry in std::fs::read_dir(&self.logs_dir)? {
214 let entry = entry?;
215 let path = entry.path();
216 if let Some(name) = path.file_name() {
217 if name.to_string_lossy().starts_with(&self.name) {
218 files.push(path);
219 }
220 }
221 }
222 Ok(files)
223 }
224
225 fn current_log_file(&self) -> Result<PathBuf> {
227 let now: UtcDateTime = OffsetDateTime::now_utc().into();
228 let file = self.logs_dir.join(format!(
229 "{}.{}",
230 self.name,
231 now.format_simple_date()?
232 ));
233 Ok(file)
234 }
235}