Comparison: where Clarion sits relative to existing tools

Copy Markdown View Source

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 formattersLoggerJSON.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/2 with a well-formed report and never interpolated data into strings, most of the value of Clarion would already exist for free via :logger itself (as section 3 of how_logging_actually_works.md shows — structure survives with no report_cb at 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 giving Clarion.info/2 etc. 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/1 and 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 :logger itself, 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 event Clarion.report/3 builds keeps the raw map in :msg and 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 custom report_cb is 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.