1#![cfg(feature = "unstable")]
7
8use ansi_term::Colour;
9use colored::{Color, Colorize};
10use defmt_decoder::Frame;
11use difference::{Changeset, Difference};
12use log::{Level, Log, Metadata, Record};
13
14use std::{
15 fmt::Write as _,
16 io,
17 sync::atomic::{AtomicUsize, Ordering},
18};
19
20const DEFMT_TARGET_MARKER: &str = "defmt@";
21
22pub fn init(verbose: bool) {
24 log::set_boxed_logger(Box::new(Logger {
25 verbose,
26 timing_align: AtomicUsize::new(8),
27 }))
28 .unwrap();
29 log::set_max_level(log::LevelFilter::Trace);
30}
31
32pub fn log_defmt(
34 frame: &Frame<'_>,
35 file: Option<&str>,
36 line: Option<u32>,
37 module_path: Option<&str>,
38) {
39 let level = match frame.level() {
40 defmt_decoder::Level::Trace => Level::Trace,
41 defmt_decoder::Level::Debug => Level::Debug,
42 defmt_decoder::Level::Info => Level::Info,
43 defmt_decoder::Level::Warn => Level::Warn,
44 defmt_decoder::Level::Error => Level::Error,
45 };
46
47 let target = format!("{}{}", DEFMT_TARGET_MARKER, frame.timestamp());
48 let display = frame.display_message();
49
50 log::logger().log(
51 &Record::builder()
52 .args(format_args!("{}", display))
53 .level(level)
54 .target(&target)
55 .module_path(module_path)
56 .file(file)
57 .line(line)
58 .build(),
59 );
60}
61
62struct Logger {
63 verbose: bool,
65
66 timing_align: AtomicUsize,
69}
70
71impl Log for Logger {
72 fn enabled(&self, metadata: &Metadata) -> bool {
73 if metadata.target().starts_with(DEFMT_TARGET_MARKER) {
74 true
76 } else {
77 if self.verbose {
80 metadata.target().starts_with("probe_run")
81 } else {
82 metadata.target().starts_with("probe_run") && metadata.level() <= Level::Info
83 }
84 }
85 }
86
87 fn log(&self, record: &Record) {
88 if !self.enabled(record.metadata()) {
89 return;
90 }
91
92 let level_color = match record.level() {
93 Level::Error => Color::Red,
94 Level::Warn => Color::Yellow,
95 Level::Info => Color::Green,
96 Level::Debug => Color::BrightWhite,
97 Level::Trace => Color::BrightBlack,
98 };
99
100 let target = record.metadata().target();
101 let is_defmt = target.starts_with(DEFMT_TARGET_MARKER);
102
103 let timestamp = if is_defmt {
104 let timestamp = target[DEFMT_TARGET_MARKER.len()..].parse::<u64>().unwrap();
105 let seconds = timestamp / 1000000;
106 let micros = timestamp % 1000000;
107 format!("{}.{:06}", seconds, micros)
108 } else {
109 format!("(HOST)")
111 };
112
113 self.timing_align
114 .fetch_max(timestamp.len(), Ordering::Relaxed);
115
116 let (stdout, stderr, mut stdout_lock, mut stderr_lock);
117 let sink: &mut dyn io::Write = if is_defmt {
118 stdout = io::stdout();
120 stdout_lock = stdout.lock();
121 &mut stdout_lock
122 } else {
123 stderr = io::stderr();
125 stderr_lock = stderr.lock();
126 &mut stderr_lock
127 };
128
129 writeln!(
130 sink,
131 "{timestamp:>0$} {level:5} {args}",
132 self.timing_align.load(Ordering::Relaxed),
133 timestamp = timestamp,
134 level = record.level().to_string().color(level_color),
135 args = color_diff(record.args().to_string()),
136 )
137 .ok();
138
139 if let Some(file) = record.file() {
140 let mod_path = record.module_path().unwrap();
142 if is_defmt || self.verbose {
144 let mut loc = file.to_string();
145 if let Some(line) = record.line() {
146 loc.push_str(&format!(":{}", line));
147 }
148 writeln!(sink, "{}", format!("└─ {} @ {}", mod_path, loc).dimmed()).ok();
149 }
150 }
151 }
152
153 fn flush(&self) {}
154}
155
156fn color_diff(text: String) -> String {
160 let lines = text.lines().collect::<Vec<_>>();
161 let nlines = lines.len();
162 if nlines > 2 {
163 let left = lines[nlines - 2];
164 let right = lines[nlines - 1];
165
166 const LEFT_START: &str = " left: `";
167 const RIGHT_START: &str = "right: `";
168 const END: &str = "`";
169 if left.starts_with(LEFT_START)
170 && left.ends_with(END)
171 && right.starts_with(RIGHT_START)
172 && right.ends_with(END)
173 {
174 const DARK_RED: Colour = Colour::Fixed(52);
175 const DARK_GREEN: Colour = Colour::Fixed(22);
176
177 let left = &left[LEFT_START.len()..left.len() - END.len()];
179 let right = &right[RIGHT_START.len()..right.len() - END.len()];
180
181 let mut buf = lines[..nlines - 2].join("\n").bold().to_string();
182 buf.push('\n');
183
184 let changeset = Changeset::new(left, right, "");
185
186 writeln!(
187 buf,
188 "{} {} / {}",
189 "diff".bold(),
190 "< left".red(),
191 "right >".green()
192 )
193 .ok();
194 write!(buf, "{}", "<".red()).ok();
195 for diff in &changeset.diffs {
196 match diff {
197 Difference::Same(s) => {
198 write!(buf, "{}", s.red()).ok();
199 }
200 Difference::Add(_) => continue,
201 Difference::Rem(s) => {
202 write!(buf, "{}", Colour::Red.on(DARK_RED).bold().paint(s)).ok();
203 }
204 }
205 }
206 buf.push('\n');
207
208 write!(buf, "{}", ">".green()).ok();
209 for diff in &changeset.diffs {
210 match diff {
211 Difference::Same(s) => {
212 write!(buf, "{}", s.green()).ok();
213 }
214 Difference::Rem(_) => continue,
215 Difference::Add(s) => {
216 write!(buf, "{}", Colour::Green.on(DARK_GREEN).bold().paint(s)).ok();
217 }
218 }
219 }
220 return buf;
221 }
222 }
223
224 text.bold().to_string()
226}