PHP Error Logging Setup Guide: From Basics to Production
Tutorials August 6, 2025 ยท 3 min read

PHP Error Logging Setup Guide: From Basics to Production

Learn how to configure PHP error logging for production applications, including custom error handlers and centralized logging.

PHP's default error handling often falls short for production applications. Errors get written to server logs that are hard to search, or worse, displayed to users. This guide shows you how to build a robust error logging system.

PHP Configuration Basics

Start by configuring PHP's error handling in php.ini or at runtime:

// Production settings
ini_set('display_errors', '0');         // Never show errors to users
ini_set('log_errors', '1');             // Log errors instead
ini_set('error_log', '/var/log/php/errors.log');
error_reporting(E_ALL);                  // Report all errors

Custom Error Handler

PHP's default error handler is limited. Create a custom one:

<?php

class ErrorHandler
{
    private string $apiUrl;
    private string $apiToken;
    private array $buffer = [];

    public function __construct(string $apiUrl, string $apiToken)
    {
        $this->apiUrl = $apiUrl;
        $this->apiToken = $apiToken;

        set_error_handler([$this, 'handleError']);
        set_exception_handler([$this, 'handleException']);
        register_shutdown_function([$this, 'handleShutdown']);
    }

    public function handleError(int $errno, string $errstr, string $errfile, int $errline): bool
    {
        $level = match($errno) {
            E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR => 'error',
            E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING => 'warning',
            E_NOTICE, E_USER_NOTICE => 'notice',
            default => 'info',
        };

        $this->log($level, $errstr, [
            'file' => $errfile,
            'line' => $errline,
            'errno' => $errno,
        ]);

        return false; // Let PHP handle it too
    }

    public function handleException(\Throwable $e): void
    {
        $this->log('error', $e->getMessage(), [
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'trace' => $e->getTraceAsString(),
            'exception_class' => get_class($e),
        ]);
    }

    public function handleShutdown(): void
    {
        $error = error_get_last();
        if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR])) {
            $this->log('critical', $error['message'], [
                'file' => $error['file'],
                'line' => $error['line'],
            ]);
        }

        $this->flush();
    }

    private function log(string $level, string $message, array $context = []): void
    {
        $this->buffer[] = [
            'level' => $level,
            'message' => $message,
            'timestamp' => date('c'),
            'context' => $context,
        ];

        if (count($this->buffer) >= 10) {
            $this->flush();
        }
    }

    private function flush(): void
    {
        if (empty($this->buffer)) return;

        $ch = curl_init($this->apiUrl);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode(['logs' => $this->buffer]),
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $this->apiToken,
            ],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 5,
        ]);
        curl_exec($ch);
        curl_close($ch);

        $this->buffer = [];
    }
}

Initialize the Error Handler

// bootstrap.php or similar
require_once 'ErrorHandler.php';

$errorHandler = new ErrorHandler(
    'https://logs.401clicks.com/api/v1/logs/batch',
    getenv('CLICKS_API_TOKEN')
);

Using Monolog (Recommended)

For more sophisticated logging, use Monolog:

composer require monolog/monolog
<?php

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class HttpHandler extends \Monolog\Handler\AbstractProcessingHandler
{
    private string $url;
    private string $token;
    private array $buffer = [];

    public function __construct(string $url, string $token, $level = Logger::DEBUG)
    {
        parent::__construct($level, true);
        $this->url = $url;
        $this->token = $token;

        register_shutdown_function([$this, 'flush']);
    }

    protected function write(array $record): void
    {
        $this->buffer[] = [
            'level' => strtolower($record['level_name']),
            'message' => $record['message'],
            'timestamp' => $record['datetime']->format('c'),
            'context' => $record['context'],
        ];

        if (count($this->buffer) >= 10) {
            $this->flush();
        }
    }

    public function flush(): void
    {
        if (empty($this->buffer)) return;
        // Send logs via HTTP (same as above)
        $this->buffer = [];
    }
}

$logger = new Logger('app');
$logger->pushHandler(new StreamHandler('php://stderr', Logger::DEBUG));
$logger->pushHandler(new HttpHandler(
    'https://logs.401clicks.com/api/v1/logs/batch',
    getenv('CLICKS_API_TOKEN')
));

Best Practices

  • Never display errors in production: Always set display_errors to 0
  • Log everything: Set error_reporting to E_ALL
  • Include context: Add request info, user IDs, etc.
  • Use appropriate levels: Don't log everything as "error"
  • Buffer and batch: Reduce HTTP overhead
  • Handle fatal errors: Register a shutdown function

Conclusion

PHP error handling requires more setup than some languages, but once configured properly, you'll catch every error that occurs in your application. Combine with a central logging service for searchable, actionable error data.

A

Admin

Published on August 6, 2025