Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/log.rs
3309 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
use chrono::Local;
7
use opentelemetry::{
8
sdk::trace::{Tracer, TracerProvider},
9
trace::{SpanBuilder, Tracer as TraitTracer, TracerProvider as TracerProviderTrait},
10
};
11
use serde::{Deserialize, Serialize};
12
use std::fmt;
13
use std::{
14
io::Write,
15
sync::atomic::{AtomicU32, Ordering},
16
};
17
use std::{path::Path, sync::Arc};
18
19
use crate::constants::COLORS_ENABLED;
20
21
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(0);
22
23
// Gets a next incrementing number that can be used in logs
24
pub fn next_counter() -> u32 {
25
INSTANCE_COUNTER.fetch_add(1, Ordering::SeqCst)
26
}
27
28
// Log level
29
#[derive(
30
clap::ValueEnum, PartialEq, Eq, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize, Default,
31
)]
32
pub enum Level {
33
Trace = 0,
34
Debug,
35
#[default]
36
Info,
37
Warn,
38
Error,
39
Critical,
40
Off,
41
}
42
43
impl 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
57
impl 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
91
pub fn new_tunnel_prefix() -> String {
92
format!("[tunnel.{}]", next_counter())
93
}
94
95
pub fn new_code_server_prefix() -> String {
96
format!("[codeserver.{}]", next_counter())
97
}
98
99
pub fn new_rpc_prefix() -> String {
100
format!("[rpc.{}]", next_counter())
101
}
102
103
// Base logger implementation
104
#[derive(Clone)]
105
pub struct Logger {
106
tracer: Arc<Tracer>,
107
sink: Vec<Box<dyn LogSink>>,
108
prefix: Option<String>,
109
}
110
111
// Copy trick from https://stackoverflow.com/a/30353928
112
pub trait LogSinkClone {
113
fn clone_box(&self) -> Box<dyn LogSink>;
114
}
115
116
impl<T> LogSinkClone for T
117
where
118
T: 'static + LogSink + Clone,
119
{
120
fn clone_box(&self) -> Box<dyn LogSink> {
121
Box::new(self.clone())
122
}
123
}
124
125
pub 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
130
impl Clone for Box<dyn LogSink> {
131
fn clone(&self) -> Box<dyn LogSink> {
132
self.clone_box()
133
}
134
}
135
136
/// The basic log sink that writes output to stdout, with colors when relevant.
137
#[derive(Clone)]
138
pub struct StdioLogSink {
139
level: Level,
140
}
141
142
impl 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)]
157
pub struct FileLogSink {
158
level: Level,
159
file: Arc<std::sync::Mutex<std::fs::File>>,
160
}
161
162
const FILE_LOG_SIZE_LIMIT: u64 = 1024 * 1024 * 10; // 10MB
163
164
impl FileLogSink {
165
pub fn new(level: Level, path: &Path) -> std::io::Result<Self> {
166
// Truncate the service log occasionally to avoid growing infinitely
167
if matches!(path.metadata(), Ok(m) if m.len() > FILE_LOG_SIZE_LIMIT) {
168
// ignore errors, can happen if another process is writing right now
169
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
184
impl 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
// ignore any errors, not much we can do if logging fails...
193
self.file.lock().unwrap().write_all(line.as_bytes()).ok();
194
}
195
196
fn write_result(&self, _message: &str) {}
197
}
198
199
impl 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
/// Creates a new logger with the additional log sink added.
248
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
/// Creates a new logger with the sink replace with the given sink.
262
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
280
pub struct DownloadLogger<'a> {
281
prefix: &'static str,
282
logger: &'a Logger,
283
}
284
285
impl crate::util::io::ReportCopyProgress for DownloadLogger<'_> {
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
307
fn 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!("\x1b[2m[{timestamp}]\x1b[0m {c}{name}\x1b[0m {prefix}{message}\n");
316
}
317
}
318
319
format!("[{timestamp}] {name} {prefix}{message}\n")
320
}
321
322
pub fn emit(level: Level, prefix: &str, message: &str) {
323
let line = format(level, prefix, message, *COLORS_ENABLED);
324
if level == Level::Trace && *COLORS_ENABLED {
325
print!("\x1b[2m{line}\x1b[0m");
326
} else {
327
print!("{line}");
328
}
329
}
330
331
/// Installs the logger instance as the global logger for the 'log' service.
332
/// Replaces any existing registered logger. Note that the logger will be leaked/
333
pub fn install_global_logger(log: Logger) {
334
log::set_logger(Box::leak(Box::new(RustyLogger(log))))
335
.map(|()| log::set_max_level(log::LevelFilter::Debug))
336
.expect("expected to make logger");
337
}
338
339
/// Logger that uses the common rust "log" crate and directs back to one of
340
/// our managed loggers.
341
struct RustyLogger(Logger);
342
343
impl log::Log for RustyLogger {
344
fn enabled(&self, metadata: &log::Metadata) -> bool {
345
metadata.level() <= log::Level::Debug
346
}
347
348
fn log(&self, record: &log::Record) {
349
if !self.enabled(record.metadata()) {
350
return;
351
}
352
353
// exclude noisy log modules:
354
let src = match record.module_path() {
355
Some("russh::cipher" | "russh::negotiation" | "russh::kex::dh") => return,
356
Some(s) => s,
357
None => "<unknown>",
358
};
359
360
self.0.emit(
361
match record.level() {
362
log::Level::Debug => Level::Debug,
363
log::Level::Error => Level::Error,
364
log::Level::Info => Level::Info,
365
log::Level::Trace => Level::Trace,
366
log::Level::Warn => Level::Warn,
367
},
368
&format!("[{}] {}", src, record.args()),
369
);
370
}
371
372
fn flush(&self) {}
373
}
374
375
#[macro_export]
376
macro_rules! error {
377
($logger:expr, $str:expr) => {
378
$logger.emit(log::Level::Error, $str)
379
};
380
($logger:expr, $($fmt:expr),+) => {
381
$logger.emit(log::Level::Error, &format!($($fmt),+))
382
};
383
}
384
385
#[macro_export]
386
macro_rules! trace {
387
($logger:expr, $str:expr) => {
388
$logger.emit(log::Level::Trace, $str)
389
};
390
($logger:expr, $($fmt:expr),+) => {
391
$logger.emit(log::Level::Trace, &format!($($fmt),+))
392
};
393
}
394
395
#[macro_export]
396
macro_rules! debug {
397
($logger:expr, $str:expr) => {
398
$logger.emit(log::Level::Debug, $str)
399
};
400
($logger:expr, $($fmt:expr),+) => {
401
$logger.emit(log::Level::Debug, &format!($($fmt),+))
402
};
403
}
404
405
#[macro_export]
406
macro_rules! info {
407
($logger:expr, $str:expr) => {
408
$logger.emit(log::Level::Info, $str)
409
};
410
($logger:expr, $($fmt:expr),+) => {
411
$logger.emit(log::Level::Info, &format!($($fmt),+))
412
};
413
}
414
415
#[macro_export]
416
macro_rules! warning {
417
($logger:expr, $str:expr) => {
418
$logger.emit(log::Level::Warn, $str)
419
};
420
($logger:expr, $($fmt:expr),+) => {
421
$logger.emit(log::Level::Warn, &format!($($fmt),+))
422
};
423
}
424
425
#[macro_export]
426
macro_rules! span {
427
($logger:expr, $span:expr, $func:expr) => {{
428
use opentelemetry::trace::TraceContextExt;
429
430
let span = $span.start($logger.tracer());
431
let cx = opentelemetry::Context::current_with_span(span);
432
let guard = cx.clone().attach();
433
let t = $func;
434
435
if let Err(e) = &t {
436
cx.span().record_error(e);
437
}
438
439
std::mem::drop(guard);
440
441
t
442
}};
443
}
444
445
#[macro_export]
446
macro_rules! spanf {
447
($logger:expr, $span:expr, $func:expr) => {{
448
use opentelemetry::trace::{FutureExt, TraceContextExt};
449
450
let span = $span.start($logger.tracer());
451
let cx = opentelemetry::Context::current_with_span(span);
452
let t = $func.with_context(cx.clone()).await;
453
454
if let Err(e) = &t {
455
cx.span().record_error(e);
456
}
457
458
cx.span().end();
459
460
t
461
}};
462
}
463
464