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 indatabase/migrations/) - Models with
LogsActivity: seeapp/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
- Local:
- 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-logging2. Publish Configuration
bash
php artisan vendor:publish --provider="Spatie\LaravelLogging\LaravelLoggingServiceProvider"3. Run Migrations
bash
php artisan migrateConfiguration
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_logsConfiguration 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=updatedQuery Parameters:
subject_type- Model class namesubject_id- Model IDevent- Event type (created, updated, deleted)causer_id- User ID who caused the activitydate_from- Start datedate_to- End datepage- Page numberper_page- Results per page
Get Activity Summary
http
GET /api/audit-logs/summary?date_from=2024-01-01&date_to=2024-01-31Monitoring & 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
- Selective logging - Only log important attributes and events
- Performance optimization - Use queue-based logging for high-volume operations
- Data privacy - Never log sensitive information like passwords
- Regular cleanup - Implement log retention policies
- Monitoring - Set up alerts for suspicious activities
- Backup strategy - Include audit logs in backup procedures
- 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.