bolechen/nova-activitylog
A Laravel Nova integration for spatie/laravel-activitylog that lets you browse, filter, and inspect activity logs and model changes directly from your Nova dashboard.
Architecture fit
activity_log table by spatie/laravel-activitylog; it does not write, enrich, or govern logging. Every substantive architectural decision (what is logged, causer resolution, retention, schema) lives in the Spatie layer and in your application code, not in this package.Integration feasibility
composer require, publish, register in NovaServiceProvider::tools(), gate with canSee(). A developer can have it rendering in under an hour.logOnlyDirty() + dontSubmitEmptyLogs(), and prove rows exist in the table via tinker before installing the viewer. The documented failure mode is an empty panel caused by missing instrumentation, not a broken package.laravel/nova and spatie/laravel-activitylog exactly, and budget for vendoring.Technical risk
canSee(), which is all-or-nothing. Whether row-level scoping exists — tenant isolation, subject-type restriction, causer filtering enforced server-side — is unknown and must be verified by reading the component's query, not the README. A low-privilege Nova user seeing cross-tenant activity is a security incident, not a bug report.causer/subject morph relations are eager-loaded, and whether filters hit the query or the client. Client-side filtering over a multi-million-row append-only table would be a hard blocker.logAll() mistake on a User model into password hashes and API tokens displayed in an admin UI.Key questions to close before committing
vendor/bolechen/nova-activitylog/src/ and resources/js/ — is pagination server-side? Are relations eager-loaded? Are filters applied in the query?canSee()?composer.json?forceDelete()d subjects?Stack fit
spatie/laravel-activitylog. No database engine constraints beyond what Spatie supports; no queue or cache dependency.status: pending → paid by a named causer at a timestamp, and closes a ticket without a DB console. Scope the pilot to that flow, not "browse all activity."Migration path
Enforce the order of operations — it is not invertible:
logOnlyDirty(), dontSubmitEmptyLogs(), logOnly([...]), and a domain-scoped useLogName(). Verify a row lands in activity_log.canSee() gate.Adjacent migrations that must land at the same time or not at all:
Relation::enforceMorphMap() after logging begins creates a historical FQCN/alias split that silently mismatches the subject filter and requires a data migration.log_name as domain axis, not model class. billing, auth, admin, integrations. Class-based names duplicate subject_type and waste the primary filter.Activity::withoutLogging(...) so test-data refreshes don't dump 200k rows into an audit table.activitylog:clean scheduled alongside the initial migration rather than after the table grows.Compatibility
laravel/nova and spatie/laravel-activitylog exactly. Treat any bump to either as a change requiring regression of this panel.default_causer config or a causer resolver) before multiple guards or queue workers enter the picture; retrofitting attribution means historical rows are permanently unattributed.Sequencing
log_name convention documented and applied, indexes created, pruning scheduled, correlation tap added for request_id/IP/impersonator.Activity::forSubject($invoice)->latest()->limit(50)) — often more valuable day-to-day than the global browse screen — plus usage measurement.Maintenance
log_name taxonomy, snake_case event naming, from/to property conventions, and default_causer handling. That convention is what makes the filters usable six months in — the package cannot enforce it.Support
DB::table('activity_log')->count()) in the runbook.forceDelete()d subjects render blank. Reviewers will report these as bugs otherwise.Scaling
activity_log is append-only; a busy application reaches millions of rows within a year. Verify server-side pagination in the Vue component before rollout and load-test against realistic volume.(subject_type, subject_id), (causer_type, causer_id, created_at), (log_name, created_at), (created_at).activitylog:clean and delete_records_older_than_days. Retention is a policy decision requiring security/legal sign-off; the package cannot make it and will happily grow unbounded.Failure modes
DB::table()->update(), Model::query()->update(), and raw upserts bypass model events entirely — invisible holes exactly where imports, jobs, and reports live. Any custom Nova action using raw queries has the same problem and must log explicitly.canSee() is the only gate, the panel is all-or-nothing and leaks cross-tenant or privileged activity to any Nova user with the menu item.logAll() on a User writes password hashes and tokens into an append-only table rendered to a wide internal audience. logOnly/logExcept is mandatory, not advisory.Ramp-up
log_name taxonomy, the event naming convention, the property snapshot rules, the retention window, and the authorization model across support, security, and legal. That work is what determines whether the panel is useful in six months, and it is independent of which viewer we use — another reason to time-box the package as a spike and fund the instrumentation program on its own merits.How can I help you explore Laravel packages today?