Logging Standards
Overview
This document defines the standards for logging throughout the backend application. Proper logging improves debugging, monitoring, auditing, and overall application reliability.
Logging Strategy
Our application uses a multi-layered logging approach:
- Application Logs - General application events (Laravel Log facade)
- Error Tracking - Real-time error monitoring (Sentry)
- Activity Logs - User and system activity auditing (Spatie Activity Log)
1. Application Logs (Laravel)
Log Channels
Laravel supports multiple log channels configured in config/logging.php:
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single', 'sentry'],
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
],Log Levels
Use appropriate log levels based on severity:
use Illuminate\Support\Facades\Log;
// DEBUG - Detailed debug information
Log::debug('User query executed', ['query' => $query, 'time' => $time]);
// INFO - Informational messages
Log::info('User logged in', ['user_id' => $user->id]);
// NOTICE - Normal but significant events
Log::notice('User updated profile', ['user_id' => $user->id]);
// WARNING - Exceptional occurrences that are not errors
Log::warning('API rate limit approaching', [
'user_id' => $user->id,
'remaining' => 10
]);
// ERROR - Runtime errors that don't require immediate action
Log::error('Payment processing failed', [
'user_id' => $user->id,
'error' => $e->getMessage()
]);
// CRITICAL - Critical conditions
Log::critical('Database connection lost', ['exception' => $e]);
// ALERT - Action must be taken immediately
Log::alert('Disk space critically low', ['available' => '1GB']);
// EMERGENCY - System is unusable
Log::emergency('Application crashed', ['exception' => $e]);Contextual Logging
Always include relevant context:
try {
$user = $this->userRepository->create($data);
Log::info('User created successfully', [
'user_id' => $user->id,
'email' => $user->email,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
]);
} catch (\Exception $e) {
Log::error('Failed to create user', [
'data' => $data,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'ip_address' => request()->ip(),
]);
throw $e;
}Request ID for Tracking
Add request ID to all logs for tracing:
// app/Http/Middleware/AssignRequestId.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
class AssignRequestId
{
public function handle($request, Closure $next)
{
$requestId = Str::uuid()->toString();
$request->headers->set('X-Request-ID', $requestId);
Log::withContext([
'request_id' => $requestId,
'user_id' => auth()->id(),
]);
$response = $next($request);
$response->headers->set('X-Request-ID', $requestId);
return $response;
}
}Register in bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) {
$middleware->append(AssignRequestId::class);
})2. Error Tracking (Sentry)
Sentry provides real-time error tracking and monitoring for production environments.
Installation
composer require sentry/sentry-laravelConfiguration
Environment Variables (.env):
SENTRY_LARAVEL_DSN=your-sentry-dsn-here
SENTRY_TRACES_SAMPLE_RATE=0.1
SENTRY_PROFILES_SAMPLE_RATE=0.1Configuration File (config/sentry.php):
return [
'dsn' => env('SENTRY_LARAVEL_DSN'),
// Performance Monitoring
'traces_sample_rate' => (float)(env('SENTRY_TRACES_SAMPLE_RATE', 0.0)),
// Profiling
'profiles_sample_rate' => (float)(env('SENTRY_PROFILES_SAMPLE_RATE', 0.0)),
'environment' => env('APP_ENV', 'production'),
// Release tracking
'release' => env('SENTRY_RELEASE'),
// Don't report these exceptions
'ignore_exceptions' => [
Illuminate\Auth\AuthenticationException::class,
Illuminate\Validation\ValidationException::class,
],
// Before send callback
'before_send' => function (\Sentry\Event $event): ?\Sentry\Event {
// Don't send events in local environment
if (app()->environment('local')) {
return null;
}
return $event;
},
];Integration Points
1. Exception Handler (app/Exceptions/Handler.php)
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
protected $dontReport = [
\Illuminate\Validation\ValidationException::class,
\Illuminate\Auth\AuthenticationException::class,
];
public function register(): void
{
$this->reportable(function (Throwable $e) {
// Sentry automatically captures all reported exceptions
if (app()->bound('sentry') && $this->shouldReport($e)) {
app('sentry')->captureException($e);
}
});
}
}2. Manual Error Reporting
use Sentry\Laravel\Integration;
try {
// Risky operation
$result = $this->processPayment($data);
} catch (\Exception $e) {
// Add additional context
\Sentry\configureScope(function (\Sentry\State\Scope $scope) use ($data): void {
$scope->setContext('payment_data', [
'amount' => $data['amount'],
'user_id' => $data['user_id'],
]);
$scope->setTag('payment_method', $data['method']);
$scope->setLevel(\Sentry\Severity::error());
});
// Capture the exception
app('sentry')->captureException($e);
throw $e;
}3. Custom Breadcrumbs
\Sentry\addBreadcrumb(new \Sentry\Breadcrumb(
\Sentry\Breadcrumb::LEVEL_INFO,
\Sentry\Breadcrumb::TYPE_DEFAULT,
'user.action',
'User viewed product',
['product_id' => $productId]
));4. Performance Monitoring
// Start a transaction
$transaction = \Sentry\startTransaction([
'op' => 'http.server',
'name' => 'GET /api/users'
]);
\Sentry\SentrySdk::getCurrentHub()->setSpan($transaction);
// Trace a specific operation
$span = $transaction->startChild([
'op' => 'db.query',
'description' => 'Fetch users from database'
]);
$users = User::all();
$span->finish();
$transaction->finish();5. User Context
// In a middleware or authentication logic
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$scope->setUser([
'id' => auth()->id(),
'email' => auth()->user()?->email,
'username' => auth()->user()?->name,
]);
});Where to Use Sentry
| Location | Purpose | Example |
|---|---|---|
app/Exceptions/Handler.php | Global exception handling | All uncaught exceptions |
app/Services/*.php | Service layer errors | Payment failures, API errors |
app/Jobs/*.php | Background job failures | Queue processing errors |
app/Console/Commands/*.php | Artisan command errors | Scheduled task failures |
| Custom error scenarios | Specific tracking needs | Business logic violations |
3. Activity Logging (Spatie Activity Log)
Track user and system activities for auditing purposes.
Installation
composer require spatie/laravel-activitylog
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrate
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-config"Configuration
Configuration File (config/activitylog.php):
return [
// Enable/disable activity logging
'enabled' => env('ACTIVITY_LOG_ENABLED', true),
// Default log name
'default_log_name' => 'default',
// Activity model
'activity_model' => \Spatie\Activitylog\Models\Activity::class,
// Database table
'table_name' => 'activity_log',
// Database connection
'database_connection' => env('ACTIVITY_LOG_DB_CONNECTION'),
// Subject returns soft deleted models
'subject_returns_soft_deleted_models' => false,
// Delete records older than (in days)
'delete_records_older_than_days' => 365,
];Integration Points
1. Automatic Model Logging
Add the LogsActivity trait to your models:
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\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', 'role']) // Only log these attributes
->logOnlyDirty() // Only log changed attributes
->dontSubmitEmptyLogs() // Don't log if nothing changed
->setDescriptionForEvent(fn(string $eventName) => "User has been {$eventName}");
}
}Now all create, update, and delete operations are automatically logged:
$user = User::create(['name' => 'John', 'email' => 'john@example.com']);
// Activity: "User has been created"
$user->update(['name' => 'Jane']);
// Activity: "User has been updated"
$user->delete();
// Activity: "User has been deleted"2. Manual Activity Logging in Controllers
// app/Http/Controllers/Api/V1/PostController.php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\Request;
use Spatie\Activitylog\Facades\Activity;
class PostController extends Controller
{
public function publish(Post $post)
{
$post->update(['status' => 'published']);
activity()
->performedOn($post)
->causedBy(auth()->user())
->withProperties([
'old_status' => 'draft',
'new_status' => 'published',
'ip_address' => request()->ip(),
])
->log('Post published');
return response()->json(['message' => 'Post published successfully']);
}
public function restore($id)
{
$post = Post::withTrashed()->findOrFail($id);
$post->restore();
activity()
->performedOn($post)
->causedBy(auth()->user())
->log('Post restored from trash');
return response()->json(['message' => 'Post restored successfully']);
}
}3. Service Layer Logging
// app/Services/User/UserService.php
namespace App\Services\User;
use App\Models\User;
use Spatie\Activitylog\Facades\Activity;
class UserService
{
public function assignRole(User $user, string $role): void
{
$oldRole = $user->role;
$user->update(['role' => $role]);
activity()
->performedOn($user)
->causedBy(auth()->user())
->withProperties([
'old_role' => $oldRole,
'new_role' => $role,
])
->log('User role changed');
}
public function suspendAccount(User $user, string $reason): void
{
$user->update(['status' => 'suspended']);
activity()
->performedOn($user)
->causedBy(auth()->user())
->withProperties([
'reason' => $reason,
'suspended_at' => now(),
])
->log('User account suspended');
}
}4. Authentication Events
// app/Listeners/LogSuccessfulLogin.php
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
use Spatie\Activitylog\Facades\Activity;
class LogSuccessfulLogin
{
public function handle(Login $event): void
{
activity()
->causedBy($event->user)
->withProperties([
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'timestamp' => now(),
])
->log('User logged in');
}
}
// Register in EventServiceProvider
protected $listen = [
\Illuminate\Auth\Events\Login::class => [
\App\Listeners\LogSuccessfulLogin::class,
],
\Illuminate\Auth\Events\Logout::class => [
\App\Listeners\LogSuccessfulLogout::class,
],
\Illuminate\Auth\Events\Failed::class => [
\App\Listeners\LogFailedLogin::class,
],
];5. Middleware for API Activity
// app/Http/Middleware/LogApiActivity.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Spatie\Activitylog\Facades\Activity;
class LogApiActivity
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// Log important API calls
if ($this->shouldLog($request)) {
activity()
->causedBy(auth()->user())
->withProperties([
'method' => $request->method(),
'url' => $request->fullUrl(),
'ip' => $request->ip(),
'status' => $response->status(),
])
->log('API call: ' . $request->path());
}
return $response;
}
private function shouldLog(Request $request): bool
{
// Only log write operations
return in_array($request->method(), ['POST', 'PUT', 'PATCH', 'DELETE']);
}
}6. Job Processing Logging
// app/Jobs/ProcessReport.php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Spatie\Activitylog\Facades\Activity;
class ProcessReport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle(): void
{
activity()
->withProperties([
'job' => self::class,
'started_at' => now(),
])
->log('Report processing started');
// Process report logic...
activity()
->withProperties([
'job' => self::class,
'completed_at' => now(),
])
->log('Report processing completed');
}
}Retrieving Activity Logs
// Get all activities
$activities = Activity::all();
// Get activities for a specific model
$activities = Activity::forSubject($user)->get();
// Get activities caused by a user
$activities = Activity::causedBy($user)->get();
// Get recent activities
$activities = Activity::latest()->take(10)->get();
// Get activities with specific log name
$activities = Activity::inLog('user_management')->get();Where to Use Activity Logs
| Location | Activity Type | Example |
|---|---|---|
app/Models/*.php | Automatic CRUD | User created, updated, deleted |
app/Http/Controllers/*.php | Controller actions | Post published, comment approved |
app/Services/*.php | Business logic | Role assigned, account suspended |
app/Listeners/*.php | Event-based | Login, logout, password reset |
app/Jobs/*.php | Background tasks | Report generated, emails sent |
app/Http/Middleware/*.php | API tracking | API calls, route access |
Logging Decision Matrix
| Scenario | Use | Reason |
|---|---|---|
| Application debugging | Laravel Log | Local development, stack traces |
| Production errors | Sentry | Real-time alerts, error aggregation |
| User actions (audit trail) | Activity Log | Compliance, security, user tracking |
| Performance issues | Sentry + Log | Transaction tracing, performance metrics |
| Security events | Activity Log + Sentry | Audit trail + alert monitoring |
| API requests/responses | Laravel Log | Development debugging |
| Failed jobs | Sentry + Log | Error tracking + job retry info |
| Authentication events | Activity Log | Security audit trail |
Best Practices
1. Don't Log Sensitive Data
// BAD
Log::info('User login', ['password' => $request->password]);
// GOOD
Log::info('User login', ['email' => $request->email]);2. Use Structured Logging
// BAD
Log::info("User {$user->id} purchased item {$item->id} for ${$amount}");
// GOOD
Log::info('Purchase completed', [
'user_id' => $user->id,
'item_id' => $item->id,
'amount' => $amount,
'currency' => 'USD',
]);3. Clean Old Logs
// Schedule in app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
// Clean activity logs older than 1 year
$schedule->command('activitylog:clean')->daily();
}4. Environment-Specific Logging
// .env
LOG_CHANNEL=stack
LOG_LEVEL=debug # local
LOG_LEVEL=error # production
SENTRY_LARAVEL_DSN= # empty in local
SENTRY_LARAVEL_DSN=your-dsn # set in production5. Don't Over-Log
// BAD - Logging in loops
foreach ($users as $user) {
Log::info('Processing user', ['user_id' => $user->id]);
}
// GOOD - Log summary
Log::info('Batch processing complete', ['users_processed' => count($users)]);Monitoring Dashboard
Create an activity dashboard to view recent activities:
// app/Http/Controllers/Admin/ActivityController.php
namespace App\Http\Controllers\Admin;
use Spatie\Activitylog\Models\Activity;
class ActivityController extends Controller
{
public function index()
{
$activities = Activity::with(['causer', 'subject'])
->latest()
->paginate(50);
return response()->json(['data' => $activities]);
}
public function show($id)
{
$activity = Activity::with(['causer', 'subject'])->findOrFail($id);
return response()->json(['data' => $activity]);
}
}Related Documentation
- Error Handling Standards - Exception handling patterns
- API Design Standards - API logging requirements
- Security Overview - Security event logging
Quick Reference
# View Laravel logs
tail -f storage/logs/laravel.log
# Clear logs
echo "" > storage/logs/laravel.log
# Clean activity logs
php artisan activitylog:clean
# Test Sentry integration
php artisan sentry:test