Skip to content

Commit

Permalink
Merge branch 'main' into print-serial
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonros authored Aug 16, 2023
2 parents 2ddf28e + ca161bf commit 79f93d1
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 170 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

- [#771]: `defmt-macros`: Ignore empty items in DEFMT_LOG

[#771]: https://github.com/knurling-rs/defmt/pull/771

## defmt-decoder v0.3.8, defmt-print v0.3.8 - 2023-08-01

- [#766] `decoder::log`: Rename `PrettyLogger` to `StdoutLogger`
- [#765]: `defmt-decoder`: Add support for customizable logger formatting

[#766]: https://github.com/knurling-rs/defmt/pull/766
[#765]: https://github.com/knurling-rs/defmt/pull/765

## [v0.3.5] - 2023-06-19

Expand Down
5 changes: 3 additions & 2 deletions decoder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ license = "MIT OR Apache-2.0"
name = "defmt-decoder"
readme = "../README.md"
repository = "https://github.com/knurling-rs/defmt"
version = "0.3.7"
version = "0.3.8"

[dependencies]
byteorder = "1"
colored = "2"
defmt-parser = { version = "=0.3.3", path = "../parser", features = ["unstable"] }
ryu = "1"
nom = "7"

# display
time = { version = "0.3", default-features = false, features = [
Expand All @@ -34,7 +35,7 @@ gimli = { version = "0.27", default-features = false, features = [
"read",
"std",
] }
object = { version = "0.30", default-features = false, features = [
object = { version = "0.31", default-features = false, features = [
"read_core",
"elf",
"std",
Expand Down
4 changes: 4 additions & 0 deletions decoder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ impl Table {
pub fn encoding(&self) -> Encoding {
self.encoding
}

pub fn has_timestamp(&self) -> bool {
self.timestamp.is_some()
}
}

// NOTE follows `parser::Type`
Expand Down
106 changes: 106 additions & 0 deletions decoder/src/log/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use nom::{
branch::alt,
bytes::complete::{take, take_till1},
character::complete::char,
combinator::{map, map_res},
multi::many0,
sequence::delimited,
IResult, Parser,
};

#[derive(Debug, PartialEq, Clone)]
#[non_exhaustive]
pub(super) enum LogSegment {
FileName,
FilePath,
LineNumber,
Log,
LogLevel,
ModulePath,
String(String),
Timestamp,
}

fn parse_argument(input: &str) -> IResult<&str, LogSegment, ()> {
let parse_enclosed = delimited(char('{'), take(1u32), char('}'));
let mut parse_type = map_res(parse_enclosed, move |s| match s {
"f" => Ok(LogSegment::FileName),
"F" => Ok(LogSegment::FilePath),
"l" => Ok(LogSegment::LineNumber),
"s" => Ok(LogSegment::Log),
"L" => Ok(LogSegment::LogLevel),
"m" => Ok(LogSegment::ModulePath),
"t" => Ok(LogSegment::Timestamp),
_ => Err(()),
});

parse_type.parse(input)
}

fn parse_string_segment(input: &str) -> IResult<&str, LogSegment, ()> {
map(take_till1(|c| c == '{'), |s: &str| {
LogSegment::String(s.to_string())
})
.parse(input)
}

pub(super) fn parse(input: &str) -> Result<Vec<LogSegment>, String> {
let mut parse_all = many0(alt((parse_argument, parse_string_segment)));

parse_all(input)
.map(|(_, output)| output)
.map_err(|e| e.to_string())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_log_template() {
let log_template = "{t} [{L}] {s}\n└─ {m} @ {F}:{l}";

let expected_output = vec![
LogSegment::Timestamp,
LogSegment::String(" [".to_string()),
LogSegment::LogLevel,
LogSegment::String("] ".to_string()),
LogSegment::Log,
LogSegment::String("\n└─ ".to_string()),
LogSegment::ModulePath,
LogSegment::String(" @ ".to_string()),
LogSegment::FilePath,
LogSegment::String(":".to_string()),
LogSegment::LineNumber,
];

let result = parse(log_template);
assert_eq!(result, Ok(expected_output));
}

#[test]
fn test_parse_string_segment() {
let result = parse_string_segment("Log: {t}");
let (input, output) = result.unwrap();
assert_eq!(input, "{t}");
assert_eq!(output, LogSegment::String("Log: ".to_string()));
}

#[test]
fn test_parse_empty_string_segment() {
let result = parse_string_segment("");
assert!(result.is_err());
}

#[test]
fn test_parse_timestamp_argument() {
let result = parse_argument("{t}");
assert_eq!(result, Ok(("", LogSegment::Timestamp)));
}

#[test]
fn test_parse_invalid_argument() {
let result = parse_argument("{foo}");
assert_eq!(result, Result::Err(nom::Err::Error(())));
}
}
14 changes: 11 additions & 3 deletions decoder/src/log/json_logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use time::OffsetDateTime;

use std::io::{self, Write};

use super::{DefmtRecord, StdoutLogger};
use super::{DefmtLoggerInfo, DefmtRecord, StdoutLogger};

pub(crate) struct JsonLogger {
should_log: Box<dyn Fn(&Metadata) -> bool + Sync + Send>,
Expand Down Expand Up @@ -41,10 +41,14 @@ impl Log for JsonLogger {
}

impl JsonLogger {
pub fn new(should_log: impl Fn(&Metadata) -> bool + Sync + Send + 'static) -> Box<Self> {
pub fn new(
log_format: Option<&str>,
host_log_format: Option<&str>,
should_log: impl Fn(&Metadata) -> bool + Sync + Send + 'static,
) -> Box<Self> {
Box::new(Self {
should_log: Box::new(should_log),
host_logger: StdoutLogger::new_unboxed(true, |_| true),
host_logger: StdoutLogger::new_unboxed(log_format, host_log_format, |_| true),
})
}

Expand All @@ -53,6 +57,10 @@ impl JsonLogger {
serde_json::to_writer(&mut sink, &SCHEMA_VERSION).ok();
writeln!(sink).ok();
}

pub fn info(&self) -> DefmtLoggerInfo {
self.host_logger.info()
}
}

/// Create a new [JsonFrame] from a log-frame from the target
Expand Down
55 changes: 44 additions & 11 deletions decoder/src/log/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
//! [`log`]: https://crates.io/crates/log
//! [`defmt`]: https://crates.io/crates/defmt

mod format;
mod json_logger;
mod stdout_logger;

use log::{Level, LevelFilter, Metadata, Record};
use log::{Level, LevelFilter, Log, Metadata, Record};
use serde::{Deserialize, Serialize};

use std::fmt;
Expand Down Expand Up @@ -119,21 +120,53 @@ impl<'a> DefmtRecord<'a> {
/// The caller has to provide a `should_log` closure that determines whether a log record should be
/// printed.
///
/// If `always_include_location` is `true`, a second line containing location information will be
/// printed for *all* records, not just for defmt frames (defmt frames always get location info
/// included if it is available, regardless of this setting).
/// An optional `log_format` string can be provided to format the way
/// logs are printed. A format string could look as follows:
/// "{t} [{L}] Location<{f}:{l}> {s}"
///
/// The arguments between curly braces are placeholders for log metadata.
/// The following arguments are supported:
/// - {f} : file name (e.g. "main.rs")
/// - {F} : file path (e.g. "src/bin/main.rs")
/// - {l} : line number
/// - {L} : log level (e.g. "INFO", "DEBUG", etc)
/// - {m} : module path (e.g. "foo::bar::some_function")
/// - {s} : the actual log
/// - {t} : log timestamp
///
/// For example, with the log format shown above, a log would look like this:
/// "23124 [INFO] Location<main.rs:23> Hello, world!"
pub fn init_logger(
always_include_location: bool,
log_format: Option<&str>,
host_log_format: Option<&str>,
json: bool,
should_log: impl Fn(&Metadata) -> bool + Sync + Send + 'static,
) {
log::set_boxed_logger(match json {
false => StdoutLogger::new(always_include_location, should_log),
) -> DefmtLoggerInfo {
let (logger, info): (Box<dyn Log>, DefmtLoggerInfo) = match json {
false => {
let logger = StdoutLogger::new(log_format, host_log_format, should_log);
let info = logger.info();
(logger, info)
}
true => {
JsonLogger::print_schema_version();
JsonLogger::new(should_log)
let logger = JsonLogger::new(log_format, host_log_format, should_log);
let info = logger.info();
(logger, info)
}
})
.unwrap();
};
log::set_boxed_logger(logger).unwrap();
log::set_max_level(LevelFilter::Trace);
info
}

#[derive(Clone, Copy)]
pub struct DefmtLoggerInfo {
has_timestamp: bool,
}

impl DefmtLoggerInfo {
pub fn has_timestamp(&self) -> bool {
self.has_timestamp
}
}
Loading

0 comments on commit 79f93d1

Please sign in to comment.