Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Elasticsearch Audit Bundle Laravel Package

borsche/elasticsearch-audit-bundle

A Symfony bundle that records who changed what into Elasticsearch: Doctrine entities audited automatically, custom domain actions logged on demand, small changes coalesced into one record, async writes via Messenger, and a filterable read API for searchable audit history.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

Whether we can ship an audit trail we're willing to stand behind. v1.3.1 is almost entirely about the integrity of the record: transaction boundaries, what actually got written versus what we think got written, and what leaves the process. That maps directly to compliance, customer trust, and incident-response decisions rather than to feature work.

  • Compliance / audit-readiness sign-off. The release closes a class of silent-partial-audit failures: an operation that commits our transaction and opens another, COMMIT/ROLLBACK sent as raw SQL, a rollback that failed and left a session open for the next request. Before this, AuditTransaction::run() could report a commit that hadn't happened. If we're telling auditors or customers "every change to these entities is recorded atomically with the change itself," this release is the difference between that claim being true and being approximately true. That is a go/no-go input, not a nice-to-have.
  • Security and privacy review of the audit pipeline. The headline fix: an object in a record reached the queue whole, private properties included, and with the outbox that queue is a table in our own database. Redaction was applied to what json_encode shows, and Messenger's PhpSerializer then stored everything else. This was live since 1.0.0 and applied even with no redact.fields configured. Any decision to route audit data through Messenger, or to store it in a shared database, should be re-reviewed against this release. If we have production data written by ≤1.3.0 with object-valued fields, a data-hygiene sweep is warranted.
  • Build vs. buy for the audit subsystem. Every fix in this release is a bug we would have written ourselves and probably not found: DBAL savepoint levels vs. driver-level commits, readonly properties and enums breaking Elasticsearch encoding, streams read to end before the value is captured, jsonSerialize() being called twice so what was checked wasn't what was written. That is a strong argument for consuming and pinning this rather than growing an in-house equivalent — and for budgeting upgrade work as a recurring line item, not a one-off integration.
  • A CI gate we can adopt cheaply. audit:check now validates every audit declaration on the audited connection before anything is flushed: always-recorded associations, associations without a representer, unmapped fields, version/generated/binary columns, untrackable collections, BLOB under #[AuditField], bound streams. It fails the check for anything it can't honour. This turns "we'll discover it in production on the first write of that entity" into a pre-merge failure. Low effort, disproportionate risk reduction — worth scheduling regardless of whether we upgrade anything else.
  • Deciding what "audited" means per entity, and what happens when we can't honour it. The failure policy (log vs throw) is now a product-visible behaviour, and the release makes the semantics precise: under log the rest of the record is written and the offending field is named; inside an AuditTransaction the commit is refused; under throw the flush fails. That's a real decision with user-facing consequences — an incomplete-but-committed row versus a failed business operation — and it should be documented as policy, not left at the default.
  • Roadmap scoping for search and reporting. immediately: true is now documented honestly: the record is written before the request ends and is findable after the index's next refresh. If any roadmap item assumes real-time audit visibility (live dashboards, "show me the last change" in the UI), it needs re-scoping or a different read path.
  • Upgrade sequencing. This ships breaking changes (§Changed): records leave the writer as plain values, so RecordCreatedEvent / CollectedRecord / transport consumers now see stdClass, arrays and strings where they previously saw application objects and DateTimeInterface. Any code doing instanceof MyDto on a change value breaks. Dates and enums are normalised to one form everywhere in changes. This is a scheduled migration, not a patch bump.

When to Consider This Package

Adopt when:

  • You have Doctrine ORM entities (including Laravel applications running Doctrine alongside or instead of Eloquent) where you need per-change history, not just a last-modified stamp.
  • Audit integrity must be transactional with the business write — you need the change and its history to commit or roll back together, and you can point AuditTransaction at the audited connection so driver-level commits and rollbacks are actually observed.
  • You already run, or plan to run, Elasticsearch for search over the history, and can accept refresh-latency visibility rather than read-your-writes.
  • You need an outbox rather than a fire-and-forget write, and you accept that the outbox is a table in your own database (which is precisely why the redaction fixes matter).
  • You can run audit:check in CI and treat a failure as a blocker — the package is now designed to be used that way.
  • You can absorb the plain-values migration in listeners, events and transports, and will pin a version and read UPGRADE.md on every bump.

Look elsewhere, or scope expectations down, when:

  • You're Eloquent-only. The package is built on Doctrine/DBAL semantics — unit of work, DBAL transaction levels, #[AuditField], entity metadata. Retrofitting it onto an Eloquent codebase is a bigger project than the audit trail is worth.
  • You need audit coverage of DDL and implicit commits. Explicitly documented as not seen: MySQL's own commit on DDL, and anything that bypasses the observed connection. If your compliance story includes schema changes, this package alone won't carry it.
  • Your writes go through stored procedures, raw SQL outside the observed connection, or heavily shared connection pools. audit:check warns when the outbox's connection isn't watched, and without the observer only the transaction level is checked — silent gaps remain by design.
  • You need audit-native retention, legal hold, per-tenant segregation, or an investigator UI. This is a recorder, not a records-management product. Budget for those separately.
  • You need strict real-time visibility of audit records in application reads.
  • You can't take a breaking change on record payload types in the near term — pin to your current version and revisit; do not upgrade opportunistically.
  • The maturity signal worries you. The defects fixed here date from 1.0.0, 1.1.0 and 1.3.0, and several involve transaction semantics that were wrong for multiple minor versions. That's honest, well-tested maintenance — but it argues for pinning, a staged rollout, and verifying your own invariants in staging rather than trusting the changelog narrative.

How to Pitch It (Stakeholders)

Executives (one paragraph): A new release of the audit-trail library we use for change history closes several defects that could have made our audit record incomplete or, in one case, exposed private data. Specifically: audit history could be reported as committed when it wasn't; a rollback that failed could leave a database session open for the next request, with subsequent work silently inside it; and objects placed in audit records were being written into a queue table in our own database with all their properties, including private ones, even with redaction unconfigured. All of these are fixed, and the package now ships a single command we can run in CI that validates every audit declaration before code reaches production. The cost is a contained upgrade: our own listeners and event consumers see plain values instead of application objects, which is a day or two of engineering plus test coverage. Recommendation: upgrade on a planned schedule, treat the CI check as a merge gate, and — if we have production audit data written before this version — run a one-off review of object-valued fields for exposed data.

Engineering (the actionable summary):

  • Breaking: records now leave the writer as scalars, arrays and bundle-built stdClass — never application objects. RecordCreatedEvent, CollectedRecord, the failure path and every transport are affected. $record->changes['x'] instanceof MyDto and DateTimeInterface on a Change side will break; consult UPGRADE.md. Dates and enums are normalised to one form per place (UTC Y-m-d H:i:s with fraction; backed enum value / pure enum name). changes isn't indexed, so no mapping depends on it; attribute values keep their mapping-indexed form.
  • Add audit:check to CI. It asks every flush-time question of every audited class at once, before any entity is involved, and fails on anything it can't honour. It also reports when the outbox's connection isn't watched — fix that, or accept level-only checking.
  • Verify transaction observation. Driver-level commits and rollbacks are counted only on the audited connection. run() now refuses operations that leave a level open, that close its transaction themselves, or that send BEGIN/START TRANSACTION/COMMIT/END/ROLLBACK/ABORT as SQL. Test your actual write paths, including any listener that runs when the frame closes.
  • Test the failure policy deliberately. Streams bound to audited columns and BLOB columns under #[AuditField] are now refused per the policy — under log the rest of the record is written with the field named and omitted, inside AuditTransaction the commit is refused, under throw the flush fails. Decide which you want per entity; the default (a log line the first time) is quiet.
  • Check redaction config. redact.max_depth and redact.max_nodes were silently inert without redact.fields; they now apply either way. If you set them and assumed they were bounding something, they weren't — re-measure payload sizes and queue table growth.
  • Known limits to document, not fix: DDL-triggered implicit commits (MySQL) are not seen; connections that aren't watched give level-only checking. Put both in our runbook.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log