Skip to content

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:

  1. Application Logs - General application events (Laravel Log facade)
  2. Error Tracking - Real-time error monitoring (Sentry)
  3. 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:

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:

php
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:

php
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:

php
// 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+):

php
->withMiddleware(function (Middleware $middleware) {
    $middleware->append(AssignRequestId::class);
})

2. Error Tracking (Sentry)

Sentry provides real-time error tracking and monitoring for production environments.

Installation

bash
composer require sentry/sentry-laravel

Configuration

Environment Variables (.env):

env
SENTRY_LARAVEL_DSN=your-sentry-dsn-here
SENTRY_TRACES_SAMPLE_RATE=0.1
SENTRY_PROFILES_SAMPLE_RATE=0.1

Configuration File (config/sentry.php):

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)

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

php
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

php
\Sentry\addBreadcrumb(new \Sentry\Breadcrumb(
    \Sentry\Breadcrumb::LEVEL_INFO,
    \Sentry\Breadcrumb::TYPE_DEFAULT,
    'user.action',
    'User viewed product',
    ['product_id' => $productId]
));

4. Performance Monitoring

php
// 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

php
// 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

LocationPurposeExample
app/Exceptions/Handler.phpGlobal exception handlingAll uncaught exceptions
app/Services/*.phpService layer errorsPayment failures, API errors
app/Jobs/*.phpBackground job failuresQueue processing errors
app/Console/Commands/*.phpArtisan command errorsScheduled task failures
Custom error scenariosSpecific tracking needsBusiness logic violations

3. Activity Logging (Spatie Activity Log)

Track user and system activities for auditing purposes.

Installation

bash
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):

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:

php
// 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:

php
$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

php
// 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

php
// 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

php
// 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

php
// 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

php
// 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

php
// 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

LocationActivity TypeExample
app/Models/*.phpAutomatic CRUDUser created, updated, deleted
app/Http/Controllers/*.phpController actionsPost published, comment approved
app/Services/*.phpBusiness logicRole assigned, account suspended
app/Listeners/*.phpEvent-basedLogin, logout, password reset
app/Jobs/*.phpBackground tasksReport generated, emails sent
app/Http/Middleware/*.phpAPI trackingAPI calls, route access

Logging Decision Matrix

ScenarioUseReason
Application debuggingLaravel LogLocal development, stack traces
Production errorsSentryReal-time alerts, error aggregation
User actions (audit trail)Activity LogCompliance, security, user tracking
Performance issuesSentry + LogTransaction tracing, performance metrics
Security eventsActivity Log + SentryAudit trail + alert monitoring
API requests/responsesLaravel LogDevelopment debugging
Failed jobsSentry + LogError tracking + job retry info
Authentication eventsActivity LogSecurity audit trail

Best Practices

1. Don't Log Sensitive Data

php
// BAD
Log::info('User login', ['password' => $request->password]);

// GOOD
Log::info('User login', ['email' => $request->email]);

2. Use Structured Logging

php
// 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

php
// 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

php
// .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 production

5. Don't Over-Log

php
// 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:

php
// 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]);
    }
}


Quick Reference

bash
# 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

CPR - Clinical Patient Records