1mod debug;
4mod errors;
5mod formatter;
6mod macros;
7mod ssh;
8
9pub use errors::LogError;
10
11use once_cell::sync::Lazy;
12use std::sync::{
13 Arc,
14 atomic::{AtomicBool, AtomicU8, Ordering},
15};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
18#[repr(u8)]
19pub enum DebugVerbosity {
21 Off = 0,
23 Safe = 1,
25 Raw = 2,
27}
28
29impl DebugVerbosity {
30 pub fn from_count(count: u8) -> Self {
32 match count {
33 0 => Self::Off,
34 1 => Self::Safe,
35 _ => Self::Raw,
36 }
37 }
38
39 fn from_stored(value: u8) -> Self {
40 match value {
41 0 => Self::Off,
42 1 => Self::Safe,
43 _ => Self::Raw,
44 }
45 }
46}
47
48pub fn sanitize_session_name(raw: &str) -> String {
50 let mut sanitized = String::with_capacity(raw.len());
51 let mut has_valid = false;
52
53 for ch in raw.chars() {
54 if ch.is_ascii_alphanumeric() || matches!(ch, '.' | '_' | '-') {
55 sanitized.push(ch);
56 has_valid = true;
57 } else {
58 sanitized.push('_');
59 }
60 }
61
62 if !has_valid || sanitized == "." || sanitized == ".." {
63 return "session".to_string();
64 }
65
66 sanitized
67}
68
69static DEBUG_VERBOSITY: AtomicU8 = AtomicU8::new(DebugVerbosity::Off as u8);
71static SSH_LOGGING: AtomicBool = AtomicBool::new(false);
72
73pub static LOGGER: Lazy<Logger> = Lazy::new(Logger::new);
75
76#[derive(Debug, Clone, Copy)]
77pub enum LogLevel {
79 Debug,
80 Info,
81 Warning,
82 Error,
83}
84
85impl LogLevel {
86 fn as_str(&self) -> &'static str {
87 match self {
88 LogLevel::Debug => "DEBUG",
89 LogLevel::Info => "INFO",
90 LogLevel::Warning => "WARN",
91 LogLevel::Error => "ERROR",
92 }
93 }
94}
95
96#[derive(Clone, Default)]
97pub struct Logger {
99 debug_logger: debug::DebugLogger,
100 ssh_logger: ssh::SshLogger,
101}
102
103impl Logger {
104 pub fn new() -> Self {
106 Self::default()
107 }
108
109 pub fn enable_debug(&self) {
111 self.enable_debug_with_verbosity(DebugVerbosity::Safe);
112 }
113
114 pub fn enable_debug_with_verbosity(&self, verbosity: DebugVerbosity) {
116 DEBUG_VERBOSITY.store(verbosity as u8, Ordering::SeqCst);
117 }
118
119 pub fn disable_debug(&self) {
121 let was_enabled = self.is_debug_enabled();
122 DEBUG_VERBOSITY.store(DebugVerbosity::Off as u8, Ordering::SeqCst);
123 if was_enabled {
124 let _ = self.debug_logger.flush();
125 }
126 }
127
128 pub fn enable_ssh_logging(&self) {
130 SSH_LOGGING.store(true, Ordering::SeqCst);
131 }
132
133 pub fn disable_ssh_logging(&self) {
135 SSH_LOGGING.store(false, Ordering::SeqCst);
136 }
137
138 pub fn debug_verbosity(&self) -> DebugVerbosity {
140 DebugVerbosity::from_stored(DEBUG_VERBOSITY.load(Ordering::SeqCst))
141 }
142
143 pub fn is_debug_enabled(&self) -> bool {
145 self.debug_verbosity() >= DebugVerbosity::Safe
146 }
147
148 pub fn is_raw_debug_enabled(&self) -> bool {
150 self.debug_verbosity() >= DebugVerbosity::Raw
151 }
152
153 pub fn is_ssh_logging_enabled(&self) -> bool {
155 SSH_LOGGING.load(Ordering::SeqCst)
156 }
157
158 pub fn log_debug(&self, message: &str) -> Result<(), LogError> {
160 if self.is_debug_enabled() {
161 self.debug_logger.log(LogLevel::Debug, message)?;
162 }
163 Ok(())
164 }
165
166 pub fn log_info(&self, message: &str) -> Result<(), LogError> {
168 if self.is_debug_enabled() {
169 self.debug_logger.log(LogLevel::Info, message)?;
170 }
171 Ok(())
172 }
173
174 pub fn log_warn(&self, message: &str) -> Result<(), LogError> {
176 if self.is_debug_enabled() {
177 self.debug_logger.log(LogLevel::Warning, message)?;
178 }
179 Ok(())
180 }
181
182 pub fn log_error(&self, message: &str) -> Result<(), LogError> {
184 if self.is_debug_enabled() {
185 self.debug_logger.log(LogLevel::Error, message)?;
186 }
187 Ok(())
188 }
189
190 pub fn flush_debug(&self) -> Result<(), LogError> {
192 self.debug_logger.flush()
193 }
194
195 pub fn log_ssh(&self, message: &str) -> Result<(), LogError> {
197 if self.is_ssh_logging_enabled() {
198 self.ssh_logger.log(message)?;
199 }
200 Ok(())
201 }
202
203 pub fn log_ssh_raw(&self, message: &str) -> Result<(), LogError> {
205 if self.is_ssh_logging_enabled() {
206 self.ssh_logger.log_raw(message)?;
207 }
208 Ok(())
209 }
210
211 pub fn log_ssh_raw_shared(&self, message: Arc<String>) -> Result<(), LogError> {
213 if self.is_ssh_logging_enabled() {
214 self.ssh_logger.log_raw_shared(message)?;
215 }
216 Ok(())
217 }
218
219 pub fn flush_ssh(&self) -> Result<(), LogError> {
221 if self.is_ssh_logging_enabled() {
222 self.ssh_logger.flush()?;
223 }
224 Ok(())
225 }
226}
227
228#[cfg(test)]
229#[path = "../test/log.rs"]
230mod tests;