Telemetry integration helpers for Threadline.
Threadline emits the following :telemetry events. No event carries row
values, actor identifiers, correlation ids, or free-text reasons — with one
narrow exception: [:threadline, :retention, :purge, :exception]'s
reason/stacktrace metadata, forwarded verbatim from a raised exception
for incident diagnosis (see the Telemetry guide's
"Keep row data out of your handlers" section before logging it anywhere
durable). See the guide for an attach_many example per family, a
telemetry_metrics example, handler-safety and cardinality warnings, and a
recipe for observing Threadline's own repo queries through your host's
[:my_app, :repo, :query] event.
| Event | Measurements | Metadata | When emitted |
|---|---|---|---|
[:threadline, :transaction, :committed] | table_count | — | an AuditTransaction is committed |
[:threadline, :action, :recorded] | status | — | Threadline.record_action/2 completes, whether it succeeds or fails |
[:threadline, :health, :checked] | covered, expected_uncovered, uncovered | — | Threadline.Health.trigger_coverage/1 returns |
[:threadline, :health, :checked, :error] | — | exception | a polled coverage check raises |
[:threadline, :health, :findings_checked] | errors, warnings | — | Threadline.Health.trigger_findings/1 returns |
[:threadline, :operator_surface, :authorize] | result | path, scope_keys | an operator-surface mount or request is authorized, denied, or errors |
[:threadline, :operator_surface, :export_authorize] | count, result | — | an export-specific authorization check raises |
[:threadline, :operator_surface, :actor_ref_mismatch] | count | — | the session actor and the scope-derived actor disagree |
[:threadline, :export, :completed] | duration, row_count | format, truncated | an export (eager CSV/JSON, the async orchestrator job, or the chunked operator-surface download) finishes successfully |
[:threadline, :export, :failed] | duration, row_count | format, error_kind, exception | an export (eager CSV/JSON, the async orchestrator job, or the chunked operator-surface download) fails |
[:threadline, :retention, :purge, :start] | monotonic_time, system_time | dry_run, telemetry_span_context | after purge/1's input checks pass, when the purge work begins |
[:threadline, :retention, :purge, :stop] | batches_run, deleted_changes, deleted_transactions, duration, monotonic_time | dry_run, telemetry_span_context | when the run or preview returns |
[:threadline, :retention, :purge, :exception] | duration, monotonic_time | dry_run, kind, reason, stacktrace, telemetry_span_context | when the database raises mid-run |
[:threadline, :retention, :batch_purged] | deleted_changes, deleted_transactions, duration | — | after a purge_loop step's change delete_all and full orphan drain both return, once per step including the terminating empty one |
[:threadline, :transaction, :committed] is automatically emitted (with
table_count: 0) when Threadline.record_action/2 succeeds. For accurate
per-transaction counts, call Threadline.Telemetry.transaction_committed/2
explicitly after a known DB transaction commit.
Every emission in this library goes through a @doc false helper function
in this module; :telemetry.execute/3 and :telemetry.span/3 are called
only here. Every event this module can emit, along with its measurement
and metadata keys, is held in an internal registry exposed through
__events__/0 (@doc false) — not a public events/0 — for use by this
library's own test suite.
Usage
Attach handlers in your application's start/2 callback:
:telemetry.attach(
"my-app-audit",
[:threadline, :action, :recorded],
&MyApp.Instrumentation.handle_event/4,
nil
)
Summary
Functions
Emits the [:threadline, :operator_surface, :actor_ref_mismatch] event with
%{count: 1} measurements and no metadata, as a pure incidence counter when
the session actor and the scope-derived actor disagree.
Emits the [:threadline, :operator_surface, :export_authorize] event with
%{result: :error, count: 1} measurements and no metadata, for an
export-specific authorization callback that raised.
Emits the [:threadline, :export, :completed] event for one logical export
that finished successfully.
Emits the [:threadline, :export, :failed] event for one logical export
that failed.
Emits the [:threadline, :health, :findings_checked] event with error and
warning counts, measured over the list Threadline.Health.trigger_findings/1
is about to return.
Emits the [:threadline, :health, :checked] event with covered / uncovered /
expected_uncovered measurements.
Emits the [:threadline, :health, :checked, :error] event when a polled
coverage check fails. The dashboard keeps the last-good snapshot and ALWAYS
reschedules the next poll; this event lets adopters alert on transient or
sustained failure.
Emits the [:threadline, :operator_surface, :authorize] event.
Emits [:threadline, :transaction, :committed] with the given table count.
Functions
Emits the [:threadline, :operator_surface, :actor_ref_mismatch] event with
%{count: 1} measurements and no metadata, as a pure incidence counter when
the session actor and the scope-derived actor disagree.
Emits the [:threadline, :operator_surface, :export_authorize] event with
%{result: :error, count: 1} measurements and no metadata, for an
export-specific authorization callback that raised.
Emits the [:threadline, :export, :completed] event for one logical export
that finished successfully.
format is the user-facing export format (:csv, :json, or :ndjson —
the async orchestrator job is always :csv). row_count is the number of
rows returned or streamed. truncated is whether the export hit its row
cap. started_at is a System.monotonic_time/0 value captured by the
caller before the export began; this helper computes duration from it.
Emits the [:threadline, :export, :failed] event for one logical export
that failed.
row_count is the number of rows written or streamed before the failure
(0 for the eager functions, since they fail before returning anything).
error_kind is one of :exception, :client_closed, :storage_error, or
:transaction_failed. exception is the raised exception struct, or
nil when the failure was not a raise — only the struct's module is
forwarded, never its message, which can echo audited database values.
started_at is the same System.monotonic_time/0 value passed to
emit_export_completed/4.
Emits the [:threadline, :health, :findings_checked] event with error and
warning counts, measured over the list Threadline.Health.trigger_findings/1
is about to return.
Emits the [:threadline, :health, :checked] event with covered / uncovered /
expected_uncovered measurements.
The expected_uncovered measurement key is (additive). External
subscribers that destructure only %{covered: c, uncovered: u} continue to
work unchanged.
Emits the [:threadline, :health, :checked, :error] event when a polled
coverage check fails. The dashboard keeps the last-good snapshot and ALWAYS
reschedules the next poll; this event lets adopters alert on transient or
sustained failure.
Takes the raised exception struct itself, not a message. Metadata is
%{exception: module} — the exception's struct module only. The message is
intentionally not forwarded: exception messages can echo database values.
Emits the [:threadline, :operator_surface, :authorize] event.
result is the authorization outcome atom (:granted, :denied, or
:error). path_or_nil is a fixed, caller-supplied path string (the
mount's own compile-time route template, not a live request path), or
nil when the caller has none to offer (a LiveView mount, or an HTTP auth
plug that chooses not to forward one). Callers must never derive this value
from a live conn.request_path/similar — doing so could forward a
dynamic, possibly-identifying route segment (e.g. a tenant id a host
nested the mount under); see the Telemetry guide's cardinality warning.
scope is the host-returned scope map, or nil/anything else when there is
none. Metadata is %{path: binary, scope_keys: [atom]} — scope_keys holds
only the scope map's KEYS, sorted, never its values, so no identity data is
forwarded.
Emits [:threadline, :transaction, :committed] with the given table count.
Call this after a DB transaction that you know produced AuditTransaction
records, when you need accurate table_count measurements.
Example
{:ok, txn} = MyApp.Repo.transaction(fn ->
# ... your writes ...
end)
Threadline.Telemetry.transaction_committed(txn, table_count: 3)