PHP cookbook · Beginner

Add structured logging with request IDs in PHP

Make PHP logs searchable and correlate all log lines from one request.

Approach

Use Monolog with a JSON formatter writing to stderr (collected by Docker/systemd), add a processor that injects a request ID (from X-Request-ID or generated), and log with context arrays instead of string interpolation. Return the request ID in a response header so users can report it.

Solution

<?php
declare(strict_types=1);

use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger;
use Monolog\LogRecord;

$requestId = preg_match('/^[a-zA-Z0-9-]{8,64}$/', $_SERVER['HTTP_X_REQUEST_ID'] ?? '')
    ? $_SERVER['HTTP_X_REQUEST_ID'] : bin2hex(random_bytes(8));
header("X-Request-ID: {$requestId}");

$handler = new StreamHandler('php://stderr', Level::Info);
$handler->setFormatter(new JsonFormatter());

$logger = new Logger('shop', [$handler]);
$logger->pushProcessor(function (LogRecord $record) use ($requestId): LogRecord {
    return $record->with(extra: $record->extra + [
        'request_id' => $requestId,
        'path' => $_SERVER['REQUEST_URI'] ?? null,
    ]);
});

$logger->info('Order placed', ['order_id' => 1234, 'total_cents' => 4999]);
// {"message":"Order placed","context":{"order_id":1234,"total_cents":4999},"level_name":"INFO","extra":{"request_id":"9f2c...","path":"/checkout"},...}

Pitfalls

  • Logging passwords, tokens or full card numbers.
  • Trusting arbitrary incoming request IDs (validate format/length).
  • Writing log files inside containers instead of stdout/stderr.

Variations

  • Symfony/Laravel: configure Monolog channels in framework config and add the processor there.
  • Ship logs to OpenTelemetry with the OTel PHP SDK for trace correlation.

Further reading: Monolog usage

Reference: https://github.com/Seldaek/monolog/blob/main/doc/01-usage.md

More PHP recipes