1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
use crate::cmd::dap_server::{
DebuggerError,
debug_adapter::{dap::adapter::DebugAdapter, protocol::ProtocolAdapter},
};
use parking_lot::{Mutex, MutexGuard};
use std::{
fs::File,
io::{Write, stderr},
path::Path,
sync::Arc,
};
use tracing::{level_filters::LevelFilter, subscriber::DefaultGuard};
use tracing_subscriber::{
EnvFilter, Layer,
fmt::{MakeWriter, format::FmtSpan},
prelude::__tracing_subscriber_SubscriberExt,
util::SubscriberInitExt,
};
/// DebugLogger manages the temporary file that is used to store the tracing messages that are generated during the DAP sessions.
/// For portions of the Debugger lifetime where no DAP session is active, the tracing messages are sent to `stderr`.
#[derive(Clone)]
pub(crate) struct DebugLogger {
/// This is a temporary buffer that is periodically flushed.
/// - When the DAP server is running, the tracing messages are sent to the console.
/// - When the DAP server exits, the remaining messages in this buffer are sent to `stderr`.
buffer: Arc<Mutex<Vec<u8>>>,
/// We need to hold onto the tracing `DefaultGuard` for the `lifetime of DebugLogger`.
/// Using the `DefaultGuard` will ensure that the tracing subscriber be dropped when the `DebugLogger`
/// is dropped at the end of the `Debugger` lifetime. If we don't set it up this way,
/// the tests will fail because a default subscriber is already set.
log_default_guard: Arc<Option<DefaultGuard>>,
}
#[derive(Clone)]
struct WriterWrapper(Arc<Mutex<Vec<u8>>>);
/// Get a handle to the buffered log file, so that `tracing` can write to it.
///
impl MakeWriter<'_> for WriterWrapper {
type Writer = Self;
fn make_writer(&self) -> Self::Writer {
self.clone()
}
}
impl Write for WriterWrapper {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let mut locked_log = self.0.lock();
locked_log.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
let mut locked_log = self.0.lock();
locked_log.flush()
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
let mut locked_log = self.0.lock();
locked_log.write_all(buf)
}
fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> {
let mut locked_log = self.0.lock();
locked_log.write_fmt(fmt)
}
}
impl DebugLogger {
/// Create a new DebugTraceFile instance
pub(crate) fn new(log_file: Option<&Path>) -> Result<Self, DebuggerError> {
let mut debug_logger = Self {
buffer: Arc::new(Mutex::new(Vec::new())),
log_default_guard: Arc::new(None),
};
debug_logger.log_default_guard = Arc::new(Some(debug_logger.setup_logging(log_file)?));
Ok(debug_logger)
}
fn locked_buffer(&self) -> MutexGuard<Vec<u8>> {
self.buffer.lock()
}
fn process_new_log_lines(&self, mut callback: impl FnMut(&str)) -> Result<(), DebuggerError> {
let new_bytes = {
let mut locked_log = self.buffer.lock();
std::mem::take(&mut *locked_log)
};
let new = String::from_utf8_lossy(&new_bytes);
let buffer_lines = new.lines();
for next_line in buffer_lines {
callback(next_line);
}
Ok(())
}
/// Flush the buffer to the DAP client's Debug Console
pub(crate) fn flush_to_dap(
&self,
debug_adapter: &mut DebugAdapter<impl ProtocolAdapter>,
) -> Result<(), DebuggerError> {
self.process_new_log_lines(|line| {
debug_adapter.log_to_console(line);
})
}
/// Flush the buffer to the stderr
pub(crate) fn flush(&self) -> Result<(), DebuggerError> {
self.process_new_log_lines(|line| eprintln!("{}", line))
}
/// Setup logging, according to the following rules.
/// 1. If the RUST_LOG environment variable is set, use it as a `LevelFilter` to configure a subscriber that
/// logs to the given destination, or default to `RUST_LOG=probe_rs_debug=warn`
/// 2. If no `log_file` destination is supplied, output will be written to the DAP client's Debug Console,
/// 3. Irrespective of the RUST_LOG environment variable, configure a subscriber that will write with `LevelFilter::ERROR` to stderr,
/// because these errors are picked up and reported to the user by the VSCode extension, when no DAP session is available.
pub fn setup_logging(
&mut self,
log_file: Option<&Path>,
) -> Result<DefaultGuard, DebuggerError> {
let environment_filter = if std::env::var("RUST_LOG").is_ok() {
EnvFilter::from_default_env()
} else {
EnvFilter::new("probe_rs=warn")
};
match log_file {
Some(log_path) => {
let log_file = File::create(log_path)?;
let log_message = format!(
r#"Log output for "{environment_filter}" will be written to: {}"#,
log_path.display()
);
// Subscriber for the designated log file.
let file_subscriber = tracing_subscriber::fmt::layer()
.json()
.with_file(true)
.with_line_number(true)
.with_span_events(FmtSpan::FULL)
.with_writer(log_file)
.with_filter(environment_filter);
// We need to always log errors to stderr, so that the DAP extension can monitor for them.
let stderr_subscriber = tracing_subscriber::fmt::layer()
.compact()
.with_ansi(false)
.with_line_number(true)
.with_span_events(FmtSpan::FULL)
.with_writer(stderr)
.with_filter(LevelFilter::ERROR);
// The stderr subscriber will always log errors to stderr, so that the VSCode extension can monitor for them.
let log_default_guard = tracing_subscriber::registry()
.with(stderr_subscriber)
.with(file_subscriber)
.set_default();
// Tell the user where RUST_LOG messages are written.
self.log_to_console(&log_message)?;
Ok(log_default_guard)
}
None => {
if let Some(LevelFilter::TRACE) = environment_filter.max_level_hint() {
return Err(DebuggerError::UserMessage(String::from(
r#"Using the `TRACE` log level to stream data to the console may have adverse effects on performance.
Consider using a less verbose log level, or use one of the `logFile` or `logToDir` options."#,
)));
}
let log_message = format!(
r#"Log output for "{environment_filter}" will be written to the Debug Console."#
);
// If no log file desitination is specified, send logs via the buffer, to the DAP
// client's Debug Console.
let buffer_layer = tracing_subscriber::fmt::layer()
.compact()
.with_ansi(false)
.without_time()
.with_line_number(true)
.with_span_events(FmtSpan::FULL)
.with_writer(WriterWrapper(self.buffer.clone()))
.with_filter(environment_filter);
let log_default_guard = tracing_subscriber::registry()
.with(buffer_layer)
.set_default();
// Tell the user where RUST_LOG messages are written.
self.log_to_console(&log_message)?;
Ok(log_default_guard)
}
}
}
/// We can send messages directly to the console, irrespective of log levels, by writing to the `buffer_file`.
/// If no `buffer_file` is available, we write to `stderr`.
pub(crate) fn log_to_console(&mut self, message: &str) -> Result<(), DebuggerError> {
let mut locked_log = self.locked_buffer();
writeln!(locked_log, "probe-rs-debug: {message}")?;
Ok(())
}
}