Administrator — Activity Logs Documentation

Version: 1.0
Last updated: December 10, 2025

Table of Contents


1. Overview

Activity Logs is the core administrative module for tracking, auditing, and viewing all user actions and system events in the DTR System. Administrators can:

  • View immutable audit trail of all user actions
  • Search and filter logs by user, action type, date range, model type
  • Track changes to sensitive data (before/after values)
  • Monitor user access patterns and login history
  • Generate compliance and security reports
  • Identify suspicious activities and unauthorized access
  • Export audit logs for external audits and legal discovery

Key files involved:

  • Admin views: resources/views/users/admin/activity-logs/ (index)
  • Controller: App\Http\Controllers\Web\Admin\ActivityLogController.php (expected)
  • Model: Spatie\ActivityLog\Models\Activity (via spatie/laravel-activity)
  • Database: activity_log table

2. Purpose & Scope

Purpose:

  • Maintain immutable audit trail for compliance (GDPR, SOX, etc.)
  • Track user actions (create, read, update, delete) on all models
  • Monitor system security (login attempts, unauthorized access)
  • Detect unauthorized or suspicious activities
  • Support internal investigations and security incidents
  • Enable legal discovery and regulatory audits
  • Provide accountability for administrative actions

Scope:

  • View all activity logs (read-only for admins)
  • Search and filter logs by multiple criteria
  • View detailed log entries with before/after values
  • Export logs for external audits (CSV, Excel, PDF)
  • Monitor specific users, models, or actions
  • Generate activity reports by date range
  • Track role and permission changes

Out of scope:

  • Modifying or deleting activity logs (immutable by design)
  • User-facing activity dashboard (documented separately)
  • Real-time alerts on activities (documented separately)
  • Advanced ML-based anomaly detection (documented separately)

3. Access & Permissions

Required permissions:

  • view_activity_logs — view all activity logs
  • view_own_activity_logs — view own activity only (for employees)
  • export_activity_logs — export logs to CSV/Excel
  • view_audit_reports — generate compliance reports

Recommended roles:

  • Super Admin — full access to all logs, export, reports
  • Admin — view all logs, export, generate reports
  • Compliance Officer — view all logs, export, generate reports
  • Security Officer — view all logs, search suspicious activities
  • Auditor (external) — read-only access to logs in audit range
  • Manager — view own team's activities only
  • Employee — view own activity only (read-only)

Implementation note:

  • Enforce RBAC server-side via middleware or gate checks
  • Non-admin users scoped to their own activity only
  • Super Admin can view all activities across system
  • Activity logs themselves are immutable (cannot be modified or deleted)

4. How to Access

Admin URLs:

  • Activity logs index: /admin/activity-logs
  • Search logs: /admin/activity-logs?search={query}&filter={type}
  • Filter by date: /admin/activity-logs?from={date}&to={date}
  • Filter by user: /admin/activity-logs?user_id={id}
  • View log details: /admin/activity-logs/{id} (modal or details page)
  • Export logs: /admin/activity-logs/export?format=csv

Browser requirements: Modern Chromium-based browsers, recent Safari/Firefox. Mobile viewing supported; recommend desktop for detailed audit analysis.


5. Interface Layout & Views

5.1 Activity Logs Index

Purpose: Display all activity logs with filtering, search and export capabilities.

View file: resources/views/users/admin/activity-logs/index.blade.php

UI elements:

Top filters & search:

  • Date range picker:

    • From: date input (default: 30 days ago)
    • To: date input (default: today)
    • Quick filters: Last 7 days, Last 30 days, Last 90 days, This month, This year
  • Search bar: global search across log_name, description, model name

    • Placeholder: "Search by action, user, model..."
    • Real-time AJAX search
  • Filter dropdowns:

    • User: select user(s) to filter (multi-select)
    • Action: select action type (create, update, delete, login, etc.)
    • Model Type: filter by affected model (User, Shift, Role, etc.)
    • Status: success, failed, warning
    • IP Address: (optional advanced filter)
  • Quick actions:

    • Export (CSV, Excel, PDF)
    • Generate Report
    • Clear Filters button

Activity logs table:

  • Columns:

    • Timestamp (sortable) — when action occurred
    • User — who performed action (name + ID)
    • Action — what happened (create, update, delete, login, etc.)
    • Model — what was affected (User, Shift, Role, etc.)
    • Description — summary of change
    • IP Address — source IP
    • Status — success/failed/warning (badge)
    • Details (link/button) — view full log entry
  • Pagination: 25 logs per page (configurable)

  • Sorting: by timestamp (newest first default), user, action, model

Key features:

  • Inline status badges (green=success, red=failed, yellow=warning)
  • Quick action dropdown per log row (View Details, Copy ID, etc.)
  • Export logs matching current filters
  • Real-time search via AJAX
  • Color-coded severity (red for deletes, orange for updates, blue for creates)
  • IP geolocation tooltip (country/city from IP)

Controller method (expected):

public function index(Request $request)
{
    $query = Activity::query();

    // Date range filter
    if ($request->from) {
        $query->where('created_at', '>=', $request->from);
    }
    if ($request->to) {
        $query->where('created_at', '<=', $request->to);
    }

    // User filter
    if ($request->user_id) {
        $query->where('causer_id', $request->user_id);
    }

    // Action filter
    if ($request->log_name) {
        $query->where('log_name', $request->log_name);
    }

    // Model filter
    if ($request->model_type) {
        $query->where('subject_type', $request->model_type);
    }

    // Global search
    if ($request->search) {
        $query->where(function ($q) use ($request) {
            $q->where('log_name', 'like', "%{$request->search}%")
              ->orWhere('description', 'like', "%{$request->search}%")
              ->orWhere('causer_type', 'like', "%{$request->search}%");
        });
    }

    $logs = $query->latest()->paginate(25);

    return view('users.admin.activity-logs.index', compact('logs'));
}

5.2 Log Details / Viewer

Purpose: View complete information about a specific activity log entry.

Trigger: Click "Details" or log row in index view.

Display elements:

Header:

  • Log ID
  • Timestamp (with timezone)
  • User who performed action (name, email, ID)
  • User's role(s) at time of action
  • IP address + geolocation (country, city, ISP)
  • User agent / browser info

Action section:

  • Action/Event name (e.g., "user_created", "shift_updated")
  • Log category (create, update, delete, login, etc.)
  • Description/Summary
  • Status (success, failed, warning)

Subject section (what was affected):

  • Model type (e.g., "App\Models\User")
  • Model ID
  • Model name/identifier (e.g., "Jane Doe" for User, "MOR-001" for Shift)
  • Link to view affected model (if still exists)

Changes section (for update actions):

  • Before/after comparison table
  • Fields changed (only show modified fields)
  • Data types highlighted
  • Sensitive fields masked (passwords, tokens, secrets)

Example update log:

Field               Before                  After
name               "John Doe"               "John Smith"
status             "active"                 "suspended"
email              "john@old.com"           "john@new.com"

Associated logs section:

  • Related activity logs (e.g., role assignment after user creation)
  • Linked actions (cascade deletes, related updates)

Actions:

  • Copy log ID button
  • Copy JSON button (full log data)
  • Print button
  • Download JSON button

5.3 Filters & Search

Date range filters:

  • Quick buttons: Last 7 days, Last 30 days, Last 90 days, This month, This year, Custom range
  • Calendar date picker (from/to)
  • Timezone selector (for viewing times in specific timezone)

User filters:

  • Search/select user(s) by name, email, ID
  • Exclude system users (optional)
  • Filter by role
  • Filter by department

Action filters:

  • create — new record created
  • update — existing record modified
  • delete — record deleted
  • read — record accessed (if tracking enabled)
  • login — successful login
  • failed_login — failed login attempt
  • logout — user logged out
  • password_reset — password changed
  • role_assigned — role added to user
  • role_removed — role removed from user
  • permission_granted — permission added
  • permission_revoked — permission removed
  • export — data exported
  • import — data imported
  • suspend — account suspended
  • reactivate — account reactivated

Model filters:

  • User
  • Shift
  • Role
  • Permission
  • Department
  • Leave Request
  • Overtime Request
  • Attendance Record
  • Profile
  • etc.

Status filters:

  • Success — action completed successfully
  • Failed — action failed (validation, permission, etc.)
  • Warning — action succeeded but with warnings

Advanced filters:

  • IP address range
  • User agent contains
  • Specific field changed
  • Multiple conditions (AND/OR logic)

6. Key Features

  • Immutable audit trail: All logs are write-once, cannot be modified or deleted
  • Comprehensive tracking: Captures all CRUD operations, logins, sensitive actions
  • Before/after values: Shows what changed and how for update actions
  • User attribution: Tracks who performed each action (causer)
  • IP address logging: Records source IP for each action
  • Model tracking: Identifies what model/record was affected
  • Search & filter: Find logs by user, action, date, model, IP
  • Export capability: CSV, Excel, PDF formats for external audits
  • Soft delete support: Tracks deletions separately (soft vs hard delete)
  • Performance optimized: Indexed queries for fast filtering on large datasets
  • Compliance ready: Meets GDPR, SOX, HIPAA audit trail requirements
  • Timezone support: View/filter logs in user's timezone
  • Bulk operations tracking: Single log entry for batch actions
  • API integration: Full API for programmatic access and external systems

7. Data Model & Database

Activity Log table fields (Spatie Laravel-Activity):

id                 — Primary key
log_name           — Category/name (string, e.g., 'default', 'user_management')
description        — Human-readable description (string, max 255)
subject_type       — Affected model class (string, e.g., 'App\Models\User')
subject_id         — Affected model ID (nullable)
causer_type        — User model class (string, e.g., 'App\Models\User')
causer_id          — User ID who performed action (nullable)
properties         — JSON data:
  {
    "ip_address": "192.168.1.100",
    "user_agent": "Mozilla/5.0...",
    "method": "POST",
    "url": "/admin/users",
    "old": {                    # Before values (for updates/deletes)
      "name": "John Doe",
      "status": "active"
    },
    "attributes": {             # After values (for creates/updates)
      "name": "Jane Doe",
      "status": "suspended"
    },
    "changes": {                # What changed (diff)
      "name": ["John Doe", "Jane Doe"],
      "status": ["active", "suspended"]
    },
    "status": "success",        # Action result status
    "reason": "User suspended for inactivity"
  }
created_at         — Timestamp when logged
updated_at         — Timestamp (usually same as created_at, immutable)

Sample records:

Activity Log 1 (User Creation):

id: 1001
log_name: "default"
description: "User created"
subject_type: "App\Models\User"
subject_id: 42
causer_type: "App\Models\User"
causer_id: 1 (Admin Jane Doe)
properties: {
  "ip_address": "203.0.113.45",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
  "attributes": {
    "first_name": "Alice",
    "last_name": "Johnson",
    "email": "alice@company.com",
    "status": "active"
  },
  "status": "success"
}
created_at: "2025-01-15 09:30:45"

Activity Log 2 (User Status Change):

id: 1002
log_name: "default"
description: "User suspended"
subject_type: "App\Models\User"
subject_id: 42 (Alice Johnson)
causer_type: "App\Models\User"
causer_id: 1 (Admin Jane Doe)
properties: {
  "ip_address": "203.0.113.46",
  "old": {
    "status": "active",
    "suspended_at": null
  },
  "attributes": {
    "status": "suspended",
    "suspended_at": "2025-01-20 14:22:00"
  },
  "changes": {
    "status": ["active", "suspended"],
    "suspended_at": [null, "2025-01-20 14:22:00"]
  },
  "reason": "Account compromised, temporary suspension pending verification",
  "status": "success"
}
created_at: "2025-01-20 14:22:30"

Activity Log 3 (Failed Login):

id: 1003
log_name: "login"
description: "Failed login attempt"
subject_type: null
subject_id: null
causer_type: null
causer_id: null
properties: {
  "ip_address": "198.51.100.100",
  "email": "alice@company.com",
  "reason": "Invalid password",
  "user_agent": "Mozilla/5.0...",
  "status": "failed",
  "attempt": 2
}
created_at: "2025-01-20 14:20:00"

8. Activity Log Types & Events

User Management Events:

  • user_created — new user account created
  • user_updated — user profile/info modified
  • user_deleted — user account deleted (soft or hard)
  • user_suspended — user account suspended
  • user_reactivated — user account reactivated
  • password_reset — password changed (by self or admin)
  • 2fa_enabled — two-factor authentication enabled
  • 2fa_disabled — two-factor authentication disabled
  • avatar_uploaded — profile photo changed
  • profile_updated — profile data changed (phone, timezone, etc.)

Role & Permission Events:

  • role_assigned — role given to user
  • role_removed — role removed from user
  • role_created — new role created
  • role_updated — role modified
  • role_deleted — role deleted
  • permission_granted — permission added to role
  • permission_revoked — permission removed from role

Authentication Events:

  • login_success — successful user login
  • login_failed — failed login attempt
  • logout — user logged out
  • session_created — new session started
  • session_destroyed — session ended
  • token_generated — API token created
  • token_revoked — API token revoked
  • 2fa_verified — 2FA challenge passed

Shift Management Events:

  • shift_created — new shift created
  • shift_updated — shift modified
  • shift_deleted — shift deleted
  • shift_assigned — user assigned to shift
  • shift_removed — user removed from shift

Data Access Events:

  • data_exported — data exported (CSV, Excel, PDF)
  • data_imported — data imported from file
  • report_generated — report created
  • bulk_action — bulk operation (bulk assign, bulk delete, etc.)

System Events:

  • settings_updated — system settings changed
  • config_changed — configuration modified
  • backup_created — database backup created
  • sync_executed — external system sync (payroll, DTR, etc.)
  • job_executed — background job ran
  • error_logged — system error occurred

9. Log Entry Structure

Complete log entry example (JSON):

{
    "id": 1001,
    "log_name": "default",
    "description": "User suspended",
    "subject_type": "App\Models\User",
    "subject_id": 42,
    "causer_type": "App\Models\User",
    "causer_id": 1,
    "properties": {
        "ip_address": "203.0.113.46",
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        "method": "POST",
        "url": "/admin/users/42/suspend",
        "old": {
            "status": "active",
            "updated_at": "2025-01-15 10:00:00"
        },
        "attributes": {
            "status": "suspended",
            "suspended_at": "2025-01-20 14:22:00",
            "updated_at": "2025-01-20 14:22:30"
        },
        "changes": {
            "status": ["active", "suspended"],
            "suspended_at": [null, "2025-01-20 14:22:00"]
        },
        "reason": "Account compromised, temporary suspension pending verification",
        "status": "success"
    },
    "created_at": "2025-01-20 14:22:30",
    "updated_at": "2025-01-20 14:22:30"
}

Key concepts:

  • subject_type / subject_id: The model that was affected
  • causer_type / causer_id: The user who performed the action
  • properties.old: Previous values (for updates/deletes)
  • properties.attributes: New/current values (for creates/updates)
  • properties.changes: Diff showing what changed (only in updates)
  • properties.status: Whether action succeeded or failed
  • properties.reason: Optional reason/justification for action

10. API Endpoints

GET /api/admin/activity-logs

  • Returns paginated list of all activity logs
  • Query params: page, per_page, search, log_name, model_type, causer_id, from, to, sort
  • Response:
    {
        "data": [
            {
                "id": 1001,
                "log_name": "default",
                "description": "User suspended",
                "subject_type": "App\Models\User",
                "subject_id": 42,
                "causer": {
                    "id": 1,
                    "name": "Jane Doe",
                    "email": "jane@company.com"
                },
                "properties": { ... },
                "created_at": "2025-01-20T14:22:30Z"
            }
        ],
        "pagination": { "total": 5000, "per_page": 25, "current_page": 1 }
    }

GET /api/admin/activity-logs/{id}

  • Returns single activity log with full details
  • Response:
    {
        "id": 1001,
        "log_name": "default",
        "description": "User suspended",
        "subject_type": "App\Models\User",
        "subject_id": 42,
        "subject": {
            "id": 42,
            "name": "Alice Johnson",
            "email": "alice@company.com"
        },
        "causer_type": "App\Models\User",
        "causer_id": 1,
        "causer": {
            "id": 1,
            "name": "Jane Doe",
            "email": "jane@company.com",
            "roles": ["admin"]
        },
        "properties": { ... },
        "created_at": "2025-01-20T14:22:30Z"
    }

GET /api/admin/activity-logs/user/{userId}

  • Get all activity logs for a specific user
  • Query params: page, from, to
  • Response: paginated activity logs

GET /api/admin/activity-logs/model/{modelType}/{modelId}

  • Get all activity logs for a specific model instance
  • Response: all logs affecting the model

GET /api/admin/activity-logs/export

  • Export activity logs matching filters
  • Query params: format (csv, excel, pdf), log_name, model_type, causer_id, from, to
  • Response: file download

POST /api/admin/activity-logs/report

  • Generate activity report
  • Body:
    {
        "report_type": "user_activity|system_overview|security|compliance",
        "from": "2025-01-01",
        "to": "2025-01-31",
        "group_by": "user|day|action",
        "include_failed": true
    }
  • Response: 200 OK + report data

11. Controller Implementation Details

Location: app/Http/Controllers/Web/Admin/ActivityLogController.php (expected)

index() — List all activity logs

public function index(Request $request)
{
    $query = Activity::query();

    // Date range filter
    if ($request->from) {
        $query->where('created_at', '>=', \Carbon\Carbon::parse($request->from)->startOfDay());
    }
    if ($request->to) {
        $query->where('created_at', '<=', \Carbon\Carbon::parse($request->to)->endOfDay());
    }

    // User (causer) filter
    if ($request->causer_id) {
        $query->where('causer_id', $request->causer_id);
    }

    // Action (log_name) filter
    if ($request->log_name) {
        $query->where('log_name', $request->log_name);
    }

    // Model type filter
    if ($request->model_type) {
        $query->where('subject_type', $request->model_type);
    }

    // Global search
    if ($request->search) {
        $query->where(function ($q) use ($request) {
            $q->where('description', 'like', "%{$request->search}%")
              ->orWhereHas('causer', function ($q) use ($request) {
                  $q->where('name', 'like', "%{$request->search}%");
              });
        });
    }

    // Sort
    $sort = $request->sort ?? 'created_at';
    $direction = $request->direction ?? 'desc';
    $query->orderBy($sort, $direction);

    $logs = $query->with('causer')->paginate(25);

    return view('users.admin.activity-logs.index', compact('logs'));
}

show() — View single log details

public function show(string $id)
{
    $log = Activity::with('causer', 'subject')->findOrFail($id);

    return view('users.admin.activity-logs.show', compact('log'));
}

export() — Export logs to file

public function export(Request $request)
{
    $query = Activity::query();

    // Apply same filters as index()
    if ($request->from) {
        $query->where('created_at', '>=', \Carbon\Carbon::parse($request->from));
    }
    if ($request->to) {
        $query->where('created_at', '<=', \Carbon\Carbon::parse($request->to));
    }
    // ... more filters

    $logs = $query->get();

    $format = $request->format ?? 'csv';

    if ($format === 'csv') {
        return $this->exportCsv($logs);
    } elseif ($format === 'excel') {
        return $this->exportExcel($logs);
    } elseif ($format === 'pdf') {
        return $this->exportPdf($logs);
    }
}

private function exportCsv($logs)
{
    $filename = 'activity-logs-' . now()->format('Y-m-d-Hi') . '.csv';

    $headers = [
        'Content-Type' => 'text/csv',
        'Content-Disposition' => "attachment; filename=\"$filename\"",
    ];

    $callback = function () use ($logs) {
        $file = fopen('php://output', 'w');
        fputcsv($file, ['ID', 'Timestamp', 'User', 'Action', 'Model', 'Status', 'Description']);

        foreach ($logs as $log) {
            fputcsv($file, [
                $log->id,
                $log->created_at->format('Y-m-d H:i:s'),
                $log->causer ? $log->causer->name : 'System',
                $log->log_name,
                class_basename($log->subject_type),
                $log->properties['status'] ?? 'success',
                $log->description,
            ]);
        }
        fclose($file);
    };

    return response()->stream($callback, 200, $headers);
}

report() — Generate activity report

public function report(Request $request)
{
    $request->validate([
        'report_type' => 'required|in:user_activity,system_overview,security,compliance',
        'from' => 'required|date',
        'to' => 'required|date|after:from',
        'group_by' => 'nullable|in:user,day,action',
    ]);

    $query = Activity::whereBetween('created_at', [
        $request->from,
        $request->to,
    ]);

    $reportType = $request->report_type;

    if ($reportType === 'user_activity') {
        return $this->userActivityReport($query);
    } elseif ($reportType === 'security') {
        return $this->securityReport($query);
    } elseif ($reportType === 'compliance') {
        return $this->complianceReport($query);
    }

    return response()->json(['error' => 'Invalid report type'], 400);
}

private function userActivityReport($query)
{
    $data = $query->groupBy('causer_id')
        ->selectRaw('causer_id, count(*) as total, log_name')
        ->with('causer')
        ->get();

    return response()->json(['data' => $data]);
}

private function securityReport($query)
{
    $data = [
        'failed_logins' => $query->where('log_name', 'login_failed')->count(),
        'successful_logins' => $query->where('log_name', 'login_success')->count(),
        'suspicious_ips' => $query->selectRaw("JSON_EXTRACT(properties, '$.ip_address') as ip")
            ->groupBy('ip')
            ->having('count', '>', 10)
            ->pluck('ip'),
        'role_changes' => $query->where('log_name', 'role_assigned')->orWhere('log_name', 'role_removed')->count(),
        'data_exports' => $query->where('log_name', 'data_exported')->count(),
    ];

    return response()->json(['data' => $data]);
}

12. Filters, Search & Reports

Quick date filters:

  • Last 7 days
  • Last 30 days
  • Last 90 days
  • This month
  • This year
  • Custom date range

Action filters:

  • Create / Update / Delete
  • Login events (success/failed)
  • Role/permission changes
  • Data export/import
  • System events

User/Model filters:

  • Filter by causer (user who performed action)
  • Filter by subject (model that was affected)
  • Filter by IP address range

Search capabilities:

  • Full-text search on description
  • User name/email search
  • Model name search
  • Fuzzy matching

Reports:

  • User Activity Report: Count of actions per user, most active users, action breakdown
  • System Overview: Total actions by type, daily activity graph, top 10 actions
  • Security Report: Failed login attempts, suspicious IPs, role changes, permission changes
  • Compliance Report: Data exports, bulk actions, sensitive field changes, termination events
  • Audit Trail: Complete sequence of events for specific user or model

Export formats: CSV, Excel, PDF


13. Integrations & System Events

User Management Integration:

  • Track user creation with assigned roles and permissions
  • Log password resets and 2FA changes
  • Monitor account suspensions and reactivations
  • Track role assignments and removals

Shift Management Integration:

  • Log shift creation, updates, deletions
  • Track user assignments and removals
  • Monitor shift status changes

DTR Integration:

  • Log clock-in/out events (if enabled)
  • Track DTR adjustments or corrections
  • Monitor DTR manual entries

Payroll Integration:

  • Log payroll runs and adjustments
  • Track deduction changes
  • Monitor payroll exports

Leave Management Integration:

  • Track leave requests created/approved/rejected
  • Log cancellations and modifications

System Events:

  • Background job execution
  • Scheduled task runs
  • API token generation/revocation
  • Configuration changes
  • Backup execution

Audit trail requirements:

  • Immutable: logs cannot be modified or deleted
  • Retention: configured per compliance policy (default 1 year minimum)
  • Granular: track individual field changes
  • Complete: capture all CRUD operations
  • Auditable: include who, what, when, where, why

14. Common Tasks & Workflows

Task 1: View Recent Activity

  1. Navigate: Admin → Activity Logs
  2. System shows logs from last 30 days by default
  3. Most recent first (newest at top)
  4. See user, action, model, timestamp ✓

Task 2: Search for Specific Action

  1. Navigate: Admin → Activity Logs
  2. Click search bar: "Search by action, user, model..."
  3. Type: "Alice Johnson" or "user_created"
  4. Results filter in real-time
  5. Click on log to see full details ✓

Task 3: Filter by User

  1. Navigate: Admin → Activity Logs
  2. Click "User" filter dropdown
  3. Search and select "Alice Johnson"
  4. System shows all actions by Alice
  5. Can combine with date range filter ✓

Task 4: View Before/After Changes

  1. Navigate: Admin → Activity Logs
  2. Find update log (e.g., "User suspended")
  3. Click "Details" or log row
  4. Scroll to "Changes" section
  5. See side-by-side before/after values ✓

Task 5: Export Logs for Audit

  1. Navigate: Admin → Activity Logs
  2. Set date range: Jan 1 - Jan 31, 2025
  3. Filter by model: "User"
  4. Click "Export" button
  5. Select format: CSV, Excel, or PDF
  6. Download file with all matching logs ✓

Task 6: Generate Security Report

  1. Navigate: Admin → Activity Logs
  2. Click "Generate Report" button
  3. Select report type: "Security"
  4. Set date range
  5. Click "Generate"
  6. View report: failed logins, role changes, suspicious IPs ✓

Task 7: Track User Onboarding Activity

  1. Navigate: Admin → Activity Logs
  2. Filter by causer: "HR Admin Jane"
  3. Filter by log_name: "user_created"
  4. Date range: last 30 days
  5. See all new users created by Jane ✓

Task 8: Investigate Suspicious Activity

  1. Navigate: Admin → Activity Logs
  2. Filter by IP address range: "198.51.100.*"
  3. Filter by log_name: "login_failed"
  4. See multiple failed login attempts
  5. Check if user account was compromised
  6. Take security action (suspend, password reset, 2FA reset) ✓

15. Audit Trail & Compliance

Compliance standards met:

  • GDPR: Complete audit trail, data subject access, right to erasure exceptions
  • SOX (Sarbanes-Oxley): Immutable logs, user attribution, change tracking
  • HIPAA: Comprehensive access logs, activity monitoring, breach detection
  • ISO 27001: Security incident logging, audit trail maintenance
  • PCI DSS: User authentication logs, data access tracking

Audit trail requirements:

  • Immutability: Logs cannot be modified or deleted (database constraints)
  • Completeness: All CRUD operations captured (hooks in models)
  • Uniqueness: Each action has unique ID and timestamp
  • Attribution: Always track who performed action (causer_id)
  • Timeliness: Log entries created immediately after action
  • Retention: Configurable per policy (min 1 year, recommend 3 years)
  • Access control: Only authorized roles can view logs

Sensitive data handling:

  • Passwords never logged (exclude from properties)
  • API tokens masked (show first 4 and last 4 chars)
  • Personally identifiable info (PII) can be masked in export
  • Encryption at rest (database encryption enabled)
  • Encryption in transit (HTTPS required)

Data retention policy:

  • Active logs: retained indefinitely (database archiving handled separately)
  • Export archives: retained per compliance policy
  • Backup retention: same as primary database
  • Audit logs: immutable, retained per legal hold requests

16. Practical Examples

Example 1: User Onboarding Audit Trail

2025-01-15 09:30:45  Admin Jane Doe    user_created       Alice Johnson     success
2025-01-15 09:31:02  Admin Jane Doe    role_assigned      Alice Johnson (role: employee)     success
2025-01-15 09:31:15  Alice Johnson     login_success      -                   success
2025-01-15 09:32:00  Alice Johnson     profile_updated    Alice Johnson     success
2025-01-15 10:15:30  Admin John Smith  role_assigned      Alice Johnson (role: team-lead)    success

Example 2: Security Incident Investigation

2025-01-20 14:15:00  Unknown IP: 198.51.100.100    login_failed    -    failed (invalid password)
2025-01-20 14:15:30  Unknown IP: 198.51.100.100    login_failed    -    failed (invalid password)
2025-01-20 14:16:00  Unknown IP: 198.51.100.100    login_failed    -    failed (invalid password)
2025-01-20 14:22:30  Admin Jane Doe                user_suspended  Alice Johnson    success (reason: account compromised)
2025-01-20 15:00:00  Alice Johnson                 password_reset  Alice Johnson    success

Example 3: Role Change Tracking

Date Range: Jan 2025

User                    Role Assigned       Date            Assigned By
Alice Johnson          team-lead            2025-01-15     Admin Jane Doe
Bob Smith              scheduler            2025-01-17     Admin Jane Doe
Carol Davis            hr-manager           2025-01-20     Admin John Smith
David Wilson           team-lead            2025-01-22     Admin Jane Doe

Removals:
Eve Martinez           team-lead            2025-01-25     Admin Jane Doe (reason: promotion to manager)
Frank Lee              scheduler            2025-01-28     Admin John Smith (reason: role reassignment)

Example 4: Data Compliance Report

Audit Period: Q4 2024 (Oct 1 - Dec 31, 2024)

Total Activities: 15,432
- Creates: 2,145
- Updates: 11,203
- Deletes: 89
- Logins: 1,245
- Failed Logins: 34
- Role Changes: 67
- Data Exports: 23

Critical Actions:
- User Suspensions: 8
- User Deletions: 5
- Role Removals: 12
- Password Resets (forced): 3

Most Active User: Admin Jane Doe (3,456 actions)
Least Active: Manager Bob Smith (23 actions)
Peak Activity Day: Dec 15 (342 actions)

17. Testing & QA Checklist

Unit tests:

  • ✓ Activity log created on model create event
  • ✓ Activity log created on model update event
  • ✓ Activity log created on model delete event
  • ✓ Only changed fields logged (not all fields)
  • ✓ Before/after values captured correctly
  • ✓ Causer (user) attribution tracked
  • ✓ IP address captured from request

Integration tests:

  • ✓ User create → activity log created
  • ✓ User update → activity log with changes captured
  • ✓ User delete → soft delete logged
  • ✓ Role assignment → activity log created
  • ✓ Login success → login_success log created
  • ✓ Login failed → login_failed log created
  • ✓ Data export → data_exported log created

E2E tests (manual):

  • ✓ Admin creates user → appears in activity logs
  • ✓ Admin updates user → changes shown before/after
  • ✓ Admin assigns role → log entry created
  • ✓ Admin searches logs → results appear
  • ✓ Admin filters by date → correct logs shown
  • ✓ Admin exports logs → CSV/Excel file generated
  • ✓ Admin views log details → full information displayed
  • ✓ Search by user → shows only that user's actions

Performance tests:

  • ✓ Index page (10,000 logs) loads < 1 sec
  • ✓ Search filters < 200ms
  • ✓ Pagination (25 per page) works smoothly
  • ✓ Export (1,000 logs) generates < 5 sec
  • ✓ Report generation < 10 sec
  • ✓ Database queries optimized (use indexes on causer_id, subject_id, created_at)

Compliance tests:

  • ✓ Logs cannot be modified (database constraint)
  • ✓ Logs cannot be deleted (soft delete not applicable)
  • ✓ Passwords not logged
  • ✓ IP addresses captured and logged
  • ✓ Timezone handled correctly
  • ✓ User attribution always present for user actions
  • ✓ Retention policy enforced

18. Troubleshooting & FAQs

Q: "Activity logs not appearing after user creation"

  • A:
    • Verify ActivityLogger middleware is registered in HTTP kernel
    • Check app/Models/User has LogsActivity trait
    • Verify activity jobs queue is running: php artisan queue:work
    • Check Laravel logs for errors: storage/logs/laravel.log
    • Try manually triggering: php artisan activity:logs:sync

Q: "Cannot find log entry for specific action"

  • A:
    • Verify action is trackable (check LogsActivity configuration)
    • Check date range is correct (timezone issue?)
    • Try broader search terms
    • Look at exact spelling of action name (case-sensitive)
    • Verify user has view_activity_logs permission

Q: "Logs are taking too long to load"

  • A:
    • Check database indexes: created_at, causer_id, subject_type, subject_id
    • Create missing indexes: php artisan migrate
    • Archive old logs (older than 1 year) to separate table
    • Use pagination (don't load all at once)
    • Limit date range filter (avoid "all time" queries)

Q: "Export file is empty or incomplete"

  • A:
    • Check filters are correct
    • Verify date range includes data
    • Try exporting without filters
    • Check file system permissions (storage/app/exports)
    • Try different format (CSV instead of Excel)

Q: "Cannot see other users' logs (permission denied)"

  • A:
    • Verify user has view_activity_logs permission
    • Admin users should have this permission
    • Non-admin can only view own logs (check authorization)
    • Assign permission via php artisan permission:grant view_activity_logs admin

Q: "Sensitive data (password, token) showing in logs"

  • A:
    • Update LogsActivity::attributesToExclude() to exclude sensitive fields
    • Example:
      protected static function getChangesForLogging($old, $new)
      {
          $exclude = ['password', 'remember_token', 'api_token'];
          // Filter out excluded fields
      }
    • Mask PII in export if needed: use mask_pii option

Q: "Timezone showing incorrect time"

  • A:
    • Check Laravel APP_TIMEZONE in .env (default: UTC)
    • Update if needed to match organization timezone
    • Or show times in user's timezone in UI
    • Database stores in UTC, display converts on read

19. Appendix: SQL & Code Examples

SQL: Find all logs for specific user (causer)

SELECT * FROM activity_log
WHERE causer_id = 42
ORDER BY created_at DESC;

SQL: Find all logs affecting specific model

SELECT * FROM activity_log
WHERE subject_type = 'App\Models\User'
  AND subject_id = 42
ORDER BY created_at DESC;

SQL: Find failed login attempts (last 7 days)

SELECT * FROM activity_log
WHERE log_name = 'login_failed'
  AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY created_at DESC;

SQL: Count activities by type (last 30 days)

SELECT log_name, COUNT(*) as count
FROM activity_log
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY log_name
ORDER BY count DESC;

SQL: Find suspicious activity (same IP, multiple failed logins)

SELECT JSON_EXTRACT(properties, '$.ip_address') as ip_address,
       COUNT(*) as attempt_count
FROM activity_log
WHERE log_name = 'login_failed'
  AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
GROUP BY ip_address
HAVING attempt_count > 5;

SQL: Create index for performance

CREATE INDEX idx_causer_id ON activity_log(causer_id);
CREATE INDEX idx_subject ON activity_log(subject_type, subject_id);
CREATE INDEX idx_log_name ON activity_log(log_name);
CREATE INDEX idx_created_at ON activity_log(created_at);

Laravel: Register activity tracking

// In AppServiceProvider or activitylog config

Activity::useLog('default');
Activity::withoutLogging(function () {
    // Actions here won't be logged
});

// In Model
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;

class User extends Model
{
    use LogsActivity;

    public function getActivitylogOptions(): LogOptions
    {
        return LogOptions::defaults()
            ->logOnly(['name', 'email', 'status'])  // Only log these fields
            ->logOnlyDirty()  // Only log if changed
            ->dontLogIfAttributesSet(['password']) // Exclude sensitive fields
            ->useLogName('user_management');
    }
}

Laravel: Custom activity logging

// Manually log an action
activity()
    ->causedBy(auth()->user())
    ->performedOn($model)
    ->withProperties([
        'ip_address' => request()->ip(),
        'reason' => 'User requested data export',
        'status' => 'success'
    ])
    ->log('data_exported');

Laravel: Query activity logs

// Get all logs for a user
$logs = Activity::whereCausedBy($user)
    ->latest()
    ->paginate(25);

// Get logs affecting a model
$logs = Activity::forSubject($model)
    ->latest()
    ->get();

// Get logs by action type
$logs = Activity::where('log_name', 'user_created')
    ->whereBetween('created_at', [$from, $to])
    ->get();

// With relationships
$logs = Activity::with('causer', 'subject')
    ->latest()
    ->paginate(25);

Blade template: Activity log table row

@foreach($logs as $log)
    <tr>
        <td class="text-sm">{{ $log->created_at->format('Y-m-d H:i:s') }}</td>
        <td>
            @if($log->causer)
                <span class="badge">{{ $log->causer->name }}</span>
            @else
                <span class="text-muted">System</span>
            @endif
        </td>
        <td>
            <span class="badge badge-{{ $log->log_name === 'deleted' ? 'danger' : 'info' }}">
                {{ $log->log_name }}
            </span>
        </td>
        <td>{{ class_basename($log->subject_type) }}</td>
        <td>{{ $log->description }}</td>
        <td>
            <span class="badge badge-{{ isset($log->properties['status']) && $log->properties['status'] === 'failed' ? 'warning' : 'success' }}">
                {{ $log->properties['status'] ?? 'success' }}
            </span>
        </td>
        <td>
            <button class="btn btn-sm btn-info" @click="viewDetails({{ $log->id }})">View</button>
        </td>
    </tr>
@endforeach

Blade template: Log details modal

<!-- Activity Log Details Modal -->
<div class="modal fade" id="logDetailsModal" tabindex="-1">
    <div class="modal-dialog modal-lg">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Activity Log #<span id="logId"></span></h5>
                <button type="button" class="close" data-dismiss="modal">
                    <span>&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <div class="row">
                    <div class="col-md-6">
                        <h6>User</h6>
                        <p id="logUser" class="text-muted"></p>
                    </div>
                    <div class="col-md-6">
                        <h6>Date/Time</h6>
                        <p id="logDate" class="text-muted"></p>
                    </div>
                </div>

                <div class="row">
                    <div class="col-md-6">
                        <h6>Action</h6>
                        <p id="logAction" class="text-muted"></p>
                    </div>
                    <div class="col-md-6">
                        <h6>Model</h6>
                        <p id="logModel" class="text-muted"></p>
                    </div>
                </div>

                <hr>

                <h6>Changes</h6>
                <table class="table table-sm" id="changesTable">
                    <thead>
                        <tr>
                            <th>Field</th>
                            <th>Before</th>
                            <th>After</th>
                        </tr>
                    </thead>
                    <tbody id="changesBody">
                    </tbody>
                </table>

                <hr>

                <h6>Raw JSON</h6>
                <pre id="logJson" class="bg-light p-3"></pre>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                <button type="button" class="btn btn-primary" @click="copyJson()">Copy JSON</button>
            </div>
        </div>
    </div>
</div>

Document version: 1.0
Maintainers: Security / Compliance / Engineering / Operations
Last updated: December 10, 2025