Skip to content

Auditing & Logging

Where this lives / How to access

  • Repo: Eliinova/the-one-otc-api
  • Config: config/logging.php
  • Audit log table: audit_logs (see migration in database/migrations/)
  • Models with LogsActivity: see app/Models/ (e.g. Product.php, User.php)
  • Horizon (queue jobs for async logging):
    • Local: http://localhost:8000/horizon
    • Staging: https://stage-api.easyotc.com/horizon
  • Required role to view audit logs: OTC_ONE_ADMIN (see permissions)
  • See /access for environment URLs and credentials.

EasyOTC uses Spatie's Laravel Logging package to provide comprehensive auditing and logging capabilities for tracking changes, system events, and user activities.

Overview

The logging system provides:

  • Change tracking - Monitor modifications to model data
  • User activity logging - Track user actions and system events
  • Audit trails - Complete history of data changes
  • Security monitoring - Detect suspicious activities
  • Compliance support - Meet regulatory requirements

Installation & Setup

1. Install Spatie Laravel Logging

bash
composer require spatie/laravel-logging

2. Publish Configuration

bash
php artisan vendor:publish --provider="Spatie\LaravelLogging\LaravelLoggingServiceProvider"

3. Run Migrations

bash
php artisan migrate

Configuration

Environment Variables

Add these to your .env file:

env
# Logging Configuration
LOG_CHANNEL=stack
LOG_LEVEL=debug

# Audit Logging
AUDIT_LOG_ENABLED=true
AUDIT_LOG_DRIVER=database
AUDIT_LOG_TABLE=audit_logs

Configuration File

Update config/logging.php:

php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['single', 'audit'],
    ],
    
    'audit' => [
        'driver' => 'database',
        'table' => 'audit_logs',
        'level' => 'info',
    ],
],

Model Auditing

Enable Auditing on Models

Add the LogsActivity trait to models you want to audit:

php
use Spatie\LaravelLogging\Traits\LogsActivity;

class Product extends Model
{
    use LogsActivity;
    
    // Define which attributes to log
    protected static $logAttributes = [
        'name',
        'price',
        'is_available',
        'inventory_count',
        'requires_prescription',
    ];
    
    // Define which events to log
    protected static $logEvents = [
        'created',
        'updated',
        'deleted',
    ];
    
    // Define which attributes to ignore
    protected static $logAttributesToIgnore = [
        'updated_at',
    ];
    
    // Customize log description
    public function getLogDescriptionAttribute(): string
    {
        return "Product '{$this->name}' was {$this->log_event}";
    }
}

Advanced Auditing Configuration

php
class User extends Model
{
    use LogsActivity;
    
    // Log all attributes except timestamps
    protected static $logAttributes = ['*'];
    protected static $logAttributesToIgnore = [
        'created_at',
        'updated_at',
        'remember_token',
    ];
    
    // Only log specific events
    protected static $logEvents = [
        'created',
        'updated',
    ];
    
    // Log only when specific attributes change
    protected static $logOnlyDirty = true;
    
    // Custom log name
    protected static $logName = 'user_management';
    
    // Custom log description
    public function getLogDescriptionAttribute(): string
    {
        $event = $this->log_event;
        $user = auth()->user();
        
        return "User '{$this->name}' was {$event} by " . ($user ? $user->name : 'system');
    }
}

Logging System Events

User Activity Logging

php
use Spatie\LaravelLogging\Facades\Log;

// Log user login
Log::info('User logged in', [
    'user_id' => $user->id,
    'email' => $user->email,
    'ip_address' => request()->ip(),
    'user_agent' => request()->userAgent(),
]);

// Log user logout
Log::info('User logged out', [
    'user_id' => $user->id,
    'session_duration' => $sessionDuration,
]);

// Log failed login attempts
Log::warning('Failed login attempt', [
    'email' => $email,
    'ip_address' => request()->ip(),
    'attempt_count' => $attemptCount,
]);

API Activity Logging

php
// Log API requests
Log::info('API request', [
    'method' => request()->method(),
    'url' => request()->fullUrl(),
    'user_id' => auth()->id(),
    'ip_address' => request()->ip(),
    'response_time' => $responseTime,
]);

// Log prescription approvals
Log::info('Prescription approved', [
    'cart_item_id' => $cartItem->id,
    'product_id' => $cartItem->product_id,
    'approved_by' => auth()->id(),
    'approval_notes' => $notes,
]);

Cart Activity Logging

php
// Log cart modifications
Log::info('Cart item added', [
    'cart_id' => $cart->id,
    'product_id' => $product->id,
    'quantity' => $quantity,
    'user_id' => auth()->id(),
]);

// Log cart checkout
Log::info('Cart checkout initiated', [
    'cart_id' => $cart->id,
    'total_items' => $cart->total_items,
    'subtotal' => $cart->subtotal,
    'user_id' => auth()->id(),
]);

Audit Trail Queries

Retrieve Audit Logs

php
use Spatie\LaravelLogging\Models\Activity;

// Get all activities for a specific model
$activities = Activity::forSubject($product)->get();

// Get activities by user
$userActivities = Activity::causedBy($user)->get();

// Get activities by event type
$createdActivities = Activity::where('event', 'created')->get();

// Get recent activities
$recentActivities = Activity::latest()->take(50)->get();

// Get activities within date range
$activities = Activity::whereBetween('created_at', [
    now()->subDays(30),
    now()
])->get();

Advanced Queries

php
// Get activities for specific model type
$productActivities = Activity::forSubjectType(Product::class)->get();

// Get activities with specific properties
$priceChanges = Activity::whereJsonContains('properties', [
    'attributes' => ['price' => '*']
])->get();

// Get activities by log name
$userManagementLogs = Activity::inLog('user_management')->get();

// Get activities with custom filters
$activities = Activity::query()
    ->where('causer_id', $userId)
    ->where('event', 'updated')
    ->whereJsonLength('properties->changes', '>', 0)
    ->get();

Log Analysis & Reporting

Generate Audit Reports

php
// Daily activity summary
$dailySummary = Activity::selectRaw('
    DATE(created_at) as date,
    COUNT(*) as total_activities,
    COUNT(DISTINCT causer_id) as unique_users,
    COUNT(CASE WHEN event = "created" THEN 1 END) as creations,
    COUNT(CASE WHEN event = "updated" THEN 1 END) as updates,
    COUNT(CASE WHEN event = "deleted" THEN 1 END) as deletions
')
->whereBetween('created_at', [now()->subDays(7), now()])
->groupBy('date')
->get();

// User activity report
$userActivity = Activity::selectRaw('
    causer_id,
    COUNT(*) as activity_count,
    COUNT(DISTINCT DATE(created_at)) as active_days
')
->whereNotNull('causer_id')
->groupBy('causer_id')
->orderBy('activity_count', 'desc')
->get();

Security Monitoring

php
// Detect suspicious activities
$suspiciousActivities = Activity::where(function ($query) {
    $query->where('event', 'deleted')
          ->orWhereJsonLength('properties->changes', '>', 5);
})
->where('created_at', '>=', now()->subHours(1))
->get();

// Monitor failed login attempts
$failedLogins = Activity::where('description', 'like', '%Failed login%')
    ->where('created_at', '>=', now()->subHours(24))
    ->groupBy('properties->email')
    ->havingRaw('COUNT(*) > 5')
    ->get();

Log Storage & Retention

Database Storage

Audit logs are stored in the audit_logs table with the following structure:

sql
CREATE TABLE audit_logs (
    id bigint unsigned NOT NULL AUTO_INCREMENT,
    log_name varchar(255) DEFAULT NULL,
    description text,
    subject_type varchar(255) DEFAULT NULL,
    subject_id bigint unsigned DEFAULT NULL,
    causer_type varchar(255) DEFAULT NULL,
    causer_id bigint unsigned DEFAULT NULL,
    properties json DEFAULT NULL,
    event varchar(255) DEFAULT NULL,
    batch_uuid char(36) DEFAULT NULL,
    created_at timestamp NULL DEFAULT NULL,
    updated_at timestamp NULL DEFAULT NULL,
    PRIMARY KEY (id),
    KEY audit_logs_subject_type_subject_id_index (subject_type, subject_id),
    KEY audit_logs_causer_type_causer_id_index (causer_type, causer_id),
    KEY audit_logs_event_index (event),
    KEY audit_logs_log_name_index (log_name)
);

Log Retention Policy

php
// Clean up old logs (run via scheduled command)
public function cleanupOldLogs()
{
    $retentionDays = config('logging.audit_retention_days', 365);
    
    Activity::where('created_at', '<', now()->subDays($retentionDays))
        ->delete();
}

API Endpoints

Get Audit Logs

http
GET /api/audit-logs?subject_type=Product&subject_id=123&event=updated

Query Parameters:

  • subject_type - Model class name
  • subject_id - Model ID
  • event - Event type (created, updated, deleted)
  • causer_id - User ID who caused the activity
  • date_from - Start date
  • date_to - End date
  • page - Page number
  • per_page - Results per page

Get Activity Summary

http
GET /api/audit-logs/summary?date_from=2024-01-01&date_to=2024-01-31

Monitoring & Alerts

Set Up Alerts

php
// Alert on suspicious activities
if ($suspiciousActivities->count() > 0) {
    // Send notification to admin
    Notification::route('mail', 'admin@example.com')
        ->notify(new SuspiciousActivityAlert($suspiciousActivities));
}

// Alert on high activity volume
if ($dailySummary->sum('total_activities') > 10000) {
    // Send notification about high activity
    Notification::route('mail', 'admin@example.com')
        ->notify(new HighActivityAlert($dailySummary));
}

Dashboard Integration

php
// Get activity statistics for dashboard
$stats = [
    'total_activities_today' => Activity::whereDate('created_at', today())->count(),
    'unique_users_today' => Activity::whereDate('created_at', today())->distinct('causer_id')->count(),
    'most_active_user' => Activity::selectRaw('causer_id, COUNT(*) as count')
        ->whereNotNull('causer_id')
        ->whereDate('created_at', today())
        ->groupBy('causer_id')
        ->orderBy('count', 'desc')
        ->first(),
];

Best Practices

  1. Selective logging - Only log important attributes and events
  2. Performance optimization - Use queue-based logging for high-volume operations
  3. Data privacy - Never log sensitive information like passwords
  4. Regular cleanup - Implement log retention policies
  5. Monitoring - Set up alerts for suspicious activities
  6. Backup strategy - Include audit logs in backup procedures
  7. Compliance - Ensure logging meets regulatory requirements

Troubleshooting

Common Issues

High disk usage:

  • Implement log retention policies
  • Use log rotation
  • Consider archiving old logs

Performance issues:

  • Use queue-based logging
  • Optimize database queries
  • Index frequently queried columns

Missing logs:

  • Check if models have LogsActivity trait
  • Verify log configuration
  • Check for failed queue jobs

This comprehensive logging system provides complete visibility into system activities, ensuring security, compliance, and operational transparency for EasyOTC.