| Title: | Tree-Style Console Logger for Nested Processes |
| Version: | 0.2.0 |
| Description: | Render nested process execution as a live, colored tree in the console, with tree connectors, status glyphs, and elapsed time per step. Nesting depth is tracked via frame exit handlers so it never desynchronizes, even when a step errors. Builds on the 'cli' package for console rendering. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| Depends: | R (≥ 4.0) |
| Imports: | cli, rlang, withr |
| Suggests: | covr, jsonlite, knitr, logger (≥ 0.3.0), pkgdown, rmarkdown, testthat (≥ 3.1.4) |
| Config/testthat/edition: | 3 |
| VignetteBuilder: | knitr |
| URL: | https://github.com/IvanSortino/logtree, https://ivansortino.github.io/logtree/ |
| BugReports: | https://github.com/IvanSortino/logtree/issues |
| Config/roxygen2/version: | 8.1.0 |
| NeedsCompilation: | no |
| Packaged: | 2026-09-01 10:13:42 UTC; sortino |
| Author: | Ivan Sortino [aut, cre, cph] |
| Maintainer: | Ivan Sortino <ivan.sortino97@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-09-01 10:30:10 UTC |
logtree: Tree-Style Console Logger for Nested Processes
Description
Render nested process execution as a live, colored tree in the console, with tree connectors, status glyphs, and elapsed time per step. Nesting depth is tracked via frame exit handlers so it never desynchronizes, even when a step errors. Builds on the 'cli' package for console rendering.
Author(s)
Maintainer: Ivan Sortino ivan.sortino97@gmail.com [copyright holder]
Authors:
Ivan Sortino ivan.sortino97@gmail.com [copyright holder]
See Also
Useful links:
Report bugs at https://github.com/IvanSortino/logtree/issues
A logger layout that renders through logtree
Description
Bridges the logger package (https://daroczig.github.io/logger/) into
logtree's tree rendering. logger's own per-call pipeline is
formatter() -> layout() -> appender(): only the layout stage receives
the structured level object (an integer with a "level" attribute
such as "INFO") – appender() only ever sees a pre-formatted
character line – so a custom layout, not a custom appender, is the
correct integration point. Register it as logger's layout and pair it
with logger::appender_void (a ready-made no-op) so that logtree's
rendering, which happens as a side effect of the layout call, is the
only visible output:
Usage
layout_logtree(
level,
msg,
namespace = NA_character_,
.logcall = sys.call(),
.topcall = sys.call(-1),
.topenv = parent.frame(),
.timestamp = Sys.time()
)
Arguments
level |
A |
msg |
Character scalar, already formatted by |
namespace, .logcall, .timestamp |
Unused; accepted only because
|
.topcall |
The call |
.topenv |
Unused; part of |
Details
logger::log_layout(logtree::layout_logtree) logger::log_appender(logger::appender_void)
logger severities map onto logtree leaf levels as: FATAL/ERROR ->
log_error(), WARN -> log_warn(), SUCCESS -> log_success(),
INFO -> log_info(), DEBUG/TRACE -> log_debug() (logger has
two debug-ish tiers, logtree has one, so both collapse to the same
leaf). Note logger's own log_threshold() already gates before the
layout is ever invoked; logtree_threshold() is then an
independent, second gate applied on top of that – both legitimately
apply at once, this is not a bug.
When the trace theme slot is on, the leaf's call site is taken from
logger's own .topcall rather than from logtree's usual frame walk. That
matters: this layout is what calls the leaf, so a frame walk would report
layout_logtree as the origin of every routed line instead of the
logger::log_info() call in your code.
Value
character(0), invisibly. The record is discarded by
logger::appender_void() regardless, so its content is irrelevant;
a zero-length character vector matches logger's layout contract.
Examples
if (rlang::is_installed("logger", version = "0.3.0")) {
logtree_reset()
logger::log_layout(layout_logtree, namespace = "logtree_demo")
logger::log_appender(logger::appender_void, namespace = "logtree_demo")
log_step("Demo step")
logger::log_info("hello", namespace = "logtree_demo")
}
Close a manually-opened step
Description
Closes the step opened by log_open() with the given id, cascading to
any of its still-open descendants (deepest-first). With no id, closes the
nearest open step, so simple last-in-first-out use needs no handle at all.
Usage
log_close(id = NULL, status = NULL)
Arguments
id |
Step handle from |
status |
Optional character scalar overriding the step's final
status: one of |
Details
A step's status only ever escalates via log_warn()/log_error() (see
status elevation); it never comes back down on its own, so a step that
logged an error and then recovered still closes with the error glyph. Pass
status to override that explicitly – this force-assigns the step's
final status regardless of what it escalated to. Because id = NULL
resolves to the nearest open step for both log_open()-managed and
log_step()-managed steps alike, this also lets you close (and override)
a log_step() step early, before its automatic close-on-frame-exit fires.
Value
A list with status and elapsed (seconds) for the step just
closed, invisibly – the same values rendered on its Done line
("running" resolves to "success", as it does for display). NULL,
invisibly, if there was no open step to close.
See Also
Examples
logtree_reset()
log_open("Step 1")
log_info("a child line")
log_close()
logtree_reset()
log_open("Step 2")
log_error("failed once")
log_close(status = "success") # recovered: override the elevated glyph
logtree_reset()
log_open("Step 3")
result <- log_close() # result$status, result$elapsed
Log a debug leaf line
Description
The most verbose leaf level, for fine-grained diagnostic detail that
would be noisy at the default verbosity. Shown only when verbosity is
"debug" (see logtree_threshold()). Like log_info() and
log_success(), it does not elevate the enclosing step's status –
unlike log_warn()/log_error().
Usage
log_debug(msg, close = FALSE, summary = NA)
Arguments
msg |
Character scalar. |
close |
Logical. When |
summary |
Whether to record this line in the |
Value
NULL, invisibly.
Examples
logtree_reset()
logtree_threshold("debug")
log_debug("Cache miss for key user:42")
logtree_threshold("info")
Log an error leaf line
Description
Also elevates the currently-open step's status to "error", so the
step's close line renders the elevated glyph even though the enclosing
function returns normally (see with_logging() for the case where the
step's code actually throws instead).
Usage
log_error(msg, close = FALSE, summary = NA)
Arguments
msg |
Character scalar. |
close |
Logical. When |
summary |
Whether to record this line in the |
Value
NULL, invisibly.
Examples
logtree_reset()
log_error("model timeout after 30s")
Log an informational leaf line
Description
Log an informational leaf line
Usage
log_info(msg, close = FALSE, summary = NA)
Arguments
msg |
Character scalar. |
close |
Logical. When |
summary |
Whether to record this line in the |
Value
NULL, invisibly.
Examples
logtree_reset()
log_info("Reading config.yml")
Open a step under manual lifetime control
Description
Like log_step() but with no automatic close: the step stays open until
you close it yourself with log_close(). This is what you want at top
level (a script or the REPL), where there is no enclosing function frame
for log_step() to hang its close on. You may also attach the step to a
chosen open parent rather than the innermost open step, letting you build
the tree by hand.
Usage
log_open(
msg,
glyph = NULL,
parent = NULL,
group = NULL,
close = FALSE,
key = NULL
)
Arguments
msg |
Character scalar. The step's label. |
glyph |
Optional character scalar overriding this step's glyph. |
parent |
Optional step handle (an id returned by |
group |
Optional named length-1 vector |
close |
Logical. When |
key |
Optional character scalar giving this step a stable identity for
re-run reconciliation, as in |
Details
Opening a step at the same depth as an already-open step – for example by
linking to a shared parent – first closes that sibling and its
descendants, since a new sibling means the previous subtree is done.
Value
The step's id, invisibly. Capture it to pass to log_close() or as
another step's parent.
See Also
Examples
logtree_reset()
s1 <- log_open("Step 1")
log_info("a child line")
log_close(s1)
Open a logged step
Description
log_step() is intended to be called from inside a function: it prints an
opening line for msg and registers an automatic close that fires when the
calling function's frame exits – whether by normal return, early
return(), or an uncaught error propagating through it. Because the close is
registered in the caller's frame rather than inside log_step() itself,
nesting depth always stays in sync, even across errors. At top level, where
there is no enclosing function frame to close on, use log_open() /
log_close() instead.
Usage
log_step(
msg,
glyph = NULL,
parent = NULL,
group = NULL,
close = FALSE,
key = NULL
)
Arguments
msg |
Character scalar. The step's label. |
glyph |
Optional character scalar overriding this step's glyph. |
parent |
Optional step handle (an id returned by |
group |
Optional named length-1 vector |
close |
Logical. When |
key |
Optional character scalar giving this step a stable identity for
re-run reconciliation. At top level (the global env) the label is used
automatically, so re-running the same line re-anchors to that node instead
of nesting under the previous run's leftovers. Pass |
Details
Opening a step at the same depth as an already-open step retires that earlier
sibling automatically – its close line is printed with no explicit
log_close() call. In the default nested pattern each log_step() descends
one level deeper, so this same-level retirement applies when you place steps
side by side via an explicit parent.
Value
The step's internal id, invisibly.
Examples
logtree_reset()
f <- function() {
log_step("Doing work")
}
f()
Log a success leaf line
Description
Log a success leaf line
Usage
log_success(msg, close = FALSE, summary = NA)
Arguments
msg |
Character scalar. |
close |
Logical. When |
summary |
Whether to record this line in the |
Value
NULL, invisibly.
Examples
logtree_reset()
log_success("Validated 12 parameters")
Log a warning leaf line
Description
Also elevates the currently-open step's status to "warning" (unless it
is already "error"), so the step's close line renders the elevated
glyph even though the enclosing function returns normally.
Usage
log_warn(msg, close = FALSE, summary = NA)
Arguments
msg |
Character scalar. |
close |
Logical. When |
summary |
Whether to record this line in the |
Value
NULL, invisibly.
Examples
logtree_reset()
log_warn("Retry 1/3 due to timeout")
Route the logger package through logtree
Description
Call once near the top of a script to make the logger package
(https://daroczig.github.io/logger/) render through logtree. It
registers layout_logtree() as logger's layout and
logger::appender_void as its appender for namespace, so from then on
every logger::log_info() / log_warn() / ... call in that namespace
prints as a logtree leaf. This is the one-call form of the manual
logger::log_layout() + logger::log_appender() pairing.
Usage
logtree_logger(namespace = "global", threshold = TRUE)
Arguments
namespace |
|
threshold |
Open |
Details
With threshold = TRUE (the default) it also opens logger's own
threshold to TRACE for the namespace. logger gates on its threshold
before the layout runs, so without this a logger::log_debug() would
never reach logtree; opening it makes logtree_threshold() the single
effective gate.
Bridge only: it does not install error handling. Wrap the run body in
with_logging() as well when you want failed-run elevation and a summary
line. The change is persistent for the session (matching logger's own
global configuration style); there is no automatic teardown.
Needs logger >= 0.3.0, the release that added appender_void – the no-op
appender this pairs the layout with, so that logtree's rendering is the only
visible output. An older logger is refused with a message saying so rather
than failing later on a missing object.
Value
NULL, invisibly.
See Also
layout_logtree() for the underlying layout, with_logging()
for top-level error handling.
Examples
if (rlang::is_installed("logger", version = "0.3.0")) {
logtree_reset()
logtree_logger(namespace = "logtree_demo")
log_step("Demo step")
logger::log_info("hello", namespace = "logtree_demo")
}
Silence logtree's output
Description
Stops every sink – console, files, and any of your own – from receiving events, without unregistering them. This is what a library that logs with logtree reaches for to keep its own test suite quiet, and what a script reaches for around a noisy section.
Usage
logtree_mute()
logtree_unmute()
Details
Three things muting deliberately does not do:
It does not stop the run being recorded. Warnings and errors still reach the
logtree_summary()digest, so a muted run can still be asked what went wrong at the end – andlogtree_summary()still prints when you call it, since that output is one you asked for rather than one that happened to you. Thewith_logging()run-summary line, which you did not ask for, is silenced.It does not disturb step bookkeeping. Steps still open and close while muted, so depth is still correct when output comes back.
It does not survive... anything, actually: like registered sinks, and unlike the open-step stack, the mute flag is left alone by
logtree_reset(). Unmute explicitly.
The logtree.silent option sets the initial state when the package is
loaded, for silencing logtree from an .Rprofile or a test setup file; after
that these functions are in charge, so a later options(logtree.silent = )
has no effect.
Value
The previous muted state (TRUE/FALSE), invisibly, so a caller can
restore whatever it found.
See Also
logtree_sink_remove() for taking a sink off altogether.
Examples
logtree_reset()
was <- logtree_mute()
f <- function() {
log_step("Load data")
log_warn("coerced 3 rows")
}
invisible(f()) # prints nothing
logtree_unmute()
# ... but the run was still recorded:
length(logtree_summary())
# Restore whatever was in force before, rather than assuming it was off.
was <- logtree_mute()
if (!was) logtree_unmute()
Reset internal logtree state
Description
Clears the open-step stack and resets the internal id counter. Mainly
useful for tests and interactive/knitr re-runs where a previous run may
have left the stack non-empty (e.g. after an uncaught error with no
with_logging() wrapper).
Usage
logtree_reset()
Details
Registered sinks are deliberately not cleared: they are configuration
rather than run state, so a file sink set up once keeps recording across
resets. Take one off with logtree_sink_remove().
Value
NULL, invisibly.
Examples
logtree_reset()
Register a sink of your own
Description
Adds an arbitrary function as an output destination. Every logged event fans
out to each registered sink in registration order, so a custom sink runs
alongside the console and any file sinks. Use it to push events at a
database, a metrics collector, or an in-test collector of your own; for the
built-in file destinations see logtree_sink_file().
Usage
logtree_sink(fn, threshold = NULL)
Arguments
fn |
A function of one argument, called with each emitted event. Its return value is ignored. |
threshold |
Minimum leaf level this sink receives: one of |
Details
fn is called with one argument, the event: a list whose kind is one of
"open", "close", "group", "group_close" or "leaf". Step-shaped
events (everything but "leaf") carry the step's record under entry, while
a leaf carries its own fields directly.
A sink that throws does not take the rest of the fanout down with it: the
error is caught, the remaining sinks still run, and a warning naming the sink
is raised once (per sink, until the next logtree_reset()). A logger should
witness failures, not become a source of them.
Value
The sink's id, invisibly – pass it to logtree_sink_remove().
See Also
logtree_sinks(), logtree_sink_remove(), logtree_sink_file(),
logtree_sink_memory()
Examples
logtree_reset()
seen <- character(0)
h <- logtree_sink(function(event) seen <<- c(seen, event$kind))
f <- function() log_step("Step one")
invisible(f())
seen
logtree_sink_remove(h)
# A sink that only ever hears about failures.
h <- logtree_sink(function(event) invisible(NULL), threshold = "error")
logtree_sink_remove(h)
Add a file sink
Description
Registers an additional output destination. Every logged event fans out to the console sink and every registered file sink, so console, text-file, and NDJSON outputs can all run simultaneously (design doc section 6).
Usage
logtree_sink_file(
path,
format = c("text", "json"),
trace = NULL,
timestamp = NULL,
threshold = NULL
)
Arguments
path |
File path to append rendered log lines to. |
format |
|
trace |
Whether this sink prints the call-site column (see the |
timestamp |
Whether this sink prints the wall-clock column (see the
|
threshold |
Minimum leaf level this file records: one of |
Details
A "json" sink writes one NDJSON object per event, with these fields:
| Field | What it holds |
ts | when the event was emitted, ISO-8601 to the millisecond with UTC offset |
run_id | identifies the run, so one run's lines can be picked out of a shared file |
level | event kind: "open", "close", "group", "group_close", "leaf" |
id, parent_id, depth | the node's identity and place in the tree |
label | a step's label, a group's name, or a leaf's message |
elapsed | seconds, on close lines only; null elsewhere |
status | a leaf's status, "step"/"group" on an opening line, or the resolved status on a close |
fn, file, line | the call site, when the trace theme slot recorded one; null otherwise
|
Value
The sink's id, invisibly – pass it to logtree_sink_remove() to
stop writing to this file.
See Also
logtree_sink() for a sink of your own, logtree_sinks() and
logtree_sink_remove() for the registry.
Examples
logtree_reset()
logtree_sink_file(tempfile(), format = "text")
with_logging({
log_step("Step one")
})
# A file that records call sites even with the console column off.
logtree_sink_file(tempfile(), format = "text", trace = TRUE)
# A debug-level record on disk while the console stays at "info".
logtree_sink_file(tempfile(), format = "json", threshold = "debug")
# A file that stamps every line with the date and time.
logtree_sink_file(tempfile(), format = "text", timestamp = TRUE)
Collect logged events in memory
Description
Registers a sink that keeps every event in a buffer instead of writing it
anywhere, so a run's logging can be asserted on rather than eyeballed.
Read the buffer back with logtree_sink_memory_events(). This is the
answer to "did my pipeline log what it should have?" – previously that meant
capturing console output and pattern-matching the rendered tree.
Usage
logtree_sink_memory(max = 1000, threshold = NULL)
Arguments
max |
Maximum number of events to keep. Once the buffer is full the
oldest events are dropped, so what you read back is always the most recent
|
threshold |
Minimum leaf level to collect, as in |
Details
The buffer is dropped when the sink is removed with logtree_sink_remove(),
and it is capped: a long-running process cannot grow it without bound.
Value
The sink's id, invisibly – pass it to
logtree_sink_memory_events() to read the buffer, and to
logtree_sink_remove() to stop collecting.
See Also
logtree_sink_memory_events(), logtree_sink()
Examples
logtree_reset()
h <- logtree_sink_memory()
f <- function() {
log_step("Load data")
log_warn("coerced 3 rows")
}
invisible(f())
events <- logtree_sink_memory_events(h)
events[, c("level", "label", "status")]
logtree_sink_remove(h)
Read a memory sink's collected events
Description
Returns everything a logtree_sink_memory() sink has collected so far, as a
data frame with one row per event and the same columns a "json" file sink
writes (see logtree_sink_file()), so the two views of a run agree:
Usage
logtree_sink_memory_events(id)
Arguments
id |
A memory sink's id, as returned by |
Details
| Column | What it holds |
ts | when the event was emitted (POSIXct) |
level | event kind: "open", "close", "group", "group_close", "leaf" |
id, parent_id, depth | the node's identity and place in the tree |
label | a step's label, a group's name, or a leaf's message |
elapsed | seconds, on close lines only; NA elsewhere |
status | a leaf's status, "step"/"group" on an opening line, or the resolved status on a close |
fn, file, line | the call site, when the trace theme slot recorded one; NA otherwise
|
Value
A data frame with one row per collected event, oldest first, and zero rows (with the columns above) when nothing has been logged yet.
See Also
Examples
logtree_reset()
h <- logtree_sink_memory()
f <- function() log_info("Reading config.yml")
invisible(f())
logtree_sink_memory_events(h)
logtree_sink_remove(h)
Remove registered sinks
Description
Unregisters one or more sinks by id. Ids that are not registered are ignored,
so cleanup code can run unconditionally. The reserved "console" id can be
removed like any other, which is how a library silences logtree's console
output outright.
Usage
logtree_sink_remove(id)
Arguments
id |
Character vector of sink ids, as returned by |
Details
Sinks deliberately survive logtree_reset(), so this is the only way to take
one off again.
Value
The removed sink functions, invisibly: a named list keyed by the ids
actually removed (empty when none matched). Re-registering one with
logtree_sink() restores it, under a fresh id and therefore at the end of
the firing order.
See Also
logtree_sink(), logtree_sinks()
Examples
logtree_reset()
h <- logtree_sink(function(event) invisible(NULL))
logtree_sinks()
logtree_sink_remove(h)
logtree_sinks()
List the registered sinks
Description
The ids of every sink currently registered, in the order they fire. The
console sink is always first under the reserved id "console" unless it has
been removed.
Usage
logtree_sinks()
Value
A character vector of sink ids.
See Also
logtree_sink(), logtree_sink_remove()
Examples
logtree_sinks()
Report a digest of notable events
Description
Prints a compact end-of-run digest of everything worth attention that
happened since the last logtree_reset(): every warning and error leaf line,
plus any step that closed with a warning, error, or interrupted status.
Each entry shows the status glyph, the breadcrumb path to where it happened,
and the message (for leaf lines) or an outcome word (for steps).
Usage
logtree_summary(
filter = NULL,
depth = NULL,
gap = NULL,
rule = NULL,
trace = NULL
)
Arguments
filter |
Optional character vector of statuses to include, e.g.
|
depth |
Optional positive integer limiting how many trailing (deepest)
breadcrumb nodes are printed. The message counts as the terminal node, so
|
gap |
Number of blank lines printed between the last log line and the
digest; |
rule |
Divider drawn above the digest. |
trace |
Pins the digest's call-site column for this call, overriding the
theme's |
Details
Unlike scrolling the live tree, the digest surfaces breakage even when no
with_logging() handler was installed – interrupted steps are picked up
from their close lines. Ordinary info / success lines are excluded unless
logged with summary = TRUE; a warning or error can be excluded with
summary = FALSE.
The digest's appearance comes from the active theme, so it is customised
through logtree_theme() like everything else: the crumb slot sets the
breadcrumb separator and the emphasis on the path nodes, the summary slot
the divider (gap, rule, line). gap, rule and trace below override
the theme for a single call.
Value
The recorded entries, invisibly: a list of records, each a list with
kind, status, msg, path (character vector), elapsed, and trace
(the call site: a list of fn, file and line, or NULL when the
trace theme slot was off and nothing was captured).
See Also
with_logging(), logtree_reset()
Examples
logtree_reset()
f <- function() {
log_step("Load data")
log_warn("coerced 3 rows")
}
f()
logtree_summary()
# Flush against the tree, with a titled divider.
logtree_summary(gap = 0, rule = "Run report")
# Call sites in the digest only: the tree above stays as it was rendered.
logtree_theme(list(trace = list(show = TRUE)))
f()
logtree_summary(trace = "error")
logtree_theme("unicode")
# Set the layout and the breadcrumb symbol once, on the theme.
logtree_theme(list(
summary = list(gap = 2, rule = FALSE),
crumb = list(glyph = " / ")
))
logtree_summary()
logtree_theme("unicode")
Set the active glyph/color theme
Description
Set the active glyph/color theme
Usage
logtree_theme(
theme = NULL,
overrides = list(),
compact = FALSE,
glyph_gap = NULL,
connector_gap = NULL,
wrap = NULL
)
Arguments
theme |
Either a preset name to swap the whole glyph set, or a named
list of per-key overrides to merge onto the currently active theme
(matching the two calling styles shown in the package documentation).
| ||||||||||||
overrides |
A named list of per-slot overrides applied on top of
| ||||||||||||
compact |
Density of the tree's per-level indentation. | ||||||||||||
glyph_gap |
Number of spaces printed between a line's status glyph and
its message text. | ||||||||||||
connector_gap |
Number of spaces printed between a leaf or close
line's own connector and its status glyph – | ||||||||||||
wrap |
Column budget a rendered line is wrapped to, instead of letting
a long message run off the right edge. Continuation lines indent to the message column and carry the rails down,
so a wrapped message still reads as one node of the tree: after a branch
connector the vertical rail continues, after a corner it does not. A
step-open line (and a group header) also rails its own glyph column,
which is where its children hang – on a depth-1 root that is the only
rail there is. A leaf's glyph column stays blank: nothing nests under a
leaf. A token with no break opportunity – a long path, a URL – is split
by display width rather than left to overflow, and a budget narrower than
the tree is deep degrades to no wrapping rather than to an unusable
one-column line.
It applies to every line logtree renders, including the
Like |
Details
An override list is keyed by slot; each slot's value is itself a named list of fields. Only the fields you name are changed – everything else is kept from the active theme.
Slots (valid names in an override / preset list):
| Slot | Applies to | Fields it accepts |
step | open / running step glyph | glyph, width, color |
info | log_info() leaf | glyph, width, color |
debug | log_debug() leaf | glyph, width, color |
success | log_success() leaf | glyph, width, color |
done | a step's own close line on a clean close | glyph, width, color, text |
warning | log_warn() / elevated step glyph | glyph, width, color, text |
error | log_error() / elevated step glyph | glyph, width, color, text |
interrupted | abnormal-exit (dimmed) glyph | glyph, width, color, text |
group | group header marker | glyph, color, bracket |
elapsed | the elapsed-time column printed on every close line | show, min, color, slow, slow_color |
trace | the optional call-site column: where in your code a line came from | show, capture, format, color |
timestamp | the optional wall-clock column printed in front of every line | format, color |
branch | child connector: the "tee" drawn before every child line | glyph, color |
corner | close-line connector: the "elbow" drawn on a step's own close line | glyph, color |
pipe | vertical rail carried down the left of nested lines | glyph, color |
crumb | logtree_summary() breadcrumb: the separator between path nodes | glyph, color, path_color |
summary | logtree_summary() divider above the digest | gap, rule, line
|
success and done are separate slots that merely look the same by
default (every preset ships the same tick in both): success styles the
log_success() leaf line, done styles the Done line a step prints when
it closes cleanly. Override one and the other is untouched. A step that
closes elevated still renders warning / error / interrupted, so done
only ever governs the clean close.
The same split governs the text field, the word a close line prints before
its elapsed time. It is read from the closing status's own slot, falling back
to done's and then to the built-in "Done" – so
list(done = list(text = "Complete")) renames every close line, while
list(error = list(text = "Failed")) renames only the ones that went wrong.
success has no text of its own precisely because a clean close reads
done. text is a close-line concern only: it never touches the message a
log_warn() or log_error() leaf prints.
The trace slot is off in every preset, and deliberately so: capturing a call
site costs a frame walk and a source-reference lookup on every logged line, so
you opt in and the default path pays nothing. Switch it on with
list(trace = list(show = "problems")) to annotate only what went wrong, or
show = TRUE for every line that can carry a call site. show also takes a
vector of statuses, for when the bundle is too much: show = "error"
annotates errors and leaves tolerated warnings bare, show = c("error", "interrupted") adds the steps that never finished. The column is appended to
the message, so it wraps with it rather than shearing the tree.
The location – {file} and {line} together, separator included – is also
emitted as one terminal hyperlink pointing at the file, at that line, so a
click anywhere on it opens your editor there. Terminals without
hyperlink support print the same text unlinked, and the escape has no
printable width either way. {file} prints relative to the working directory
where the source sits under it – a bare file name is not something a
terminal can resolve.
On source references. {file} and {line} come from R's source
references, which exist only when the code was parsed with keep.source = TRUE. That is the default in an interactive session and under
devtools::load_all(), but not under plain Rscript and not for an
installed package. {fn} is always available. Rather than print NA, the
expander drops any whitespace-separated run of the template whose placeholders
are all unavailable – so the default format degrades from
pipeline.R:12 load_data() to load_data(), and a "{file}:{line}" format
degrades to no column at all. Set options(keep.source = TRUE) at the top of
a script if you want locations under Rscript.
Capturing and printing are separate: show decides what a line prints,
capture = TRUE decides that call sites are recorded whatever show
prints. list(trace = list(show = FALSE, capture = TRUE)) therefore leaves
the tree exactly as it was while giving the digest and any "json" sink
locations to work with. The order matters – capture happens as the run
unfolds, so a call site not recorded then cannot be recovered afterwards.
The timestamp slot is off in every preset for a different reason: a tree
read as it happens does not need to be told the time, and a column that is not
there is one the message has room for. It is the log read afterwards that
wants it – lining a run up against a monitoring graph, another service's log,
or a report that something broke at about half past two. Switch it on with
list(timestamp = list(format = "%H:%M:%S")).
The column is padded to a fixed width measured from a rendered sample, not
from the format string, so a format whose width varies with the value ("%B",
March one month and December the next) cannot shear the tree from one
line to the next. It counts against
the wrapping budget like any other column, and a wrapped message's
continuation rows carry a blank column rather than a repeated time – one
event happened once. logtree_summary()'s digest carries no timestamp at all:
it replays events that already happened, so stamping those lines with the time
the digest was printed would be a lie.
Two places outside the tree also report call sites when the slot is on:
logtree_summary()'s digest lines, which apply the same status filter as the
tree does, and the fn / file / line fields of a "json" sink (which
carries them whenever they were captured, regardless of show). See
logtree_sink_file() for pinning a file sink's column independently of the
console's.
Fields (valid names inside a slot):
| Field | Type | Accepted values |
glyph | character(1) | Any string, including "". In package source, non-ASCII must be written as \u/\U escapes, never literal characters. |
width | integer(1) | Rendered display width of glyph (1 for normal, 2 for emoji / wide cells). Drives column alignment and cannot be measured, so set it to the true width. Status slots only (step, info, debug, success, done, warning, error, interrupted). |
color | character, NULL, or a named list on trace | One or more cli styles, or NULL for no styling. Named colors ("red", "cyan", "silver", ...), bright variants ("br_red"), backgrounds ("bg_blue"), text styles ("bold", "italic", "dim"), or a hex string ("#ff8800"). A character vector combines styles, e.g. c("red", "bold"). On the elapsed slot it styles the time itself. On trace it also accepts a named list styling the parts of the column separately: location for a {file}/{line} run (the separator between them included -- the location is one thing, styled and linked whole), fn for the function name, and base for everything else, which in the default format is the (). That is what the coloured presets ship: all of it dim, with the location in silver and fn in cyan, so the two read apart. A plain character vector on trace styles the whole column. NULL in the colourless ascii and ci presets. See cli::combine_ansi_styles(). |
text | character(1) | Close-line status slots only (done, warning, error, interrupted). The word a close line prints before its elapsed time; "" drops it, leaving the glyph and the time. Two placeholders are expanded: {label} (the closing step's own label, or a group's name) and {elapsed} (the formatted time). A template that places {elapsed} itself owns that column, so the time is not appended after it a second time. |
show | logical(1), or character on trace | On elapsed: FALSE drops the elapsed-time column entirely, default TRUE. On trace: FALSE (the default in every preset) off entirely; TRUE every line that can carry a call site; "problems" a shorthand for c("warning", "error", "interrupted"); or a vector of statuses naming exactly what to annotate -- "running" (open lines), "info", "debug", "success", "warning", "error" (leaves of that status) and "interrupted" (a close line whose step unwound). show = "error" is errors without their warnings. An ordinary close line never carries one whatever the set: its site is its own open line's. Unknown tokens are dropped, and anything unrecognised reads as FALSE. |
format | character(1) | On trace: a template for the call-site column over three placeholders: {fn} (the enclosing function's name), {file} and {line} (where the log call sits). Default "{file}:{line} {fn}()". A whitespace-separated run whose placeholders are all unavailable is dropped whole, so the default degrades to load_data() rather than printing NA -- see the note on source references below. On timestamp: a strftime format such as "%H:%M:%S" or "%Y-%m-%d %H:%M:%S", or NULL (the default in every preset) for no column at all. |
capture | logical(1) | trace slot only. TRUE records a call site on every line even where show prints none, for "record, print later": a quiet console whose logtree_summary() digest or "json" sink still carries locations. Default FALSE in every preset. It only ever adds -- a show that asks for a column already implies capture, and capture = FALSE never takes that away. This is the one part of the feature that cannot be decided after the fact: a call site not recorded while the run happened is gone, because the frame stack it came from has unwound. |
min | numeric(1) | elapsed slot only. Hide times below this many seconds -- min = 0.1 silences the 0.00s noise on trivial steps. Default 0 (show everything). |
slow | numeric(1) or NULL | elapsed slot only. Times at or over this many seconds count as slow and are styled with slow_color instead of color. NULL (the default) means nothing is ever flagged. |
slow_color | character or NULL | elapsed slot only. Styles applied to a slow time in place of color. Same accepted values as color; "yellow" in the unicode, emoji and minimal presets, NULL in the colourless ascii and ci presets. |
bracket | logical(1) | group slot only. TRUE wraps the header name in < >; default FALSE. |
path_color | character or NULL | crumb slot only. Styles the breadcrumb's path nodes, setting them apart from a leaf's message (which stays unstyled). Same accepted values as color; "bold" in the unicode and emoji presets, NULL in ascii. |
gap | integer(1) | summary slot only. Blank lines printed above the digest; 0 prints it flush against the tree. |
rule | logical(1) or character(1) | summary slot only. TRUE draws a cli::rule() labelled with the digest header, FALSE draws none, a string sets a custom title. |
line | integer(1) or character(1) | summary slot only. The rule's line, passed to cli::rule()'s line: a line type (1-8, "double", ...) or the string to repeat ("-" in the ascii preset).
|
Value
NULL, invisibly.
Examples
logtree_theme("ascii")
logtree_theme("unicode")
# No preset named: these merge onto the active theme, whichever it is.
logtree_theme(overrides = list(success = list(glyph = "*")))
# The close ("Done") tick is its own slot, restyled independently:
logtree_theme(list(done = list(glyph = "=", color = "silver")))
logtree_theme(list(group = list(glyph = "#", bracket = TRUE)))
logtree_theme(list(crumb = list(glyph = " / ", path_color = "cyan")))
logtree_theme(list(summary = list(gap = 2, rule = "Run report")))
# The word on a close line: every one at once, or only the failures.
logtree_theme(list(done = list(text = "Complete")))
logtree_theme(list(error = list(text = "Failed")))
logtree_theme(list(done = list(text = "{label} took {elapsed}")))
logtree_theme(list(done = list(text = ""))) # glyph + time only
# The elapsed-time column: hide the trivial, flag the slow.
logtree_theme(list(elapsed = list(min = 0.1)))
logtree_theme(list(elapsed = list(color = "silver", slow = 5,
slow_color = "red")))
logtree_theme(list(elapsed = list(show = FALSE)))
# The call-site column: off by default, loudest on the lines that matter.
logtree_theme(list(trace = list(show = "problems")))
logtree_theme(list(trace = list(show = TRUE)))
logtree_theme(list(trace = list(show = "error"))) # errors only
# Record call sites but print none: the digest can still show them.
logtree_theme(list(trace = list(show = FALSE, capture = TRUE)))
logtree_theme(list(trace = list(show = c("error", "interrupted"))))
logtree_theme(list(trace = list(show = TRUE, format = "{fn}()")))
logtree_theme(list(trace = list(format = "{file}:{line}", color = "silver")))
logtree_theme(list(trace = list(show = FALSE))) # back off again
# The wall-clock column: off by default, in front of every line when on.
logtree_theme(list(timestamp = list(format = "%H:%M:%S")))
logtree_theme(list(timestamp = list(format = "%Y-%m-%d %H:%M:%S",
color = "silver")))
logtree_theme(list(timestamp = list(format = NULL))) # back off again
# Naming one swaps the whole preset, clearing every override above.
logtree_theme("unicode")
logtree_theme("unicode", compact = "medium")
logtree_theme("unicode", compact = "tight")
# Tight rails, but with the glyph spaced off its connector.
logtree_theme("unicode", compact = "tight", connector_gap = 1)
# Spacing between the glyph and the message.
logtree_theme(glyph_gap = 0) # tightest: no space after the glyph
logtree_theme(glyph_gap = 2) # roomier message column
# Wrapping long messages (on by default, at the console width).
logtree_theme(wrap = 72) # pin a fixed width
logtree_theme(wrap = FALSE) # let long lines overflow instead
logtree_theme(wrap = TRUE) # back to the console width
# The other two presets.
logtree_theme("minimal") # no connectors: indentation only
logtree_theme("ci") # [ok] / [warn] / [fail], no colour
logtree_theme("unicode") # back to the default
Set the minimum log level threshold to render
Description
Leaf lines below this level are silently skipped: log_debug() counts as
"debug", log_info() and log_success() count as "info", log_warn()
as "warn", log_error() as "error". Step open/close lines always
render regardless of verbosity, since hiding them would break the tree
structure.
Usage
logtree_threshold(level = c("debug", "info", "warn", "error"))
Arguments
level |
One of |
Details
This is the default for every sink; a sink registered with its own
threshold ignores it (see logtree_sink_file() and logtree_sink()), which
is what lets a debug-level log file coexist with an ordinary console.
Verbosity governs rendering only, never what the run remembers. A suppressed
log_warn()/log_error() still elevates the enclosing step's close glyph,
and still reaches the logtree_summary() digest – what is hidden is the leaf
line's own text, not the fact that it happened.
Value
NULL, invisibly.
See Also
logtree_sink_file() and logtree_sink() for per-sink thresholds.
Examples
logtree_threshold("info")
Run an expression with top-level error handling and a run summary
Description
Wrap a script or pipeline's top-level call in with_logging() so an
uncaught error leaves a clean, correctly-colored tree instead of dimmed
"interrupted" steps. On error, every currently open step is marked
failed, the error is logged as a leaf line, then rethrown –
with_logging() never silently swallows errors. It also prints a
"Run complete" / "Run failed" summary line with elapsed time.
Usage
with_logging(expr, summary = TRUE, global = FALSE, warnings = FALSE)
Arguments
expr |
Code to run. Omitted when |
summary |
Print an end-of-run summary line? Default |
global |
If |
warnings |
Route R's own conditions into the tree as leaf lines?
Two consequences to weigh before switching it on:
In global mode the routing applies only while logtree steps are open, so a session-persistent handler cannot swallow warnings from unrelated code. |
Details
Note: expr is lazily evaluated, so log_step() calls written inside
the { ... } block close when the function lexically enclosing that
block returns – not necessarily when with_logging() itself returns.
Use with_logging({ ... }) as a function's entire body to keep these
in sync; if other code runs after the call in the same function, steps
opened inside the block stay open until that function returns.
The global = TRUE form is meant for the top level of a script, where
there is no frame to wrap. It is not shown in the examples below because
it installs a session-persistent handler and is only meaningful for an
error that reaches top level:
with_logging(global = TRUE)
log_open("Load data")
stop("EOF") # marks the open step failed + logs "EOF" before R exits
warnings = TRUE additionally routes R's own conditions into the tree:
a warning() raised by wrapped code becomes a log_warn() leaf and a
message() becomes a log_info() leaf, in place rather than on stderr, so
the tree is a complete record of the run rather than half of one. It is
opt-in because routing means muffling – see the argument's own
documentation below for what that costs.
Value
In block mode, the value of expr, invisibly. In global mode,
NULL, invisibly.
Examples
logtree_reset()
with_logging({
log_step("Step one")
log_success("done")
})
# R's own conditions routed into the tree instead of onto stderr.
logtree_reset()
with_logging({
log_step("Load data")
warning("3 rows coerced to NA")
message("using cached manifest")
}, warnings = TRUE)