Skip to content

Commit 17aa6f1

Browse files
authored
Option to supply custom defmt formats (#818)
* Add output format argument * Load defmt location data * Use user-provided defmt format * Changelog
1 parent b028c52 commit 17aa6f1

File tree

4 files changed

+110
-27
lines changed

4 files changed

+110
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- Add `watchdog-reset` strategy to `--after` subcommand (#779)
1919
- Add `ROM` version of `read-flash` command (#812)
2020
- `espflash` can detect the log format automatically from ESP-HAL metadata. Reqires `esp-println` 0.14 (presumably, yet to be released) (#809)
21+
- Add `--output-format` option to monitor (#818)
2122

2223
### Changed
2324

espflash/src/cli/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,16 @@ pub struct MonitorConfigArgs {
279279
/// Avoids restarting the device before monitoring
280280
#[arg(long, requires = "non_interactive")]
281281
no_reset: bool,
282-
/// Logging format.
282+
/// The encoding of the target's serial output.
283283
#[arg(long, short = 'L')]
284284
log_format: Option<LogFormat>,
285+
/// The format of the printed defmt messages.
286+
///
287+
/// You can also use one of two presets: oneline (default) and full.
288+
///
289+
/// See <https://defmt.ferrous-systems.com/custom-log-output>
290+
#[arg(long, short = 'F')]
291+
output_format: Option<String>,
285292
/// External log processors to use (comma separated executables)
286293
#[arg(long)]
287294
processors: Option<String>,

espflash/src/cli/monitor/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,16 @@ pub fn monitor(
109109
.log_format
110110
.unwrap_or_else(|| deduce_log_format(elf))
111111
{
112-
LogFormat::Defmt => Box::new(parser::esp_defmt::EspDefmt::new(elf)?),
113-
LogFormat::Serial => Box::new(parser::serial::Serial),
112+
LogFormat::Defmt => Box::new(parser::esp_defmt::EspDefmt::new(
113+
elf,
114+
monitor_args.output_format,
115+
)?),
116+
LogFormat::Serial => {
117+
if monitor_args.output_format.is_some() {
118+
warn!("Output format specified but log format is serial. Ignoring output format.");
119+
}
120+
Box::new(parser::serial::Serial)
121+
}
114122
};
115123

116124
let mut external_processors =

espflash/src/cli/monitor/parser/esp_defmt.rs

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use std::io::Write;
22

33
use crossterm::{style::Print, QueueableCommand};
4-
use defmt_decoder::{Frame, Table};
5-
use miette::{bail, Context, Diagnostic, Result};
4+
use defmt_decoder::{
5+
log::format::{Formatter, FormatterConfig, FormatterFormat},
6+
Frame,
7+
Table,
8+
};
9+
use log::warn;
10+
use miette::{bail, ensure, Context, Diagnostic, Result};
611
use thiserror::Error;
712

813
use crate::cli::monitor::parser::InputParser;
@@ -22,9 +27,13 @@ pub enum DefmtError {
2227
NoDefmtData,
2328

2429
#[error("Failed to parse defmt data")]
25-
#[diagnostic(code(espflash::monitor::defmt::parse_failed))]
30+
#[diagnostic(code(espflash::monitor::defmt::table_parse_failed))]
2631
TableParseFailed,
2732

33+
#[error("Failed to parse defmt location data")]
34+
#[diagnostic(code(espflash::monitor::defmt::location_parse_failed))]
35+
LocationDataParseFailed,
36+
2837
#[error("Unsupported defmt encoding: {0:?}. Only rzcobs is supported.")]
2938
#[diagnostic(code(espflash::monitor::defmt::unsupported_encoding))]
3039
UnsupportedEncoding(defmt_decoder::Encoding),
@@ -101,16 +110,16 @@ impl FrameDelimiter {
101110
}
102111
}
103112

104-
#[derive(Debug)]
105-
pub struct EspDefmt {
106-
delimiter: FrameDelimiter,
113+
struct DefmtData {
107114
table: Table,
115+
locs: Option<defmt_decoder::Locations>,
116+
formatter: Formatter,
108117
}
109118

110-
impl EspDefmt {
119+
impl DefmtData {
111120
/// Loads symbols from the ELF file (if provided) and initializes the
112121
/// context.
113-
fn load_table(elf: Option<&[u8]>) -> Result<Table> {
122+
fn load(elf: Option<&[u8]>, output_format: Option<String>) -> Result<Self> {
114123
let Some(elf) = elf else {
115124
bail!(DefmtError::NoElf);
116125
};
@@ -125,35 +134,93 @@ impl EspDefmt {
125134

126135
// We only support rzcobs encoding because it is the only way to multiplex
127136
// a defmt stream and an ASCII log stream over the same serial port.
128-
if encoding == defmt_decoder::Encoding::Rzcobs {
129-
Ok(table)
137+
ensure!(
138+
encoding == defmt_decoder::Encoding::Rzcobs,
139+
DefmtError::UnsupportedEncoding(encoding)
140+
);
141+
142+
let locs = table
143+
.get_locations(elf)
144+
.map_err(|_e| DefmtError::LocationDataParseFailed)?;
145+
146+
let locs = if !table.is_empty() && locs.is_empty() {
147+
warn!("Insufficient DWARF info; compile your program with `debug = 2` to enable location info.");
148+
None
149+
} else if table.indices().all(|idx| locs.contains_key(&(idx as u64))) {
150+
Some(locs)
130151
} else {
131-
bail!(DefmtError::UnsupportedEncoding(encoding))
132-
}
133-
}
152+
warn!("Location info is incomplete; it will be omitted from the output.");
153+
None
154+
};
134155

135-
pub fn new(elf: Option<&[u8]>) -> Result<Self> {
136-
Self::load_table(elf).map(|table| Self {
137-
delimiter: FrameDelimiter::new(),
156+
let show_location = locs.is_some();
157+
let has_timestamp = table.has_timestamp();
158+
159+
let format = match output_format.as_deref() {
160+
None | Some("oneline") => FormatterFormat::OneLine {
161+
with_location: show_location,
162+
},
163+
Some("full") => FormatterFormat::Default {
164+
with_location: show_location,
165+
},
166+
Some(format) => FormatterFormat::Custom(format),
167+
};
168+
169+
Ok(Self {
138170
table,
171+
locs,
172+
formatter: Formatter::new(FormatterConfig {
173+
format,
174+
is_timestamp_available: has_timestamp,
175+
}),
139176
})
140177
}
141178

142-
fn handle_raw(bytes: &[u8], out: &mut dyn Write) {
143-
out.write_all(bytes).unwrap();
144-
}
179+
fn print(&self, frame: Frame<'_>, out: &mut dyn Write) {
180+
let loc = self.locs.as_ref().and_then(|locs| locs.get(&frame.index()));
181+
let (file, line, module) = if let Some(loc) = loc {
182+
(
183+
Some(loc.file.display().to_string()),
184+
Some(loc.line.try_into().unwrap()),
185+
Some(loc.module.as_str()),
186+
)
187+
} else {
188+
(None, None, None)
189+
};
190+
let s = self
191+
.formatter
192+
.format_frame(frame, file.as_deref(), line, module);
145193

146-
fn handle_defmt(frame: Frame<'_>, out: &mut dyn Write) {
147-
out.queue(Print(frame.display(true).to_string())).unwrap();
194+
out.queue(Print(s)).unwrap();
148195
out.queue(Print("\r\n")).unwrap();
149196

150197
out.flush().unwrap();
151198
}
152199
}
153200

201+
pub struct EspDefmt {
202+
delimiter: FrameDelimiter,
203+
defmt_data: DefmtData,
204+
}
205+
206+
impl std::fmt::Debug for EspDefmt {
207+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208+
f.debug_struct("EspDefmt").finish()
209+
}
210+
}
211+
212+
impl EspDefmt {
213+
pub fn new(elf: Option<&[u8]>, output_format: Option<String>) -> Result<Self> {
214+
DefmtData::load(elf, output_format).map(|defmt_data| Self {
215+
delimiter: FrameDelimiter::new(),
216+
defmt_data,
217+
})
218+
}
219+
}
220+
154221
impl InputParser for EspDefmt {
155222
fn feed(&mut self, bytes: &[u8], out: &mut dyn Write) {
156-
let mut decoder = self.table.new_stream_decoder();
223+
let mut decoder = self.defmt_data.table.new_stream_decoder();
157224

158225
self.delimiter.feed(bytes, |frame| match frame {
159226
FrameKind::Defmt(frame) => {
@@ -162,12 +229,12 @@ impl InputParser for EspDefmt {
162229
decoder.received(FRAME_END);
163230

164231
if let Ok(frame) = decoder.decode() {
165-
Self::handle_defmt(frame, out);
232+
self.defmt_data.print(frame, out);
166233
} else {
167234
log::warn!("Failed to decode defmt frame");
168235
}
169236
}
170-
FrameKind::Raw(bytes) => Self::handle_raw(bytes, out),
237+
FrameKind::Raw(bytes) => out.write_all(bytes).unwrap(),
171238
});
172239
}
173240
}

0 commit comments

Comments
 (0)