The short version: everything below operates on the output side —
taking whatever :logger events already exist and rendering/shipping them
somewhere. Clarion operates on the call-site side — helping the code
that produces a log event build a correct one in the first place. These are
different, non-competing layers, and a real application typically wants one
tool from each category, not one instead of the other.
The layers
library/app code
|
v
Clarion.report/3 <-- call-site correctness (what Clarion does)
|
v
:logger core dispatch <-- OTP, unopinionated, see how_logging_actually_works.md
|
v
handler + formatter <-- output shape (JSON, flat text, ship to Loki/Betterstack)If the map never gets into :logger intact (because the call site
interpolated it into a string), no formatter downstream — however
well-written — has anything structured left to format. That's the gap none
of the packages below address, because none of them run at the call site.
logger_json (formerly discussed as logger_formatter_json-adjacent)
LoggerJSON is a set of :logger
formatters — LoggerJSON.Formatters.Basic, plus
Datadog/Google-Cloud/Elastic-shaped variants — configured via
config :logger, :default_handler, formatter: LoggerJSON.Formatters.Basic.new(...).
It takes whatever event :logger already has and renders it as JSON
matching a specific platform's schema.
It does not touch the call site: it has no macro, no helper, no opinion
about what you pass to Logger.info/2. If you hand it a {:string, ...}
event, it JSON-encodes a string. If you hand it a well-formed
{:report, map}, it JSON-encodes the map's fields. LoggerJSON's output
quality is entirely downstream of whether the caller gave :logger
something structured — which is exactly the problem Clarion is trying to
solve one layer up.
logger_formatter_json (Erlang, no Elixir deps)
Same category, Erlang-side: a :logger formatter module with no
dependencies beyond a JSON encoder (thoas), usable from any BEAM language.
Same relationship to Clarion as LoggerJSON: it's a rendering target for
whatever event already exists, agnostic to how that event was constructed.
flatlog
flatlog is a :logger formatter that
turns report maps into single-line key=value-style text (logfmt-ish),
intended to be both human-scannable in a terminal and easy for tools like
grep/log shippers to parse. It's a genuinely nice middle ground between
"pretty but unparseable" and "JSON but unreadable at a glance" — and it's
the same idea Clarion's default_report_cb/1 uses for the dev-console
rendering it produces.
The distinction holds anyway: flatlog decides how to render a report that
already exists as a report. It has no opinion on, and no mechanism for
influencing, what a library does before calling Logger.info/2. A library
that interpolates a map into a string before logging gets exactly the same
flat, useless single line out of flatlog as it would out of any other
formatter — because by then there's only a string to flatten.
Backend/shipping packages (Loki, Betterstack, etc.)
logger_loki_backend, loki_logger, betterstack_logger_backend, and
similar packages are transport layers: they take rendered/structured
:logger output and ship it over HTTP to a specific hosted log platform.
They're one step further downstream than the JSON formatters above —
concerned with where logs go, not what shape they arrive in — and have
even less overlap with Clarion's problem space. If the event that reaches
them was already flattened to a string at the call site, they'll faithfully
ship that string; they have no way to know or recover that it used to be
richer.
Is the distinction thinner than it looks?
Being honest about the edge cases:
- If every library and every application always called
Logger.info/2with a well-formed report and never interpolated data into strings, most of the value of Clarion would already exist for free via:loggeritself (as section 3 ofhow_logging_actually_works.mdshows — structure survives with noreport_cbat all, just less readable console output). Clarion's actual leverage is behavioral: making the wrong thing (logging a string) harder to reach for than the right thing (logging a report), by givingClarion.info/2etc. a signature that requires a map/keyword and never accepts a bare string. It's a guardrail on an API that already technically supports the right pattern, not new plumbing under:logger. default_report_cb/1and flatlog solve visually similar problems (nice single-line dev output from a map). Clarion doesn't try to out-format flatlog — a caller who already likes flatlog's exact output style is free to configure it as their console formatter; Clarion's own default is just a zero-config fallback so a report_cb always exists, not a competing formatting product.- None of the reviewed packages, and no part of
:loggeritself, guard against a library's own report_cb or a third-party primary filter eagerly rendering a report and discarding it for other consumers (section 5 of the research doc). Clarion can't stop a filter installed by some unrelated dependency either — nothing sitting at the call site can, since that damage happens later in the pipeline. What Clarion actually guarantees is scoped to itself: every eventClarion.report/3builds keeps the raw map in:msgand any renderer strictly in:meta.report_cb, proven by a test that attaches an independent structured consumer and confirms it receives the untouched map even when a customreport_cbis also configured.
So: real, not manufactured, distinction — but a narrow one. Clarion is a call-site guardrail plus a sane default renderer, not a replacement for any of the tools above. A production setup reasonably uses Clarion in a library's source and LoggerJSON or flatlog as the application's configured formatter and a Loki/Betterstack backend to ship the result — three different layers, no conflict.