All articles
LoggingPHPLaravelDevOpsMonitoring

Structured Logging in PHP: Why error_log() Is Not Enough

PKParth Kanasagra
8 min read

The first sign that your logging is broken is when you are in an incident, it is late at night, and you cannot find the error because the logs are full of "Request received", "User logged in", and "Process complete" with no context attached.

Log Levels: Use Them Correctly

  • DEBUG — detailed info for debugging. Never in production.
  • INFO — normal business events worth recording: "User subscribed", "Invoice generated"
  • WARNING — something unexpected happened but the request succeeded: "Payment retry attempt 2"
  • ERROR — something failed: thrown exception, failed API call
  • CRITICAL — a core system is down: database unreachable, Redis unavailable

Set LOG_LEVEL=warning in production. DEBUG and INFO logs in production create noise and fill disks.

Always Include Context

// Useless
Log::error('Payment failed');

// Useful
Log::error('Payment processing failed', [
    'user_id'        => $user->id,
    'order_id'       => $order->id,
    'amount'         => $order->amount,
    'payment_method' => $paymentMethod->type,
    'gateway_error'  => $exception->getMessage(),
    'gateway_code'   => $exception->getCode(),
]);

With the second log, you know: who, what order, how much, what payment method, and what the gateway said. With the first, you know nothing.

Structured JSON Logging for Production

// config/logging.php
'production' => [
    'driver'    => 'monolog',
    'handler'   => StreamHandler::class,
    'formatter' => JsonFormatter::class,
    'with'      => ['stream' => storage_path('logs/laravel.log')],
],

JSON logs are indexable. A log aggregation service (Logtail, Papertrail, Datadog) can query context.user_id = 42 across thousands of entries in milliseconds. Plain text cannot.

Separate Channels for Different Concerns

'channels' => [
    'payments' => [
        'driver' => 'daily',
        'path'   => storage_path('logs/payments.log'),
        'level'  => 'debug',  // Full payment audit trail
        'days'   => 90,       // Longer retention for compliance
    ],
    'security' => [
        'driver' => 'daily',
        'path'   => storage_path('logs/security.log'),
        'level'  => 'info',
        'days'   => 365,
    ],
],

Centralize Logs in Production

When you are running multiple servers, logs on individual machines mean SSH-ing into each one during an incident. Options: Logtail (great free tier), Papertrail (simple and cheap), Datadog (full platform, expensive).

What Not to Log

  • Passwords, tokens, API keys — ever
  • Full credit card numbers — ever
  • Unnecessary personal data (addresses, phone numbers you do not need for debugging)
  • Request bodies by default — they may contain sensitive data

In GDPR territories, logging unnecessary personal data is a compliance issue, not just bad practice.

Related Articles

PK

Parth Kanasagra

Certified Senior Laravel & PHP Developer · 9+ years experience · Available for hire