Activity Logging Guide
Last Updated: 2026-01-13 Status: Enhanced with Dashboard Widget, Quick Filters & Comparison View Plan Reference: 011-activity-logging.md, 076-activity-logging-improvement.md, 097-playwright-activity-logging-tests.md
Overview
The Activity Logging system provides a comprehensive audit trail of all significant actions performed in the portal. It tracks user actions, data changes, and system events to support auditing, debugging, and compliance requirements.
Table of Contents
Accessing Activity Logs
Navigation
| Access Point | Location | URL | Role |
|---|---|---|---|
| Activity Log | Admin sidebar | /admin/activity | Admin |
| Entity Activity | Detail pages | Tab on entity | Admin |
| User Activity | User profile | /admin/users/{id}/activity | Admin |
| My Activity | Profile | /profile/activity | Both |
Permissions
| Action | Admin | Client User |
|---|---|---|
| View all activity | ✅ | ❌ |
| View own activity | ✅ | ✅ |
| View client activity | ✅ | Own client only |
| Export activity logs | ✅ | ❌ |
| Delete activity logs | ❌ | ❌ |
What Gets Logged
User Actions
| Action | Event Type | Details Logged |
|---|---|---|
| Login | user.login | IP, user agent, timestamp |
| Logout | user.logout | Session duration |
| Password change | user.password_changed | User ID |
| Profile update | user.updated | Changed fields |
| Failed login | auth.failed | Email attempted, IP |
Entity Operations
| Entity | Events | Details |
|---|---|---|
| Client | created, updated, deleted | All changed fields |
| Project | created, updated, status_changed, deleted | Fields + old/new values |
| Invoice | created, sent, paid, deleted | Status changes, amounts |
| File | uploaded, downloaded, deleted | Filename, size, user |
System Events
| Event | Type | Details |
|---|---|---|
| Email sent | mail.sent | Recipient, subject |
| Notification | notification.sent | Type, recipient |
| Export generated | export.created | Type, format, user |
| Settings changed | settings.updated | Key, old/new value |
How to Use
Viewing Activity Log
- Navigate to Admin → Activity Log
- View chronological list of activities
- Each entry shows:
- Timestamp
- User who performed action
- Action type
- Entity affected
- Description
Filtering Activities
| Filter | Options |
|---|---|
| Date Range | Today, This Week, Custom |
| User | Select specific user |
| Action Type | Login, Create, Update, Delete |
| Entity Type | Client, Project, Invoice, File |
| Search | Free-text search |
Viewing Entity Activity
- Navigate to entity detail (e.g., Client)
- Click "Activity" tab
- View activity related to that entity only
Exporting Activity
- Apply desired filters
- Click "Export"
- Select format (CSV, PDF)
- Download file
Log Entry Details
Standard Log Entry
{
"id": 12345,
"user_id": 1,
"event": "client.updated",
"subject_type": "App\\Models\\Client",
"subject_id": 42,
"properties": {
"old": {
"company_name": "Old Name",
"email": "old@example.com"
},
"new": {
"company_name": "New Name",
"email": "new@example.com"
}
},
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"created_at": "2026-01-09T14:30:00Z"
}
Viewing Entry Details
- Click on any log entry
- Expand to see full details:
- Changed fields (before/after)
- User information
- IP address and device
- Timestamp
Technical Architecture
Model
Location: app/Models/ActivityLog.php
class ActivityLog extends Model
{
protected $fillable = [
'user_id',
'event',
'subject_type',
'subject_id',
'causer_type',
'causer_id',
'properties',
'ip_address',
'user_agent',
];
protected $casts = [
'properties' => 'array',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function subject(): MorphTo
{
return $this->morphTo();
}
public function causer(): MorphTo
{
return $this->morphTo();
}
}
Logging Trait
Location: app/Traits/LogsActivity.php
trait LogsActivity
{
protected static function bootLogsActivity(): void
{
static::created(function ($model) {
activity()
->performedOn($model)
->causedBy(auth()->user())
->withProperties(['new' => $model->toArray()])
->log('created');
});
static::updated(function ($model) {
activity()
->performedOn($model)
->causedBy(auth()->user())
->withProperties([
'old' => $model->getOriginal(),
'new' => $model->getChanges(),
])
->log('updated');
});
static::deleted(function ($model) {
activity()
->performedOn($model)
->causedBy(auth()->user())
->withProperties(['old' => $model->toArray()])
->log('deleted');
});
}
}
Activity Logger Service
Location: app/Services/ActivityLogger.php
class ActivityLogger
{
public function log(string $event, ?Model $subject = null, array $properties = []): ActivityLog
{
return ActivityLog::create([
'user_id' => auth()->id(),
'event' => $event,
'subject_type' => $subject ? get_class($subject) : null,
'subject_id' => $subject?->id,
'properties' => $properties,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
]);
}
}
Usage in Models
class Client extends Model
{
use LogsActivity;
// Activity logging is automatic for create, update, delete
}
Manual Logging
// Log custom activity
activity()
->performedOn($invoice)
->causedBy(auth()->user())
->withProperties(['status' => 'paid', 'amount' => $invoice->total])
->log('invoice.paid');
// Or via service
app(ActivityLogger::class)->log('invoice.paid', $invoice, [
'status' => 'paid',
'amount' => $invoice->total,
]);
Controller
Location: app/Http/Controllers/Admin/ActivityLogController.php
| Method | Route | Description |
|---|---|---|
index() | GET /admin/activity | List all activity |
show() | GET /admin/activity/{log} | View details |
export() | GET /admin/activity/export | Export logs |
Routes
Route::prefix('admin/activity')->group(function () {
Route::get('/', [ActivityLogController::class, 'index'])->name('admin.activity.index');
Route::get('/export', [ActivityLogController::class, 'export'])->name('admin.activity.export');
Route::get('/{log}', [ActivityLogController::class, 'show'])->name('admin.activity.show');
});
Database Schema
Table: activity_logs
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
user_id | bigint | User who acted |
event | string | Event type |
subject_type | string | Model class |
subject_id | bigint | Model ID |
causer_type | string | Who caused it |
causer_id | bigint | Causer ID |
properties | json | Additional data |
ip_address | string | Client IP |
user_agent | string | Browser info |
created_at | timestamp | When logged |
Indexes
// Migration
$table->index(['subject_type', 'subject_id']);
$table->index(['user_id', 'created_at']);
$table->index('event');
$table->index('created_at');
Retention Policy
Default Retention
| Log Type | Retention Period |
|---|---|
| Security events | 1 year |
| User actions | 90 days |
| System events | 30 days |
Cleanup Command
php artisan activity:cleanup --days=90
Scheduled Cleanup
// app/Console/Kernel.php
$schedule->command('activity:cleanup --days=90')->daily();
Advanced Features
Dashboard Widget (New)
The Activity Summary widget displays key activity metrics on the admin dashboard.
Component: resources/views/components/activity-summary-widget.blade.php
Features:
- Total activity count for selected period
- Action breakdown by type (created, updated, deleted, etc.)
- Top active users with activity counts
- Recent activity timeline
- Period selector (day, week, month)
Usage in Dashboard:
<x-activity-summary-widget
:stats="$activityStats"
:period="$activityPeriod"
/>
Clickable Elements:
- Action badges link to filtered activity list
- User names link to user-filtered activity list
- Recent activities link to comparison view for updates
One-Click Filtering (New)
The activity index page now supports instant filtering by clicking on any attribute:
Clickable Elements:
| Element | Filter Applied | Example URL |
|---|---|---|
| User name | ?user_id={id} | /admin/activity?user_id=1 |
| Action badge | ?action={action} | /admin/activity?action=created |
| IP address | ?ip_address={ip} | /admin/activity?ip_address=192.168.1.1 |
| Subject type | ?subject_type={type} | /admin/activity?subject_type=Client |
Visual Indicators:
- Active filters are highlighted with indigo color
- Action badges show ring indicator when active
- Clear button appears when any filter is active
Comparison View (New)
The comparison view displays before/after changes for update activities.
Access: /admin/activity/comparison
Features:
- Shows only "updated" activities with change data
- Expandable cards for each activity
- Side-by-side before/after comparison table
- Field-by-field change visualization
- Red highlighting for old values
- Green highlighting for new values
- Support for arrays, booleans, and null values
Filtering:
| Filter | Description |
|---|---|
| User | Filter by who made the change |
| Resource Type | Filter by entity type (Client, Project, etc.) |
| Date Range | Filter by change date |
Controller Method: ActivityController@comparison
Timeline View
The timeline view provides a visual, chronological display of activity logs grouped by date.
Access: /admin/activity/timeline
Features:
- Activities grouped by date
- Visual timeline with color-coded action types
- Date range filtering
- User and action filtering
- Quick navigation between days
Advanced Filtering
The ActivityFilterService provides comprehensive filtering:
Location: app/Services/ActivityFilterService.php
class ActivityFilterService
{
public function filter(Request $request): LengthAwarePaginator
{
// Supports: user_id, action, subject_type, date_from, date_to, ip_address, search
}
public function getAvailableFilters(): array
{
// Returns available actions and subject types
}
public function getStatistics(string $period = 'week'): array
{
// Returns activity counts by action and top users
}
}
Available Filters:
| Filter | Description | Example |
|---|---|---|
user_id | Filter by specific user | ?user_id=1 |
action | Filter by action type | ?action=created |
subject_type | Filter by entity type | ?subject_type=Client |
date_from | Start date | ?date_from=2026-01-01 |
date_to | End date | ?date_to=2026-01-31 |
ip_address | Filter by IP | ?ip_address=192.168 |
search | Search description/properties | ?search=invoice |
Export Functionality
Export activity logs in CSV or JSON format.
Location: app/Services/ActivityExportService.php
class ActivityExportService
{
public function toCsv(Collection $activities): string;
public function toJson(Collection $activities): string;
public function toArray(Collection $activities): array;
}
Routes:
| Route | Description |
|---|---|
GET /admin/activity/export?format=csv | Export as CSV |
GET /admin/activity/export?format=json | Export as JSON |
GET /admin/activity/export?limit=500 | Limit export to 500 records |
Filters can be combined with export:
/admin/activity/export?format=csv&action=created&date_from=2026-01-01
Log Archival
Archive old logs to storage before deletion:
Command: php artisan activity:archive
# Archive logs older than 90 days
php artisan activity:archive --days=90
# Preview without making changes
php artisan activity:archive --days=90 --dry-run
# Archive but keep original records
php artisan activity:archive --days=90 --keep
Archive Location: storage/app/activity-logs/archive-{date}.json
Archive Format:
{
"archived_at": "2026-01-12T10:30:00Z",
"cutoff_date": "2025-10-14T00:00:00Z",
"count": 1234,
"activities": [...]
}
Scheduled Archival:
// app/Console/Kernel.php
$schedule->command('activity:archive --days=90')->monthly();
Activity Statistics API
Get activity statistics for dashboards:
Route: GET /admin/activity/statistics?period=week
Periods: day, week, month, year
Response:
{
"period": "week",
"start_date": "2026-01-06T00:00:00Z",
"total_count": 523,
"by_action": {
"created": 145,
"updated": 234,
"deleted": 12,
"login": 132
},
"top_users": [
{"user": "John Doe", "count": 89},
{"user": "Jane Smith", "count": 67}
]
}
Related Features
Dependencies
| Feature | Relationship |
|---|---|
| Authentication | Logs auth events |
| Authorization | Controls log access |
Complementary Features
| Feature | Description |
|---|---|
| Audit Compliance | Compliance reporting |
| Reports | Activity reports |
| Analytics | Activity metrics |
Best Practices
For Security
- Don't log sensitive data (passwords, tokens)
- Index for efficient queries
- Archive old logs instead of deleting
- Protect log access with authorization
For Developers
- Use traits for automatic logging
- Log meaningful events only
- Include context in properties
- Consider performance for high-volume
Testing
E2E Tests (Playwright)
Activity logging includes comprehensive end-to-end tests using Playwright.
Location: e2e/activity-logging/
| Test File | Description |
|---|---|
index.spec.ts | Activity log list and display |
filtering.spec.ts | Filter functionality |
detail.spec.ts | Detail view tests |
timeline.spec.ts | Timeline view tests |
export.spec.ts | Export functionality |
statistics.spec.ts | Statistics page |
permissions.spec.ts | Access control |
logging.spec.ts | Verify logging works |
entity-activity.spec.ts | Entity-specific activity |
Running Tests:
# Run all E2E tests
npm run test:e2e
# Run with UI
npm run test:e2e:ui
# Run headed (visible browser)
npm run test:e2e:headed
# Debug mode
npm run test:e2e:debug
# View report
npm run test:e2e:report
Troubleshooting
| Issue | Solution |
|---|---|
| Logs not appearing | Check trait is used on model |
| Performance slow | Add indexes, prune old logs |
| Too much logging | Configure what to log |
| Missing user | Check auth middleware order |
See Also
- Audit Compliance - Compliance
- Security - Security features
- Reports - Activity reports