1#![doc(html_root_url = "https://docs.rs/color-spantrace/0.2.1")]
63#![cfg_attr(
64 nightly,
65 feature(rustdoc_missing_doc_code_examples),
66 warn(rustdoc::missing_doc_code_examples)
67)]
68#![warn(
69 missing_debug_implementations,
70 missing_docs,
71 rust_2018_idioms,
72 unreachable_pub,
73 bad_style,
74 dead_code,
75 improper_ctypes,
76 non_shorthand_field_patterns,
77 no_mangle_generic_items,
78 overflowing_literals,
79 path_statements,
80 patterns_in_fns_without_body,
81 unconditional_recursion,
82 unused,
83 unused_allocation,
84 unused_comparisons,
85 unused_parens,
86 while_true
87)]
88use once_cell::sync::OnceCell;
89use owo_colors::{style, Style};
90use std::env;
91use std::fmt;
92use std::fs::File;
93use std::io::{BufRead, BufReader};
94use tracing_error::SpanTrace;
95
96static THEME: OnceCell<Theme> = OnceCell::new();
97
98#[derive(Debug, Copy, Clone, Default)]
100pub struct Theme {
101 file: Style,
102 line_number: Style,
103 target: Style,
104 fields: Style,
105 active_line: Style,
106}
107
108impl Theme {
109 pub fn new() -> Self {
111 Self::default()
112 }
113
114 pub fn dark() -> Self {
116 Self {
117 file: style().purple(),
118 line_number: style().purple(),
119 active_line: style().white().bold(),
120 target: style().bright_red(),
121 fields: style().bright_cyan(),
122 }
123 }
124
125 pub fn light() -> Self {
128 Self {
129 file: style().purple(),
130 line_number: style().purple(),
131 target: style().red(),
132 fields: style().blue(),
133 active_line: style().bold(),
134 }
135 }
136
137 pub fn file(mut self, style: Style) -> Self {
139 self.file = style;
140 self
141 }
142
143 pub fn line_number(mut self, style: Style) -> Self {
145 self.line_number = style;
146 self
147 }
148
149 pub fn target(mut self, style: Style) -> Self {
151 self.target = style;
152 self
153 }
154
155 pub fn fields(mut self, style: Style) -> Self {
157 self.fields = style;
158 self
159 }
160
161 pub fn active_line(mut self, style: Style) -> Self {
163 self.active_line = style;
164 self
165 }
166}
167
168#[derive(Debug)]
170pub struct InstallThemeError;
171
172impl fmt::Display for InstallThemeError {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 f.write_str("could not set the provided `Theme` globally as another was already set")
175 }
176}
177
178impl std::error::Error for InstallThemeError {}
179
180pub fn set_theme(theme: Theme) -> Result<(), InstallThemeError> {
188 THEME.set(theme).map_err(|_| InstallThemeError)
189}
190
191pub fn colorize(span_trace: &SpanTrace) -> impl fmt::Display + '_ {
209 let theme = *THEME.get_or_init(Theme::dark);
210 ColorSpanTrace { span_trace, theme }
211}
212
213struct ColorSpanTrace<'a> {
214 span_trace: &'a SpanTrace,
215 theme: Theme,
216}
217
218macro_rules! try_bool {
219 ($e:expr, $dest:ident) => {{
220 let ret = $e.unwrap_or_else(|e| $dest = Err(e));
221
222 if $dest.is_err() {
223 return false;
224 }
225
226 ret
227 }};
228}
229
230struct Frame<'a> {
231 metadata: &'a tracing_core::Metadata<'static>,
232 fields: &'a str,
233 theme: Theme,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
238enum Verbosity {
239 Minimal,
241 Medium,
243 Full,
245}
246
247impl Verbosity {
248 fn lib_from_env() -> Self {
249 Self::convert_env(
250 env::var("RUST_LIB_BACKTRACE")
251 .or_else(|_| env::var("RUST_BACKTRACE"))
252 .ok(),
253 )
254 }
255
256 fn convert_env(env: Option<String>) -> Self {
257 match env {
258 Some(ref x) if x == "full" => Verbosity::Full,
259 Some(_) => Verbosity::Medium,
260 None => Verbosity::Minimal,
261 }
262 }
263}
264
265impl Frame<'_> {
266 fn print(&self, i: u32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 self.print_header(i, f)?;
268 self.print_fields(f)?;
269 self.print_source_location(f)?;
270 Ok(())
271 }
272
273 fn print_header(&self, i: u32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 write!(
275 f,
276 "{:>2}: {}{}{}",
277 i,
278 self.theme.target.style(self.metadata.target()),
279 self.theme.target.style("::"),
280 self.theme.target.style(self.metadata.name()),
281 )
282 }
283
284 fn print_fields(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 if !self.fields.is_empty() {
286 write!(f, " with {}", self.theme.fields.style(self.fields))?;
287 }
288
289 Ok(())
290 }
291
292 fn print_source_location(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 if let Some(file) = self.metadata.file() {
294 let lineno = self
295 .metadata
296 .line()
297 .map_or("<unknown line>".to_owned(), |x| x.to_string());
298 write!(
299 f,
300 "\n at {}:{}",
301 self.theme.file.style(file),
302 self.theme.line_number.style(lineno),
303 )?;
304 } else {
305 write!(f, "\n at <unknown source file>")?;
306 }
307
308 Ok(())
309 }
310
311 fn print_source_if_avail(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 let (lineno, filename) = match (self.metadata.line(), self.metadata.file()) {
313 (Some(a), Some(b)) => (a, b),
314 _ => return Ok(()),
316 };
317
318 let file = match File::open(filename) {
319 Ok(file) => file,
320 Err(_) => return Ok(()),
322 };
323
324 use std::fmt::Write;
325
326 let reader = BufReader::new(file);
328 let start_line = lineno - 2.min(lineno - 1);
329 let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
330 let mut buf = String::new();
331 for (line, cur_line_no) in surrounding_src.zip(start_line..) {
332 if cur_line_no == lineno {
333 write!(
334 &mut buf,
335 "{:>8} > {}",
336 cur_line_no.to_string(),
337 line.unwrap()
338 )?;
339 write!(f, "\n{}", self.theme.active_line.style(&buf))?;
340 buf.clear();
341 } else {
342 write!(f, "\n{:>8} │ {}", cur_line_no, line.unwrap())?;
343 }
344 }
345
346 Ok(())
347 }
348}
349
350impl fmt::Display for ColorSpanTrace<'_> {
351 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352 let mut err = Ok(());
353 let mut span = 0;
354
355 writeln!(f, "{:━^80}\n", " SPANTRACE ")?;
356 self.span_trace.with_spans(|metadata, fields| {
357 let frame = Frame {
358 metadata,
359 fields,
360 theme: self.theme,
361 };
362
363 if span > 0 {
364 try_bool!(write!(f, "\n",), err);
365 }
366
367 try_bool!(frame.print(span, f), err);
368
369 if Verbosity::lib_from_env() == Verbosity::Full {
370 try_bool!(frame.print_source_if_avail(f), err);
371 }
372
373 span += 1;
374 true
375 });
376
377 err
378 }
379}