Packages
Reusable structured audit logging, DB change tracking, and crash reporting for Elixir/Phoenix apps
Current section
Files
Jump to
Current section
Files
audit_trail
CHANGELOG.md
CHANGELOG.md
# Changelog
All notable changes to this project are documented in this file.
## [0.1.2]
### Added
- **Transaction correlation id.** Every event dispatched while a `Repo.transaction`/`Ecto.Multi` is open (schema-tracked Repo calls, and any manual `emit`/`monitor` made inside the transaction function) is now stamped with a shared `transaction_id`, generated once when the outermost transaction opens. A transaction touching several tables previously shipped as N fully uncorrelated log lines with no way to reconstruct that they were one unit; `AuditTrail.get_logs(%{transaction_id: "..."})` now pulls the whole set back together. Added as a filter across the Loki reader, the Postgres/TimescaleDB adapter, and the Test adapter.
- **`:telemetry` events** for the health of the logging pipeline itself (not your audit events — those go through your storage adapter): `[:audit_trail, :buffer, :dropped]`, `[:audit_trail, :buffer, :flushed]`, `[:audit_trail, :shipper, :succeeded]`, `[:audit_trail, :shipper, :failed]`, `[:audit_trail, :dead_letter, :dropped]`, `[:audit_trail, :dead_letter, :redrive, :succeeded]`, `[:audit_trail, :dead_letter, :redrive, :failed]`, and `[:audit_trail, :crash, :reported]`. See the new "Telemetry" section in the README. `:telemetry` is now an explicit dependency (previously only pulled in transitively via Ecto).
- **Persistent dead-letter queue (opt-in).** New `dead_letter_persistent: true` config backs `AuditTrail.DeadLetter` with `:dets` instead of plain ETS, so entries queued while Loki is down survive a node restart/deploy instead of being lost. Off by default — existing behavior is unchanged unless you opt in. Pairs with the new `dead_letter_path:` config.
- `AuditTrail.clear_actor/0` and `AuditTrail.clear_tenant/0` — reset the process-dictionary actor/tenant back to their defaults. Previously only reachable via the internal `AuditTrail.ActorStore.clear/0`/`AuditTrail.TenantStore.clear/0`, with no public, documented way to do this. Useful for pooled/long-lived processes (a GenServer working through a job queue, shared test processes) that shouldn't leak one actor's/tenant's context into the next unit of work.
- `AuditTrail.DeadLetter` gained proper documentation for its existing operational functions (`push/1`, `dump/0`, `count/0`, `clear/0`, `redrive/0`) — previously undocumented entirely, with `redrive/0`'s explanation only ever existing as a source comment that never reached hexdocs.
### Fixed
- **`AuditTrail.LiveViewAudit` now truncates oversized param values** the same way `AuditTrail.ControllerAudit` already did. A LiveView event pushing a large list or a base64 blob through `handle_event` params (e.g. from a JS hook) previously got dumped into the audit payload in full; it's now truncated the same as a controller file upload.
- **`AuditTrail.DeadLetter` no longer reports a failed redrive as a success.** `AuditTrail.Shipper.push/1` returned whatever `AuditTrail.DeadLetter.push/1` returned on retry exhaustion — a `GenServer.cast`, which always returns `:ok` regardless of whether the entries are ever redelivered. `redrive_batch/0` treated that `:ok` as a real success and deleted the original queued entries while a failed redrive silently re-queued a duplicate copy behind the scenes. `Shipper.push/2` now returns a real `:ok | {:error, reason}`, and redrive calls it with `dead_letter?: false` so a failed redrive correctly leaves entries in place instead of duplicating them.
- **Closed a redaction gap in `AuditTrail.Sanitizer` and `AuditTrail.Diff`.** Any struct not covered by a specific clause (an app-defined struct without an `:id` field, ending up in `original_record:`/`params:`/a tracked embed) was collapsed via `inspect/1`, which prints every field verbatim — including ones matching `sensitive_fields/0` — bypassing redaction entirely. Such structs are now expanded to a plain map and redacted the same way any other map is.
- **LogQL injection in `AuditTrail.Reader`.** Filter values (`type`, `status`, `actor_id`, `tenant`, `resource`, `resource_id`, `operation`, `search`) were interpolated directly into the LogQL query string with no escaping — a value containing `"` could break out of a label/line matcher and alter the query. All filter values are now escaped before interpolation.
### Documentation
- Full pass over every public function's `@doc` in `lib/audit_trail.ex` (`monitor/4`, `emit/2`, `log_repo/5`, `log_external_api/3`, `set_actor/1`, `set_actor/2`, `get_actor/0`, `get_tenant/0`, `get_logs/1`) — most previously rendered as a bare `See AuditTrail.Logger.emit/2.` stub on hexdocs, or nothing at all (`get_logs/1`, `get_tenant/0`). `log_repo/5` in particular had no documentation or README mention anywhere despite being a real, distinct public API.
- `AuditTrail.Task` — `await/2` had no documentation at all; `async/1`/`start/1`/`start_link/1` were only covered collectively by the moduledoc (and `start_link/1` wasn't mentioned anywhere). Each now has its own `@doc`.
- `child_spec/1` marked `@doc false` — it was showing up as a callable public function on hexdocs despite the README explicitly warning it should never be called directly.
- `AuditTrail.CrashReporter`'s four `:logger` handler callbacks and `AuditTrail.Buffer`'s `child_spec/1`/`start_link/1` were leaking onto their hexdocs pages as undocumented public functions (neither module used `@impl`/`@doc false` the way the rest of the codebase does). Marked `@doc false` with an explanation of why.
- README: added the previously entirely-undocumented `AuditTrail.Adapters.SMTP` mailer option (only the Swoosh path was documented before), a full `AuditTrail.Task` usage example, and the full `crash_routing` match-type table (`{:exception, ...}` and `{:source, ...}` existed in code but were never documented — only `{:module, ...}` and `:default` were shown). Corrected the crash-dedup description, which undersold the actual behavior (a periodic digest email for recurring crashes, not just "emailed once").
- README: new "Telemetry" section, "Correlating multi-table changes with `transaction_id`" subsection, updated "Buffer tuning" config block, and updated Limitations table entry for dead-letter durability now being fixable via config rather than a hard limitation.
## [0.1.1]
- Bump to v0.1.1, loosen `decimal` constraint to `~> 2.4 or ~> 3.0`.
- Sanitizer module updates; removed a missing `flush` function reference; mix file updates.
## [0.1.0]
- Initial release.