Compact, high-signal stack traces (Roadmap 05, idea D1).
A failed assertion or exception usually produces a wall of java.*, clojure.lang.*,
nREPL and generated invokeStatic frames, burying the two frames that actually name your
code. This namespace is split hard between classification and printing:
frame-category and interesting-frame?
answer "is this frame mine?" from plain data, no exceptions required;clean-stacktrace returns the surviving frames (never truncates);format-stacktrace renders them, eliding the middle so the throw site and the
entry point stay visible.Detail level resolves as: explicit :detail/:max-frames opts > *stacktrace-detail*
D_CORE_STACKTRACE_DETAILenv var >:normal.
Filters are overridable per call via :keep / :drop predicates over a normalized frame
map, so nobody debugging clojure.lang internals is stuck with our taste.
Your own code is recognised by namespace prefix. d_core/d-core are built in; a different
project registers its prefix once, with no code required:
D_CORE_PROJECT_NS=my-app,my-app-workers clojure -M:test
or at runtime with register-project-ns!, or per call with :extra-project.
Compact, high-signal stack traces (Roadmap 05, idea D1).
A failed assertion or exception usually produces a wall of `java.*`, `clojure.lang.*`,
nREPL and generated `invokeStatic` frames, burying the two frames that actually name your
code. This namespace is split hard between *classification* and *printing*:
- classification is pure and table-driven: [[frame-category]] and [[interesting-frame?]]
answer "is this frame mine?" from plain data, no exceptions required;
- [[clean-stacktrace]] returns the surviving frames (never truncates);
- [[format-stacktrace]] renders them, eliding the middle so the throw site *and* the
entry point stay visible.
Detail level resolves as: explicit `:detail`/`:max-frames` opts > [[*stacktrace-detail*]]
> `D_CORE_STACKTRACE_DETAIL` env var > `:normal`.
Filters are overridable per call via `:keep` / `:drop` predicates over a normalized frame
map, so nobody debugging `clojure.lang` internals is stuck with our taste.
Your own code is recognised by namespace prefix. `d_core`/`d-core` are built in; a different
project registers its prefix once, with no code required:
D_CORE_PROJECT_NS=my-app,my-app-workers clojure -M:test
or at runtime with [[register-project-ns!]], or per call with `:extra-project`.Global detail for stack-trace rendering: :compact, :normal, or :full.
The root value is nil, meaning unset — an explicit binding always wins, and an unbound
var falls through to D_CORE_STACKTRACE_DETAIL and then to default-detail. That is
what lets the environment variable take effect at all.
Global detail for stack-trace rendering: `:compact`, `:normal`, or `:full`. The root value is nil, meaning *unset* — an explicit `binding` always wins, and an unbound var falls through to `D_CORE_STACKTRACE_DETAIL` and then to [[default-detail]]. That is what lets the environment variable take effect at all.
(clean-stacktrace throwable)(clean-stacktrace throwable overrides)The full vector of interesting frames for throwable (or for a seq of frames).
Never truncates: elision is a printing concern, so pair this with format-stacktrace
when you want bounded output. Accepts the same :keep / :drop overrides as
interesting-frame?.
The full vector of interesting frames for `throwable` (or for a seq of frames). Never truncates: elision is a *printing* concern, so pair this with [[format-stacktrace]] when you want bounded output. Accepts the same `:keep` / `:drop` overrides as [[interesting-frame?]].
Namespace prefixes treated as your code out of the box.
Namespace prefixes treated as *your* code out of the box.
(demunge-ns n)Reverse Clojure's symbol munging on a namespace name: d_core.libs.stacktrace_test
becomes d-core.libs.stacktrace-test.
This is load-bearing. Clojure compiles (ns foo-bar.baz) into a JVM class named
foo_bar.baz, and the slash that would make package-prefix matching work is gone, so
every project frame looks like third-party noise until it is de-munged.
Reverse Clojure's symbol munging on a namespace name: `d_core.libs.stacktrace_test` becomes `d-core.libs.stacktrace-test`. This is load-bearing. Clojure compiles `(ns foo-bar.baz)` into a JVM class named `foo_bar.baz`, and the slash that would make package-prefix matching work is gone, so every project frame looks like third-party noise until it is de-munged.
(env-detail)Detail level requested by the D_CORE_STACKTRACE_DETAIL environment variable, or nil.
Detail level requested by the `D_CORE_STACKTRACE_DETAIL` environment variable, or nil.
(env-project-namespaces)Namespace prefixes requested by the D_CORE_PROJECT_NS environment variable, or nil.
D_CORE_PROJECT_NS=my-app,my_app.workers clojure -M:test
Namespace prefixes requested by the `D_CORE_PROJECT_NS` environment variable, or nil. D_CORE_PROJECT_NS=my-app,my_app.workers clojure -M:test
(format-stacktrace throwable)(format-stacktrace throwable {:keys [detail max-frames] :as overrides})Render throwable (or a seq of frames) as a compact, legible string.
Options:
:detail :compact (3 frames) | :normal (5) | :full (all, capped at 200):max-frames explicit budget, overriding :detail:color? ANSI colors (default false — this namespace never guesses about TTY;
D3 owns the global color/verbosity policy and will set it here):keep/:drop predicate overrides, as in interesting-frame?The exception header is included only when given an actual Throwable.
Render `throwable` (or a seq of frames) as a compact, legible string.
Options:
- `:detail` `:compact` (3 frames) | `:normal` (5) | `:full` (all, capped at 200)
- `:max-frames` explicit budget, overriding `:detail`
- `:color?` ANSI colors (default false — this namespace never guesses about TTY;
D3 owns the global color/verbosity policy and will set it here)
- `:keep`/`:drop` predicate overrides, as in [[interesting-frame?]]
The exception header is included only when given an actual `Throwable`.(frame x)Normalize a StackTraceElement, an already-normalized map, or any frame-like value into
a plain map {:class :method :file :line}. Total: never throws, returns nil for junk.
Keeping frames as maps is what makes classification a pure function over data.
Normalize a `StackTraceElement`, an already-normalized map, or any frame-like value into
a plain map `{:class :method :file :line}`. Total: never throws, returns nil for junk.
Keeping frames as maps is what makes classification a pure function over data.How many project frames each detail level shows before eliding the middle.
How many project frames each detail level shows before eliding the middle.
(frame-category f)(frame-category f overrides)Classify a frame as :plumbing, :jvm, :library, :test, :project, or :unknown.
Pure, and total: pass a raw StackTraceElement, a normalized frame map, or bare
{:class ...} data. overrides may carry :extra-project.
The source namespace drives classification, not the munged class name — Clojure class names contain no usable namespace separator, so matching on them directly classifies every project frame as third-party noise.
Classify a frame as `:plumbing`, `:jvm`, `:library`, `:test`, `:project`, or `:unknown`.
Pure, and total: pass a raw `StackTraceElement`, a normalized frame map, or bare
`{:class ...}` data. `overrides` may carry `:extra-project`.
The *source namespace* drives classification, not the munged class name — Clojure class
names contain no usable namespace separator, so matching on them directly classifies
every project frame as third-party noise.(frame-fn cname)Best-effort function name for a Clojure frame, or nil for JVM frames.
Best-effort function name for a Clojure frame, or nil for JVM frames.
(frame-summary throwable)(frame-summary throwable overrides)Counts describing a frame sequence after classification:
{:total n :project n :hidden n}. Pure and cheap — handy for telemetry or for asserting
in tests that noise was actually dropped.
Counts describing a frame sequence after classification:
`{:total n :project n :hidden n}`. Pure and cheap — handy for telemetry or for asserting
in tests that noise was actually dropped.Hard ceiling on frames rendered even at :full detail, so a pathological trace cannot
flood a terminal or an agent's context window.
Hard ceiling on frames rendered even at `:full` detail, so a pathological trace cannot flood a terminal or an agent's context window.
(interesting-frame? f)(interesting-frame? f overrides)True when a frame is worth showing by default: project code and test code, plus anything
a caller forces in via overrides.
overrides is an optional map of :keep / :drop predicates applied to the normalized
frame. :keep wins, so an explicit escape hatch always beats the built-in noise table.
True when a frame is worth showing by default: project code and test code, plus anything a caller forces in via `overrides`. `overrides` is an optional map of `:keep` / `:drop` predicates applied to the normalized frame. `:keep` wins, so an explicit escape hatch always beats the built-in noise table.
Ordered [category pattern] table. The first pattern matching a frame's class name
decides its category, so the most specific prefixes must come first.
Ordered `[category pattern]` table. The first pattern matching a frame's class name decides its category, so the most specific prefixes must come first.
(normalize-frame f)Fully normalize one frame: the four raw fields plus the derived :src (source namespace)
and :fn (function name) that classification and rendering both rely on. Idempotent.
Fully normalize one frame: the four raw fields plus the derived `:src` (source namespace) and `:fn` (function name) that classification and rendering both rely on. Idempotent.
(normalize-frames throwable)The full frame pipeline: normalize, drop junk, and collapse compiler-generated
duplicates. Pure — this is the seq clean-stacktrace filters and
format-stacktrace renders.
The full frame pipeline: normalize, drop junk, and collapse compiler-generated duplicates. Pure — this is the seq [[clean-stacktrace]] filters and [[format-stacktrace]] renders.
(normalize-ns-pattern x)Coerce a user-supplied namespace/prefix into the hyphenated form that source-ns
produces, so my_app, my-app, :my-app, and "my_app.core" all match the same frames.
A leading colon is stripped: when prefixes arrive as strings (the env var, or a token from a comma-separated list) they may still carry EDN keyword syntax, which would otherwise never match a real namespace.
Coerce a user-supplied namespace/prefix into the hyphenated form that [[source-ns]] produces, so `my_app`, `my-app`, `:my-app`, and `"my_app.core"` all match the same frames. A leading colon is stripped: when prefixes arrive as *strings* (the env var, or a token from a comma-separated list) they may still carry EDN keyword syntax, which would otherwise never match a real namespace.
(parse-detail x)Coerce a value into a known detail level, or nil when unrecognized.
Coerce a value into a known detail level, or nil when unrecognized.
(parse-project-namespaces s)Split a comma- or whitespace-separated list of namespace prefixes into patterns.
Used for the D_CORE_PROJECT_NS environment variable.
Split a comma- or whitespace-separated list of namespace prefixes into patterns. Used for the `D_CORE_PROJECT_NS` environment variable.
(project-frame? f)(project-frame? f {:keys [extra-project]})True when a frame belongs to your own code.
Consulted in order: project-patterns (defaults + register-project-ns! + the
D_CORE_PROJECT_NS env var), then the :extra-project prefixes supplied for this call.
True when a frame belongs to your own code. Consulted in order: [[project-patterns]] (defaults + [[register-project-ns!]] + the `D_CORE_PROJECT_NS` env var), then the `:extra-project` prefixes supplied for this call.
Registered project namespace prefixes. Seeded from default-project-namespaces plus the
D_CORE_PROJECT_NS environment variable (read once at load time).
Prefer register-project-ns! over mutating this directly.
Registered project namespace prefixes. Seeded from [[default-project-namespaces]] plus the `D_CORE_PROJECT_NS` environment variable (read once at load time). Prefer [[register-project-ns!]] over mutating this directly.
(project-patterns)The namespace prefixes currently treated as project code.
The namespace prefixes currently treated as project code.
(raw-frames x)The raw frame sequence behind a Throwable, a single frame, or a seq of frames.
Total: returns nil rather than throwing on anything unexpected.
The raw frame sequence behind a `Throwable`, a single frame, or a seq of frames. Total: returns nil rather than throwing on anything unexpected.
(register-project-ns! nses)Register one or more namespace prefixes as your code, so their frames survive filtering. Accepts a string, keyword, symbol, or a collection of them.
(register-project-ns! "my-app")
(register-project-ns! ["my-app" "my-app-workers"])
Returns the resulting pattern vector. For a code-free setup, set D_CORE_PROJECT_NS
instead (comma- or space-separated) — it is read when this namespace loads.
Register one or more namespace prefixes as *your* code, so their frames survive filtering.
Accepts a string, keyword, symbol, or a collection of them.
(register-project-ns! "my-app")
(register-project-ns! ["my-app" "my-app-workers"])
Returns the resulting pattern vector. For a code-free setup, set `D_CORE_PROJECT_NS`
instead (comma- or space-separated) — it is read when this namespace loads.(resolve-detail)(resolve-detail x)Resolve a detail level: explicit option > dynamic var > env var > default-detail.
Resolve a detail level: explicit option > dynamic var > env var > [[default-detail]].
(seed-project-namespaces env-value)Given the raw value of D_CORE_PROJECT_NS (or nil), produce the initial pattern vector.
Pure, so the environment can be exercised in tests without touching the real environment.
Given the raw value of `D_CORE_PROJECT_NS` (or nil), produce the initial pattern vector. Pure, so the environment can be exercised in tests without touching the real environment.
(source-ns cname)Best-effort source namespace of a frame's class name, or nil for plain JVM classes.
d_core.libs.stacktrace$format_stacktrace -> d-core.libs.stacktrace
nrepl.middleware.session$eval -> nrepl.middleware.session
java.lang.Thread -> java.lang
clojure.lang.ExceptionInfo -> clojure.lang
Only a trailing class segment is stripped. Genuine sub-namespaces are kept whole —
otherwise nrepl.middleware.session would collapse to nrepl.middleware and
classification would lose the signal it filters on.
Best-effort source namespace of a frame's class name, or nil for plain JVM classes. `d_core.libs.stacktrace$format_stacktrace` -> `d-core.libs.stacktrace` `nrepl.middleware.session$eval` -> `nrepl.middleware.session` `java.lang.Thread` -> `java.lang` `clojure.lang.ExceptionInfo` -> `clojure.lang` Only a trailing *class* segment is stripped. Genuine sub-namespaces are kept whole — otherwise `nrepl.middleware.session` would collapse to `nrepl.middleware` and classification would lose the signal it filters on.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |