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

Alpaca Activitylog Laravel Package

webmodern/alpaca-activitylog

Activity log integration for the Alpaca admin panel. Automatically records create, update, and delete actions on your Eloquent models, then surfaces a clear, searchable audit trail of who changed what and when inside the Alpaca interface.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require webmodern/alpaca-activitylog
php artisan vendor:publish --tag="alpaca-activitylog-config"
php artisan vendor:publish --tag="alpaca-activitylog-migrations"
php artisan migrate

Look here first, in order:

  1. config/alpaca-activitylog.php — table name, causer resolver/guards, default log name, attribute capture toggles. This file answers 80% of "why isn't this being logged?"
  2. The published migration — column names (subject_type/subject_id, causer_type/causer_id, event, properties, batch_uuid) determine every query you'll write later.
  3. The model trait (e.g. LogsActivity) — the $recordEvents / $logAttributes (or equivalent) properties are where per-model behaviour is configured.

First use case — audit trail on a business-critical model:

use Webmodern\AlpacaActivityLog\Traits\LogsActivity;

class Invoice extends Model
{
    use LogsActivity;

    protected static $recordEvents = ['created', 'updated', 'deleted'];
    protected static $logOnlyDirty = true;
    protected static $logAttributes = ['status', 'total', 'paid_at'];
}

Create an invoice, change status, then read the trail back with the package's Activity model (latest(), forSubject($invoice), forEvent('updated')). If that round-trip works, the wiring is correct and you can move on to manual/custom events.

Implementation Patterns

Automatic logging via trait, manual logging via helper. Use the trait for "what changed on this record" and the fluent/helper API for business events that aren't a model write:

activity()
    ->performedOn($invoice)
    ->causedBy($user)
    ->withProperties(['from' => 'draft', 'to' => 'sent', 'reason' => $reason])
    ->event('invoice.sent')
    ->log('invoice sent to customer');

Group multi-step operations with a batch UUID. Any workflow that touches several records (an approval that updates Invoice, Order, Subscription) should share a batch so the audit UI can render one timeline instead of three unrelated rows:

activity()->inBatch($batchUuid)->event('approval.step')->performedOn($invoice)->log('approved');

Enrich every entry centrally instead of at call sites. Use tapActivity (or an Activity model event listener) to stamp IP, user agent, tenant id, and correlation id once:

protected function tapActivity(Activity $activity, string $eventName): void
{
    $activity->properties = $activity->properties->merge([
        'ip' => request()?->ip(),
        'tenant_id' => tenant('id'),
    ]);
}

Causer resolution is configuration, not code. For web requests the default guard-based resolver is fine. For queued jobs, console commands, and webhooks, set the causer explicitly (->causedBy($user)) or you get null causers that are indistinguishable from anonymous activity. Consider a small middleware or job base class that rehydrates the causer from a payload/subject owner.

Query patterns you'll repeat in dashboards and exports:

Activity::forSubject($invoice)->latest()->paginate(50);
Activity::forCauser($user)->where('created_at', '>=', $since)->get();
Activity::forEvent('invoice.sent')->inLog('billing')->get();

Testing. Use RefreshDatabase, and either assert on the table directly (assertDatabaseHas('activity_log', ['event' => 'updated', 'subject_id' => $invoice->id])) or assertDatabaseCount. Disable logging globally in phpunit.xml / TestCase (config flag or by swapping the trait behaviour) for suites where the extra writes are pure overhead, then re-enable in the handful of tests that actually assert on the trail.

Operational hygiene. Schedule the cleanup/prune command for retention, and keep the properties column indexed-free but do add indexes on (subject_type, subject_id) and created_at if the table grows past a few hundred thousand rows — the default migration may not cover your read pattern.

Gotchas and Tips

  • This is a 0.1.x package — treat the API as unstable. The v0.1.1 release notes are only a changelog link with no description, so there is no stated change to reason about. Before upgrading, diff the source between tags (git diff v0.1.0..v0.1.1 -- src/ config/ database/) rather than trusting a patch bump to be harmless: in a pre-1.0 package, config keys, column names, and trait property names can change in a patch release. Pin with "webmodern/alpaca-activitylog": "0.1.*" and upgrade deliberately.

  • Re-publish config/migrations on upgrade — carefully. Published migrations are copied into your app and are not updated by composer update. A new column added upstream silently won't exist in your DB. Keep the published migration files under version control and compare them against the vendor copy after each upgrade; write your own additive migration if they differ.

  • The table name is configurable, so never hardcode activity_log in raw queries. Read it from config (config('alpaca-activitylog.table_name')) or query through the model.

  • Dirty-attribute logging is only as good as your $logAttributes. With logOnlyDirty enabled and an empty attribute list, you may log nothing at all. Log the whole model only for small tables — properties payloads for large models bloat the table and slow every read.

  • Never log secrets. Excluded-attributes config is the safety net; make sure password fields, tokens, and API keys on related models are excluded, or you've built a credential archive.

  • Beware observer recursion. If you log activity from a model event of the Activity model itself (or from a global saved listener), you can create infinite loops or duplicate entries. Scope model listeners narrowly.

  • Transactions roll back the audit trail too. If you log inside a DB transaction that later fails, the activity row disappears. For compliance-grade logging of attempts, write the entry after commit (DB::afterCommit) or to a separate connection.

  • Soft deletes and morph maps. With morph-map enforcement (Relation::enforceMorphMap), historical rows with FQCNs stop resolving — a classic issue when you introduce a morph map months after the activity table has data. Decide on the morph map before you start logging. Also confirm the subject still resolves after SoftDeletes (trashed subjects are often excluded from default morph queries).

  • Console and queue context loss. request() is null, there's no auth guard, and created_at uses the app timezone — all three make console-generated entries look odd in a UI built for web entries. Stamp a source property ("cli", "queue", "api") in tapActivity so you can filter them.

  • Extension points to know: custom Activity model binding in config (add scopes/accessors there), the causer resolver (swap for multi-guard/multi-tenant apps), and tapActivity (central enrichment). Prefer these over editing vendor code — it's the only thing that will survive an upgrade in a 0.1.x package.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin