% Better debug logs
% Michael Stone
% February 13, 2022
---
header-includes: |
---
The standard interfaces in rust for "printf debugging" are traits called
`Debug` and `Display`, each with a method named `fmt()` that look like
```rust
pub trait Debug {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>;
}
```
While a good choice for a general-purpose, portable, standard library,
individual programs may benefit from a logging interface that is more tailored
to their user interface:
![Hierarchical logging with search-based highlighting](./logging.png)
In the case of my recent work on [depict](https://github.com/mstone/depict), I
wanted to experiment with arranging my logs hierarchically (based on the
structure of the computation generating them) and with making them searchable
in my program's user interface.
Ultimately, I wound up deciding to store my logs in records that look like:
```rust
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum Record {
String { name: Option, ty: Option, names: Vec, val: String, },
Group { name: Option, ty: Option, names: Vec, val: Vec, },
Svg { name: Option, ty: Option, names: Vec, val: String, }
}
```
The idea here is that anything that I would normally have logged on a single
line with `printf()` or a `Display` or `Debug` method call will be recorded as
a `Record::String`, along with metadata about the computation or event whose
value is being recorded and about what *search terms* the value in question
should be indexed under: i.e., what `names` the value being logged is
responsive for.
My logging interface parallels the design of the `Debug` and `Display` traits,
with two modifications:
```rust
pub trait Log {
fn log(&self, cx: Cx, l: &mut Logger) -> Result<(), Error>;
}
```
1. My trait is generic over a type parameter `Cx` of "context" which can be
used by by trait implementors to make extra information -- such as a closure --
available at value logging time, e.g., in order to give meaning to otherwise
opaque integers or other primary keys that may exist in a complex value being
logged as stand-ins or indices for data that live elsewhere by *calculating all
the "names"* that the value being logged should be indexed by.
2. Analogous to the `std::fmt::Formatter` helper, implementations of my trait
delegate the actual mechanics of logging data structured according to their
expectations to a `&mut Logger` object, here with methods for logging sets,
sequences, maps, their associated elements and pairs, as well as strings,
images, and (nested) groups of the above.
Next, by defining a trait like this one yourself, you can:
a. implement it on whatever types you like, including standard library
container types like `HashMap`s and `BTreeMap`s without requiring
newtype/"Printer" logging wrappers,
b. customize the kinds of information to be logged to fit your UI: for example,
logging hierarchical arrangements of text and images if those are easy to
display because you're working in a GUI and so have access to an HTML renderer,
or to nestable widgets, etc.
Finally, to round out the discussion, here's a fairly representative example of
what the trait impl code looks like for a decently complicated data structure,
in this case, a mapping from original to solved box positions implemented via
some nested `BTreeMap`s in which a "context" closure is used to give meaning to
the otherwise meaningless integers stored in the BTrees by logging the names of
the objects that the integers are indices for:
```rust
/// Marker trait for closures mapping LocIx to names
pub(crate) trait L2n : Fn(VerticalRank, OriginalHorizontalRank) -> Vec> {}
impl Vec>> L2n for CX {}
impl log::Log for BTreeMap> {
fn log(&self, cx: CX, l: &mut log::Logger) -> Result<(), log::Error> {
l.with_map("solved_locs", "SolvedLocs", self.iter(), |lvl, row, l| {
l.with_map(format!("solved_locs[{lvl}]"), "SolvedLocs[i]", row.iter(), |ohr, shr, l| {
let mut src_names = names![lvl, ohr];
src_names.append(&mut cx(*lvl, *ohr));
l.log_pair(
"Loc",
src_names,
format!("{lvl}v, {ohr}h"),
"SolvedHorizontalRank",
names![shr],
format!("{lvl}v, {shr}s")
)
})
})
}
}
```