1use chrono::Local;
7use opentelemetry::{
8 sdk::trace::{Tracer, TracerProvider},
9 trace::{SpanBuilder, Tracer as TraitTracer, TracerProvider as TracerProviderTrait},
10};
11use serde::{Deserialize, Serialize};
12use std::fmt;
13use std::{
14 io::Write,
15 sync::atomic::{AtomicU32, Ordering},
16};
17use std::{path::Path, sync::Arc};
18
19use crate::constants::COLORS_ENABLED;
20
21static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(0);
22
23pub fn next_counter() -> u32 {
25 INSTANCE_COUNTER.fetch_add(1, Ordering::SeqCst)
26}
27
28#[derive(
30 clap::ValueEnum, PartialEq, Eq, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize, Default,
31)]
32pub enum Level {
33 Trace = 0,
34 Debug,
35 #[default]
36 Info,
37 Warn,
38 Error,
39 Critical,
40 Off,
41}
42
43impl fmt::Display for Level {
44 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45 match self {
46 Level::Critical => write!(f, "critical"),
47 Level::Debug => write!(f, "debug"),
48 Level::Error => write!(f, "error"),
49 Level::Info => write!(f, "info"),
50 Level::Off => write!(f, "off"),
51 Level::Trace => write!(f, "trace"),
52 Level::Warn => write!(f, "warn"),
53 }
54 }
55}
56
57impl Level {
58 pub fn name(&self) -> Option<&str> {
59 match self {
60 Level::Trace => Some("trace"),
61 Level::Debug => Some("debug"),
62 Level::Info => Some("info"),
63 Level::Warn => Some("warn"),
64 Level::Error => Some("error"),
65 Level::Critical => Some("critical"),
66 Level::Off => None,
67 }
68 }
69
70 pub fn color_code(&self) -> Option<&str> {
71 if !*COLORS_ENABLED {
72 return None;
73 }
74
75 match self {
76 Level::Trace => None,
77 Level::Debug => Some("\x1b[36m"),
78 Level::Info => Some("\x1b[35m"),
79 Level::Warn => Some("\x1b[33m"),
80 Level::Error => Some("\x1b[31m"),
81 Level::Critical => Some("\x1b[31m"),
82 Level::Off => None,
83 }
84 }
85
86 pub fn to_u8(self) -> u8 {
87 self as u8
88 }
89}
90
91pub fn new_tunnel_prefix() -> String {
92 format!("[tunnel.{}]", next_counter())
93}
94
95pub fn new_code_server_prefix() -> String {
96 format!("[codeserver.{}]", next_counter())
97}
98
99pub fn new_rpc_prefix() -> String {
100 format!("[rpc.{}]", next_counter())
101}
102
103#[derive(Clone)]
105pub struct Logger {
106 tracer: Arc<Tracer>,
107 sink: Vec<Box<dyn LogSink>>,
108 prefix: Option<String>,
109}
110
111pub trait LogSinkClone {
113 fn clone_box(&self) -> Box<dyn LogSink>;
114}
115
116impl<T> LogSinkClone for T
117where
118 T: 'static + LogSink + Clone,
119{
120 fn clone_box(&self) -> Box<dyn LogSink> {
121 Box::new(self.clone())
122 }
123}
124
125pub trait LogSink: LogSinkClone + Sync + Send {
126 fn write_log(&self, level: Level, prefix: &str, message: &str);
127 fn write_result(&self, message: &str);
128}
129
130impl Clone for Box<dyn LogSink> {
131 fn clone(&self) -> Box<dyn LogSink> {
132 self.clone_box()
133 }
134}
135
136#[derive(Clone)]
138pub struct StdioLogSink {
139 level: Level,
140}
141
142impl LogSink for StdioLogSink {
143 fn write_log(&self, level: Level, prefix: &str, message: &str) {
144 if level < self.level {
145 return;
146 }
147
148 emit(level, prefix, message);
149 }
150
151 fn write_result(&self, message: &str) {
152 println!("{}", message);
153 }
154}
155
156#[derive(Clone)]
157pub struct FileLogSink {
158 level: Level,
159 file: Arc<std::sync::Mutex<std::fs::File>>,
160}
161
162const FILE_LOG_SIZE_LIMIT: u64 = 1024 * 1024 * 10; impl FileLogSink {
165 pub fn new(level: Level, path: &Path) -> std::io::Result<Self> {
166 if matches!(path.metadata(), Ok(m) if m.len() > FILE_LOG_SIZE_LIMIT) {
168 let _ = std::fs::remove_file(path);
170 }
171
172 let file = std::fs::OpenOptions::new()
173 .append(true)
174 .create(true)
175 .open(path)?;
176
177 Ok(Self {
178 level,
179 file: Arc::new(std::sync::Mutex::new(file)),
180 })
181 }
182}
183
184impl LogSink for FileLogSink {
185 fn write_log(&self, level: Level, prefix: &str, message: &str) {
186 if level < self.level {
187 return;
188 }
189
190 let line = format(level, prefix, message, false);
191
192 self.file.lock().unwrap().write_all(line.as_bytes()).ok();
194 }
195
196 fn write_result(&self, _message: &str) {}
197}
198
199impl Logger {
200 pub fn test() -> Self {
201 Self {
202 tracer: Arc::new(TracerProvider::builder().build().tracer("codeclitest")),
203 sink: vec![],
204 prefix: None,
205 }
206 }
207
208 pub fn new(tracer: Tracer, level: Level) -> Self {
209 Self {
210 tracer: Arc::new(tracer),
211 sink: vec![Box::new(StdioLogSink { level })],
212 prefix: None,
213 }
214 }
215
216 pub fn span(&self, name: &str) -> SpanBuilder {
217 self.tracer.span_builder(format!("serverlauncher/{}", name))
218 }
219
220 pub fn tracer(&self) -> &Tracer {
221 &self.tracer
222 }
223
224 pub fn emit(&self, level: Level, message: &str) {
225 let prefix = self.prefix.as_deref().unwrap_or("");
226 for sink in &self.sink {
227 sink.write_log(level, prefix, message);
228 }
229 }
230
231 pub fn result(&self, message: impl AsRef<str>) {
232 for sink in &self.sink {
233 sink.write_result(message.as_ref());
234 }
235 }
236
237 pub fn prefixed(&self, prefix: &str) -> Logger {
238 Logger {
239 prefix: Some(match &self.prefix {
240 Some(p) => format!("{}{} ", p, prefix),
241 None => format!("{} ", prefix),
242 }),
243 ..self.clone()
244 }
245 }
246
247 pub fn tee<T>(&self, sink: T) -> Logger
249 where
250 T: LogSink + 'static,
251 {
252 let mut new_sinks = self.sink.clone();
253 new_sinks.push(Box::new(sink));
254
255 Logger {
256 sink: new_sinks,
257 ..self.clone()
258 }
259 }
260
261 pub fn with_sink<T>(&self, sink: T) -> Logger
263 where
264 T: LogSink + 'static,
265 {
266 Logger {
267 sink: vec![Box::new(sink)],
268 ..self.clone()
269 }
270 }
271
272 pub fn get_download_logger<'a>(&'a self, prefix: &'static str) -> DownloadLogger<'a> {
273 DownloadLogger {
274 prefix,
275 logger: self,
276 }
277 }
278}
279
280pub struct DownloadLogger<'a> {
281 prefix: &'static str,
282 logger: &'a Logger,
283}
284
285impl<'a> crate::util::io::ReportCopyProgress for DownloadLogger<'a> {
286 fn report_progress(&mut self, bytes_so_far: u64, total_bytes: u64) {
287 if total_bytes > 0 {
288 self.logger.emit(
289 Level::Trace,
290 &format!(
291 "{} {}/{} ({:.0}%)",
292 self.prefix,
293 bytes_so_far,
294 total_bytes,
295 (bytes_so_far as f64 / total_bytes as f64) * 100.0,
296 ),
297 );
298 } else {
299 self.logger.emit(
300 Level::Trace,
301 &format!("{} {}/{}", self.prefix, bytes_so_far, total_bytes,),
302 );
303 }
304 }
305}
306
307fn format(level: Level, prefix: &str, message: &str, use_colors: bool) -> String {
308 let current = Local::now();
309 let timestamp = current.format("%Y-%m-%d %H:%M:%S").to_string();
310
311 let name = level.name().unwrap();
312
313 if use_colors {
314 if let Some(c) = level.color_code() {
315 return format!(
316 "\x1b[2m[{}]\x1b[0m {}{}\x1b[0m {}{}\n",
317 timestamp, c, name, prefix, message
318 );
319 }
320 }
321
322 format!("[{}] {} {}{}\n", timestamp, name, prefix, message)
323}
324
325pub fn emit(level: Level, prefix: &str, message: &str) {
326 let line = format(level, prefix, message, *COLORS_ENABLED);
327 if level == Level::Trace && *COLORS_ENABLED {
328 print!("\x1b[2m{}\x1b[0m", line);
329 } else {
330 print!("{}", line);
331 }
332}
333
334pub fn install_global_logger(log: Logger) {
337 log::set_logger(Box::leak(Box::new(RustyLogger(log))))
338 .map(|()| log::set_max_level(log::LevelFilter::Debug))
339 .expect("expected to make logger");
340}
341
342struct RustyLogger(Logger);
345
346impl log::Log for RustyLogger {
347 fn enabled(&self, metadata: &log::Metadata) -> bool {
348 metadata.level() <= log::Level::Debug
349 }
350
351 fn log(&self, record: &log::Record) {
352 if !self.enabled(record.metadata()) {
353 return;
354 }
355
356 let src = match record.module_path() {
358 Some("russh::cipher" | "russh::negotiation" | "russh::kex::dh") => return,
359 Some(s) => s,
360 None => "<unknown>",
361 };
362
363 self.0.emit(
364 match record.level() {
365 log::Level::Debug => Level::Debug,
366 log::Level::Error => Level::Error,
367 log::Level::Info => Level::Info,
368 log::Level::Trace => Level::Trace,
369 log::Level::Warn => Level::Warn,
370 },
371 &format!("[{}] {}", src, record.args()),
372 );
373 }
374
375 fn flush(&self) {}
376}
377
378#[macro_export]
379macro_rules! error {
380 ($logger:expr, $str:expr) => {
381 $logger.emit(log::Level::Error, $str)
382 };
383 ($logger:expr, $($fmt:expr),+) => {
384 $logger.emit(log::Level::Error, &format!($($fmt),+))
385 };
386 }
387
388#[macro_export]
389macro_rules! trace {
390 ($logger:expr, $str:expr) => {
391 $logger.emit(log::Level::Trace, $str)
392 };
393 ($logger:expr, $($fmt:expr),+) => {
394 $logger.emit(log::Level::Trace, &format!($($fmt),+))
395 };
396 }
397
398#[macro_export]
399macro_rules! debug {
400 ($logger:expr, $str:expr) => {
401 $logger.emit(log::Level::Debug, $str)
402 };
403 ($logger:expr, $($fmt:expr),+) => {
404 $logger.emit(log::Level::Debug, &format!($($fmt),+))
405 };
406 }
407
408#[macro_export]
409macro_rules! info {
410 ($logger:expr, $str:expr) => {
411 $logger.emit(log::Level::Info, $str)
412 };
413 ($logger:expr, $($fmt:expr),+) => {
414 $logger.emit(log::Level::Info, &format!($($fmt),+))
415 };
416 }
417
418#[macro_export]
419macro_rules! warning {
420 ($logger:expr, $str:expr) => {
421 $logger.emit(log::Level::Warn, $str)
422 };
423 ($logger:expr, $($fmt:expr),+) => {
424 $logger.emit(log::Level::Warn, &format!($($fmt),+))
425 };
426 }
427
428#[macro_export]
429macro_rules! span {
430 ($logger:expr, $span:expr, $func:expr) => {{
431 use opentelemetry::trace::TraceContextExt;
432
433 let span = $span.start($logger.tracer());
434 let cx = opentelemetry::Context::current_with_span(span);
435 let guard = cx.clone().attach();
436 let t = $func;
437
438 if let Err(e) = &t {
439 cx.span().record_error(e);
440 }
441
442 std::mem::drop(guard);
443
444 t
445 }};
446}
447
448#[macro_export]
449macro_rules! spanf {
450 ($logger:expr, $span:expr, $func:expr) => {{
451 use opentelemetry::trace::{FutureExt, TraceContextExt};
452
453 let span = $span.start($logger.tracer());
454 let cx = opentelemetry::Context::current_with_span(span);
455 let t = $func.with_context(cx.clone()).await;
456
457 if let Err(e) = &t {
458 cx.span().record_error(e);
459 }
460
461 cx.span().end();
462
463 t
464 }};
465}