mirror of
https://github.com/esp-rs/espflash.git
synced 2026-03-23 15:50:21 +00:00
Option to supply custom defmt formats (#818)
* Add output format argument * Load defmt location data * Use user-provided defmt format * Changelog
This commit is contained in:
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add `watchdog-reset` strategy to `--after` subcommand (#779)
|
||||
- Add `ROM` version of `read-flash` command (#812)
|
||||
- `espflash` can detect the log format automatically from ESP-HAL metadata. Reqires `esp-println` 0.14 (presumably, yet to be released) (#809)
|
||||
- Add `--output-format` option to monitor (#818)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -279,9 +279,16 @@ pub struct MonitorConfigArgs {
|
||||
/// Avoids restarting the device before monitoring
|
||||
#[arg(long, requires = "non_interactive")]
|
||||
no_reset: bool,
|
||||
/// Logging format.
|
||||
/// The encoding of the target's serial output.
|
||||
#[arg(long, short = 'L')]
|
||||
log_format: Option<LogFormat>,
|
||||
/// The format of the printed defmt messages.
|
||||
///
|
||||
/// You can also use one of two presets: oneline (default) and full.
|
||||
///
|
||||
/// See <https://defmt.ferrous-systems.com/custom-log-output>
|
||||
#[arg(long, short = 'F')]
|
||||
output_format: Option<String>,
|
||||
/// External log processors to use (comma separated executables)
|
||||
#[arg(long)]
|
||||
processors: Option<String>,
|
||||
|
||||
@@ -109,8 +109,16 @@ pub fn monitor(
|
||||
.log_format
|
||||
.unwrap_or_else(|| deduce_log_format(elf))
|
||||
{
|
||||
LogFormat::Defmt => Box::new(parser::esp_defmt::EspDefmt::new(elf)?),
|
||||
LogFormat::Serial => Box::new(parser::serial::Serial),
|
||||
LogFormat::Defmt => Box::new(parser::esp_defmt::EspDefmt::new(
|
||||
elf,
|
||||
monitor_args.output_format,
|
||||
)?),
|
||||
LogFormat::Serial => {
|
||||
if monitor_args.output_format.is_some() {
|
||||
warn!("Output format specified but log format is serial. Ignoring output format.");
|
||||
}
|
||||
Box::new(parser::serial::Serial)
|
||||
}
|
||||
};
|
||||
|
||||
let mut external_processors =
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use std::io::Write;
|
||||
|
||||
use crossterm::{style::Print, QueueableCommand};
|
||||
use defmt_decoder::{Frame, Table};
|
||||
use miette::{bail, Context, Diagnostic, Result};
|
||||
use defmt_decoder::{
|
||||
log::format::{Formatter, FormatterConfig, FormatterFormat},
|
||||
Frame,
|
||||
Table,
|
||||
};
|
||||
use log::warn;
|
||||
use miette::{bail, ensure, Context, Diagnostic, Result};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::cli::monitor::parser::InputParser;
|
||||
@@ -22,9 +27,13 @@ pub enum DefmtError {
|
||||
NoDefmtData,
|
||||
|
||||
#[error("Failed to parse defmt data")]
|
||||
#[diagnostic(code(espflash::monitor::defmt::parse_failed))]
|
||||
#[diagnostic(code(espflash::monitor::defmt::table_parse_failed))]
|
||||
TableParseFailed,
|
||||
|
||||
#[error("Failed to parse defmt location data")]
|
||||
#[diagnostic(code(espflash::monitor::defmt::location_parse_failed))]
|
||||
LocationDataParseFailed,
|
||||
|
||||
#[error("Unsupported defmt encoding: {0:?}. Only rzcobs is supported.")]
|
||||
#[diagnostic(code(espflash::monitor::defmt::unsupported_encoding))]
|
||||
UnsupportedEncoding(defmt_decoder::Encoding),
|
||||
@@ -101,16 +110,16 @@ impl FrameDelimiter {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EspDefmt {
|
||||
delimiter: FrameDelimiter,
|
||||
struct DefmtData {
|
||||
table: Table,
|
||||
locs: Option<defmt_decoder::Locations>,
|
||||
formatter: Formatter,
|
||||
}
|
||||
|
||||
impl EspDefmt {
|
||||
impl DefmtData {
|
||||
/// Loads symbols from the ELF file (if provided) and initializes the
|
||||
/// context.
|
||||
fn load_table(elf: Option<&[u8]>) -> Result<Table> {
|
||||
fn load(elf: Option<&[u8]>, output_format: Option<String>) -> Result<Self> {
|
||||
let Some(elf) = elf else {
|
||||
bail!(DefmtError::NoElf);
|
||||
};
|
||||
@@ -125,35 +134,93 @@ impl EspDefmt {
|
||||
|
||||
// We only support rzcobs encoding because it is the only way to multiplex
|
||||
// a defmt stream and an ASCII log stream over the same serial port.
|
||||
if encoding == defmt_decoder::Encoding::Rzcobs {
|
||||
Ok(table)
|
||||
} else {
|
||||
bail!(DefmtError::UnsupportedEncoding(encoding))
|
||||
}
|
||||
}
|
||||
ensure!(
|
||||
encoding == defmt_decoder::Encoding::Rzcobs,
|
||||
DefmtError::UnsupportedEncoding(encoding)
|
||||
);
|
||||
|
||||
pub fn new(elf: Option<&[u8]>) -> Result<Self> {
|
||||
Self::load_table(elf).map(|table| Self {
|
||||
delimiter: FrameDelimiter::new(),
|
||||
let locs = table
|
||||
.get_locations(elf)
|
||||
.map_err(|_e| DefmtError::LocationDataParseFailed)?;
|
||||
|
||||
let locs = if !table.is_empty() && locs.is_empty() {
|
||||
warn!("Insufficient DWARF info; compile your program with `debug = 2` to enable location info.");
|
||||
None
|
||||
} else if table.indices().all(|idx| locs.contains_key(&(idx as u64))) {
|
||||
Some(locs)
|
||||
} else {
|
||||
warn!("Location info is incomplete; it will be omitted from the output.");
|
||||
None
|
||||
};
|
||||
|
||||
let show_location = locs.is_some();
|
||||
let has_timestamp = table.has_timestamp();
|
||||
|
||||
let format = match output_format.as_deref() {
|
||||
None | Some("oneline") => FormatterFormat::OneLine {
|
||||
with_location: show_location,
|
||||
},
|
||||
Some("full") => FormatterFormat::Default {
|
||||
with_location: show_location,
|
||||
},
|
||||
Some(format) => FormatterFormat::Custom(format),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
table,
|
||||
locs,
|
||||
formatter: Formatter::new(FormatterConfig {
|
||||
format,
|
||||
is_timestamp_available: has_timestamp,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_raw(bytes: &[u8], out: &mut dyn Write) {
|
||||
out.write_all(bytes).unwrap();
|
||||
}
|
||||
fn print(&self, frame: Frame<'_>, out: &mut dyn Write) {
|
||||
let loc = self.locs.as_ref().and_then(|locs| locs.get(&frame.index()));
|
||||
let (file, line, module) = if let Some(loc) = loc {
|
||||
(
|
||||
Some(loc.file.display().to_string()),
|
||||
Some(loc.line.try_into().unwrap()),
|
||||
Some(loc.module.as_str()),
|
||||
)
|
||||
} else {
|
||||
(None, None, None)
|
||||
};
|
||||
let s = self
|
||||
.formatter
|
||||
.format_frame(frame, file.as_deref(), line, module);
|
||||
|
||||
fn handle_defmt(frame: Frame<'_>, out: &mut dyn Write) {
|
||||
out.queue(Print(frame.display(true).to_string())).unwrap();
|
||||
out.queue(Print(s)).unwrap();
|
||||
out.queue(Print("\r\n")).unwrap();
|
||||
|
||||
out.flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EspDefmt {
|
||||
delimiter: FrameDelimiter,
|
||||
defmt_data: DefmtData,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for EspDefmt {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("EspDefmt").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl EspDefmt {
|
||||
pub fn new(elf: Option<&[u8]>, output_format: Option<String>) -> Result<Self> {
|
||||
DefmtData::load(elf, output_format).map(|defmt_data| Self {
|
||||
delimiter: FrameDelimiter::new(),
|
||||
defmt_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InputParser for EspDefmt {
|
||||
fn feed(&mut self, bytes: &[u8], out: &mut dyn Write) {
|
||||
let mut decoder = self.table.new_stream_decoder();
|
||||
let mut decoder = self.defmt_data.table.new_stream_decoder();
|
||||
|
||||
self.delimiter.feed(bytes, |frame| match frame {
|
||||
FrameKind::Defmt(frame) => {
|
||||
@@ -162,12 +229,12 @@ impl InputParser for EspDefmt {
|
||||
decoder.received(FRAME_END);
|
||||
|
||||
if let Ok(frame) = decoder.decode() {
|
||||
Self::handle_defmt(frame, out);
|
||||
self.defmt_data.print(frame, out);
|
||||
} else {
|
||||
log::warn!("Failed to decode defmt frame");
|
||||
}
|
||||
}
|
||||
FrameKind::Raw(bytes) => Self::handle_raw(bytes, out),
|
||||
FrameKind::Raw(bytes) => out.write_all(bytes).unwrap(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user