Skip to content

Commit efe01e0

Browse files
95ulisseKodrAus
authored andcommitted
Indentation for multiline log messages (#134)
1 parent b598a2c commit efe01e0

File tree

2 files changed

+127
-4
lines changed

2 files changed

+127
-4
lines changed

src/fmt/mod.rs

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ pub(crate) struct Builder {
117117
pub default_format_timestamp_nanos: bool,
118118
pub default_format_module_path: bool,
119119
pub default_format_level: bool,
120+
pub default_format_indent: Option<usize>,
120121
#[allow(unknown_lints, bare_trait_objects)]
121122
pub custom_format: Option<Box<Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send>>,
122123
built: bool,
@@ -129,6 +130,7 @@ impl Default for Builder {
129130
default_format_timestamp_nanos: false,
130131
default_format_module_path: true,
131132
default_format_level: true,
133+
default_format_indent: Some(4),
132134
custom_format: None,
133135
built: false,
134136
}
@@ -161,6 +163,7 @@ impl Builder {
161163
module_path: built.default_format_module_path,
162164
level: built.default_format_level,
163165
written_header_value: false,
166+
indent: built.default_format_indent,
164167
buf,
165168
};
166169

@@ -184,6 +187,7 @@ struct DefaultFormat<'a> {
184187
level: bool,
185188
timestamp_nanos: bool,
186189
written_header_value: bool,
190+
indent: Option<usize>,
187191
buf: &'a mut Formatter,
188192
}
189193

@@ -289,7 +293,54 @@ impl<'a> DefaultFormat<'a> {
289293
}
290294

291295
fn write_args(&mut self, record: &Record) -> io::Result<()> {
292-
writeln!(self.buf, "{}", record.args())
296+
match self.indent {
297+
298+
// Fast path for no indentation
299+
None => writeln!(self.buf, "{}", record.args()),
300+
301+
Some(indent_count) => {
302+
303+
// Create a wrapper around the buffer only if we have to actually indent the message
304+
305+
struct IndentWrapper<'a, 'b: 'a> {
306+
fmt: &'a mut DefaultFormat<'b>,
307+
indent_count: usize
308+
}
309+
310+
impl<'a, 'b> Write for IndentWrapper<'a, 'b> {
311+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
312+
let mut first = true;
313+
for chunk in buf.split(|&x| x == b'\n') {
314+
if !first {
315+
write!(self.fmt.buf, "\n{:width$}", "", width = self.indent_count)?;
316+
}
317+
self.fmt.buf.write_all(chunk)?;
318+
first = false;
319+
}
320+
321+
Ok(buf.len())
322+
}
323+
324+
fn flush(&mut self) -> io::Result<()> {
325+
self.fmt.buf.flush()
326+
}
327+
}
328+
329+
// The explicit scope here is just to make older versions of Rust happy
330+
{
331+
let mut wrapper = IndentWrapper {
332+
fmt: self,
333+
indent_count
334+
};
335+
write!(wrapper, "{}", record.args())?;
336+
}
337+
338+
writeln!(self.buf)?;
339+
340+
Ok(())
341+
}
342+
343+
}
293344
}
294345
}
295346

@@ -303,7 +354,7 @@ mod tests {
303354
let buf = fmt.buf.buf.clone();
304355

305356
let record = Record::builder()
306-
.args(format_args!("log message"))
357+
.args(format_args!("log\nmessage"))
307358
.level(Level::Info)
308359
.file(Some("test.rs"))
309360
.line(Some(144))
@@ -330,10 +381,11 @@ mod tests {
330381
module_path: true,
331382
level: true,
332383
written_header_value: false,
384+
indent: None,
333385
buf: &mut f,
334386
});
335387

336-
assert_eq!("[INFO test::path] log message\n", written);
388+
assert_eq!("[INFO test::path] log\nmessage\n", written);
337389
}
338390

339391
#[test]
@@ -350,9 +402,73 @@ mod tests {
350402
module_path: false,
351403
level: false,
352404
written_header_value: false,
405+
indent: None,
406+
buf: &mut f,
407+
});
408+
409+
assert_eq!("log\nmessage\n", written);
410+
}
411+
412+
#[test]
413+
fn default_format_indent_spaces() {
414+
let writer = writer::Builder::new()
415+
.write_style(WriteStyle::Never)
416+
.build();
417+
418+
let mut f = Formatter::new(&writer);
419+
420+
let written = write(DefaultFormat {
421+
timestamp: false,
422+
timestamp_nanos: false,
423+
module_path: true,
424+
level: true,
425+
written_header_value: false,
426+
indent: Some(4),
427+
buf: &mut f,
428+
});
429+
430+
assert_eq!("[INFO test::path] log\n message\n", written);
431+
}
432+
433+
#[test]
434+
fn default_format_indent_zero_spaces() {
435+
let writer = writer::Builder::new()
436+
.write_style(WriteStyle::Never)
437+
.build();
438+
439+
let mut f = Formatter::new(&writer);
440+
441+
let written = write(DefaultFormat {
442+
timestamp: false,
443+
timestamp_nanos: false,
444+
module_path: true,
445+
level: true,
446+
written_header_value: false,
447+
indent: Some(0),
448+
buf: &mut f,
449+
});
450+
451+
assert_eq!("[INFO test::path] log\nmessage\n", written);
452+
}
453+
454+
#[test]
455+
fn default_format_indent_spaces_no_header() {
456+
let writer = writer::Builder::new()
457+
.write_style(WriteStyle::Never)
458+
.build();
459+
460+
let mut f = Formatter::new(&writer);
461+
462+
let written = write(DefaultFormat {
463+
timestamp: false,
464+
timestamp_nanos: false,
465+
module_path: false,
466+
level: false,
467+
written_header_value: false,
468+
indent: Some(4),
353469
buf: &mut f,
354470
});
355471

356-
assert_eq!("log message\n", written);
472+
assert_eq!("log\n message\n", written);
357473
}
358474
}

src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,13 @@ impl Builder {
529529
self
530530
}
531531

532+
/// Configures the amount of spaces to use to indent multiline log records.
533+
/// A value of `None` disables any kind of indentation.
534+
pub fn default_format_indent(&mut self, indent: Option<usize>) -> &mut Self {
535+
self.format.default_format_indent = indent;
536+
self
537+
}
538+
532539
/// Adds a directive to the filter for a specific module.
533540
///
534541
/// # Examples

0 commit comments

Comments
 (0)