Clarion
Correct structured logging for library authors: readable in a dev console,
fully queryable in production, and immune to the one failure mode that
usually destroys it — a caller's report_cb can never cause the original
structured data to be lost, because Clarion never calls it itself.
The problem
# naive: readable now, unrecoverable later
Logger.info("retrying request to #{url}, attempt #{attempt}/#{max}")
Fine in a terminal. Once this event reaches a JSON handler (Datadog, Loki,
anything doing programmatic log analysis), url, attempt, and max are
gone — flattened into one string before :logger ever saw them as data.
No formatter, no handler, no clever config downstream can get them back.
# Clarion: same information, still structured everywhere it goes
require Clarion
Clarion.error(%{event: :http_retry, url: url, attempt: attempt, max: max})
Every consumer of this event gets what it actually wants: a console
handler renders http_retry url=... attempt=2 max=3 via
Clarion.default_report_cb/1; a JSON handler gets real url/attempt/
max fields to filter, alert, and query on.
Why this needs a library at all
Erlang's :logger already supports structured reports and a report_cb
rendering callback — this isn't new machinery. What's missing is a call
site API that makes the correct pattern the only reachable one:
Clarion.info/2, .warning/2, and .error/2 require a map or keyword
list, not a string, so there's no code path where a report gets
interpolated into text before it ever reaches :logger.
Read docs/how_logging_actually_works.md
for the verified, experiment-backed explanation of exactly where structure
gets destroyed in the current Logger/:logger pipeline, and
docs/comparison.md for how Clarion differs from
JSON formatters, text formatters, and log-shipping backends that already
exist on Hex — short version: those all operate on an event after it
exists; Clarion operates at the call site that creates it.
Installation
def deps do
[
{:clarion, "~> 0.1.0"}
]
end
Quick start
defmodule MyHttpClient do
require Clarion
def request(url) do
case do_request(url) do
{:ok, response} ->
Clarion.info(%{event: :http_request_succeeded, url: url, status: response.status})
{:ok, response}
{:error, reason} ->
Clarion.error(%{event: :http_request_failed, url: url, reason: reason})
{:error, reason}
end
end
end
See docs/getting_started.md for custom
report_cbs, passing extra metadata, and testing that your own logging
stays structured.
Documentation
Full docs, including all extras above, are on HexDocs.
Contributing
See CONTRIBUTING.md.
License
MIT — see LICENSE.