| Title: | Tree-Style Console Logger for Nested Processes |
| Version: | 0.1.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 |
| RoxygenNote: | 8.0.0 |
| Depends: | R (≥ 4.0) |
| Imports: | cli, rlang, withr |
| Suggests: | covr, jsonlite, knitr, logger, 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 |
| NeedsCompilation: | no |
| Packaged: | 2026-07-28 22:10:36 UTC; sortino |
| Author: | Ivan Sortino [aut, cre, cph] |
| Maintainer: | Ivan Sortino <ivan.sortino97@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-08-06 13:40:08 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, .topcall, .topenv, .timestamp |
Unused; accepted
only because |
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.
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 (requireNamespace("logger", quietly = TRUE)) {
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.
Value
NULL, invisibly.
See Also
layout_logtree() for the underlying layout, with_logging()
for top-level error handling.
Examples
if (requireNamespace("logger", quietly = TRUE)) {
logtree_reset()
logtree_logger(namespace = "logtree_demo")
log_step("Demo step")
logger::log_info("hello", namespace = "logtree_demo")
}
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()
Value
NULL, invisibly.
Examples
logtree_reset()
Add a file sink
Description
Registers an additional output destination. Every logged event fans out to the console sink (always on) 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"))
Arguments
path |
File path to append rendered log lines to. |
format |
|
Value
NULL, invisibly.
Examples
logtree_reset()
logtree_sink_file(tempfile(), format = "text")
with_logging({
log_step("Step one")
})
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)
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
|
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.
Value
The recorded entries, invisibly: a list of records, each a list with
kind, status, msg, path (character vector), and elapsed.
See Also
with_logging(), logtree_reset()
Examples
logtree_reset()
f <- function() {
log_step("Load data")
log_warn("coerced 3 rows")
}
f()
logtree_summary()
Set the active glyph/color theme
Description
Set the active glyph/color theme
Usage
logtree_theme(
theme = c("unicode", "ascii", "emoji"),
overrides = list(),
compact = FALSE
)
Arguments
theme |
Either a preset name ( |
overrides |
A named list of per-key overrides applied on top of
|
compact |
Density of the tree's per-level indentation. |
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 | success glyph (clean close, log_success()) | glyph, width, color |
warning | log_warn() / elevated step glyph | glyph, width, color |
error | log_error() / elevated step glyph | glyph, width, color |
interrupted | abnormal-exit (dimmed) glyph | glyph, width, color |
group | group header marker | glyph, color, bracket |
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
|
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, warning, error, interrupted). |
color | character or NULL | 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"). See cli::combine_ansi_styles(). |
bracket | logical(1) | group slot only. TRUE wraps the header name in < >; default FALSE.
|
Value
NULL, invisibly.
Examples
logtree_theme("ascii")
logtree_theme("unicode")
logtree_theme(overrides = list(success = list(glyph = "*")))
logtree_theme(overrides = list(group = list(glyph = "#", bracket = TRUE)))
logtree_theme("unicode", compact = "medium")
logtree_theme("unicode", compact = "tight")
logtree_theme("unicode")
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. Suppressed log_warn()/log_error() calls still elevate the
enclosing step's close glyph – verbosity only hides the leaf line's own text.
Usage
logtree_threshold(level = c("debug", "info", "warn", "error"))
Arguments
level |
One of |
Value
NULL, invisibly.
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)
Arguments
expr |
Code to run. Omitted when |
summary |
Print an end-of-run summary line? Default |
global |
If |
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
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")
})