logtree 0.2.0
Breaking changes
NDJSON schema: a
"json"sink’stsis now ISO-8601 to the millisecond with a UTC offset ("2026-01-02T03:04:05.000+0000") instead of a bare epoch number, and every record carries arun_idso one run can be picked out of a shared file. The id is derived from the wall clock, the pid and a session counter, so it never perturbs the RNG stream; it is refreshed perwith_logging()call and perlogtree_reset().Verbosity is now a rendering gate only. A warning suppressed by
logtree_threshold("error")still reaches thelogtree_summary()digest (it already elevated its step’s close glyph).summary = TRUE/FALSEstill pin and exclude a line as before.
Sinks
New sink registry:
logtree_sink(fn)registers any function of one argument and returns an id,logtree_sinks()lists ids in firing order, andlogtree_sink_remove(id)unregisters, handing back the removed functions so the removal can be undone.logtree_sink_file()now returns an id too; the console sink lives under the reserved id"console". A sink that throws is skipped with a single warning rather than breaking the fanout.logtree_reset()still leaves sinks alone – they are configuration, and removal is the explicit path.New memory sink for asserting on your own logging:
logtree_sink_memory(max = 1000)buffers events andlogtree_sink_memory_events(id)reads them back as a data frame, one row per event – so a test can ask did this pipeline log what it should without pattern-matching console glyphs. Its columns are a"json"sink’s columns, enforced by a sharedevent_record(), so a replayed log file and the same run read from memory cannot disagree.Per-sink verbosity:
logtree_sink_file(),logtree_sink()andlogtree_sink_memory()takethreshold =, so a log file can capture everything without dragging the console down with it. The globallogtree_threshold()is the default for sinks that do not pin one, read afresh per event. Step open/close lines are never gated.New
logtree_mute()/logtree_unmute()and alogtree.silentoption read at load: gates the fanout while leaving the registry untouched, so a library can silence its own test suite without discarding its sink configuration. Both return the state they replaced. A muted run is still recorded (the flag is checked after the digest sees the event), sologtree_summary()still prints when called; only the unasked-forwith_logging()run-summary line is suppressed. Depth bookkeeping is untouched, and the flag surviveslogtree_reset().
Theme
New
traceslot: an opt-in call-site column saying where in your code a line came from.list(trace = list(show = "problems"))annotates warning/error leaves and interrupted steps;show = TRUEcovers every line that can carry one;showalso takes statuses directly ("error",c("error", "interrupted"),"running"). An ordinary close line never carries one – its site is its own open line’s. The same filter governs thelogtree_summary()digest, andlogtree_summary(trace = )pins it for a single call the waygapandruledo; it can only narrow what was captured, which is whatcapture = TRUEis for (record everywhere, print nothing – a quiet tree with an annotated digest or"json"sink). Content is a template over{fn},{file},{line}(default"{file}:{line} {fn}()"), styled bycolor = list(base=, location=, fn=); the location is printed relative to the working directory and emitted as a terminal hyperlink. It is appended to the message, so it wraps with it and never shears the tree.logtree_sink_file()gains atraceargument.{file}/{line}needkeep.source(absent under plainRscript); runs whose placeholders are all missing are dropped rather than rendered asNA.showisFALSEin all five presets, and with it off nothing is captured. Two call sites were fixed to make this honest: an error caught bywith_logging()now traces to the condition’s own call rather than to the calling handler, and aloggerline now uses the.topcallit passes in rather than tracing tolayout_logtree().New
timestampslot: an opt-in wall-clock column in front of every line.list(timestamp = list(format = "%H:%M:%S"))switches it on;NULL(all five presets) is off. Width is measured from a rendered sample, not the format string, since"%B"is five columns in March and eight in December. It counts against the wrapping budget, and a wrapped message’s continuation rows carry a blank column – one event happened once. Thewith_logging()run-summary line is stamped; thelogtree_summary()digest is not, since it replays events that already happened.logtree_sink_file()gains atimestampargument.New
elapsedslot for the time column on close lines:show = FALSEdrops it,minhides anything faster than a threshold (silencing0.00snoise),colorstyles it, andslow+slow_colorrestyle the ones worth noticing. The run-summary line takes the colouring but never the hiding rules. Defaults leave output unchanged.New
doneslot: a step’s own close line is styled separately fromlog_success()leaves, which keepsuccess. Every preset ships the same glyph in both, so default output is unchanged.The word a step prints when it closes is themeable via a
textfield on a status slot, read from the closing status’s slot and falling back todone’s then"Done"– solist(done = list(text = "Complete"))renames every close line andlist(error = list(text = "Failed"))only the failures.text = ""drops the word;{label}and{elapsed}are expanded, and a template placing{elapsed}itself owns that column.New
crumbslot (glyph,color,path_color) forlogtree_summary()breadcrumbs: path nodes carry an emphasis and the separator its own dimmer style, while a leaf’s message stays unstyled. An angle quote in the unicode and emoji presets, plain>in ascii.New
summaryslot (gap,rule,line):logtree_summary()now sets its digest off from the tree with a blank gap and aclirule labelled with the counts, so it reads as its own block.gapandruleoverride the layout for a single call (rule = FALSErestores the old plain header, a string titles the rule).Two new horizontal knobs on
logtree_theme():glyph_gap(spaces between a line’s status glyph and its message, applied to every line kind so the message column stays aligned) andconnector_gap(spaces between a leaf or close line’s connector and its glyph).connector_gaptrackscol_gapunless set, socompact = "tight"can keep rail columns flush while leaf glyphs still get air (|- i msgrather than|-i msg); it never touches a step’s open line or a group header, and does not compound with depth. Both default to existing behaviour.New
"ci"preset: bracketed word glyphs ([step],[info],[debug],[ok],[done],[warn],[fail],[break]) over pure-ASCII connectors with no colour, so a captured build log survives a runner that strips ANSI and mangles UTF-8 and a failure greps as[fail]. Each word declares its true width, so the message column still lines up; unlikeascii, the corner (\-) differs from the branch (|-).New
"minimal"preset: no tree connectors at all – depth is carried by indentation alone (two columns per level), so output stays legible where box-drawing characters do not survive. Also a lighter glyph vocabulary, dimmed elapsed times, no group marker and wordless close lines. The trade:info,debugandinterruptedall render the middle dot and are told apart by colour.
Other
Console output now wraps by default: long messages are word-wrapped at
cli::console_width()instead of running off the right edge.logtree_theme(wrap = )governs it –TRUEfollows the terminal (measured at render time, so a mid-run resize is picked up), a number pins a width,FALSErestores the old behaviour. Continuation lines indent to the message column and carry the rails down, so a wrapped message still reads as one node. An unbreakable token is split by display width, and a budget narrower than the tree is deep degrades to no wrapping. It covers the digest and run-summary lines too; file sinks are never wrapped.with_logging(warnings = TRUE)routes R’s own conditions into the tree:warning()becomes alog_warn()leaf andmessage()alog_info()leaf, at the depth where they happened.warnings = "warning"routes only the one kind. Off by default, deliberately: routing means muffling – a routed condition no longer reacheswarnings(), the caller’s handlers or stderr. A routed warning also elevates its enclosing step, so wrapping noisy third-party code will turn steps yellow. In global mode the routing applies only while logtree steps are open.The
loggerintegration declares the version it needs:Suggests: logger (>= 0.3.0).logtree_logger()useslogger::appender_void, which arrived in 0.3.0; guards previously checked only whetherloggerwas installed, so an older one failed with'appender_void' is not an exported objectin examples, the vignette and tests instead of being skipped. Guards check the version now, andlogtree_logger()refuses an oldloggerup front.
Bug fixes
logtree_theme()swaps a preset only when one is named. A call passing justoverridesorcompactused to re-resolve thethemedefault and silently reset everything to"unicode". Such a call now merges onto the active theme, as documented; a barelogtree_theme()is a no-op.An unknown slot in a
logtree_theme()override is now an error naming the slot and listing the valid ones, instead of failing insidemodifyList()withis.list(x) is not TRUE.
