roadrunner-php/app-logger
Middleware-style PSR-3 logger for RoadRunner PHP apps. Captures request/worker lifecycle events and forwards them to your logger of choice, helping you standardize structured logs and simplify debugging in long-running workers.
Start by installing the package via Composer:
composer require roadrunner-php/app-logger
Then, in your bootstrap or application entry point (e.g., app.php), initialize the logger using the provided factory:
use Spiral\RoadRunner\Worker;
use Spiral\RoadRunnerAppLogger\LoggerFactory;
$worker = Worker::create();
$logger = (new LoggerFactory($worker))->create();
// Optionally inject environment-aware context (e.g., request ID)
$logger = $logger->withContext(['request_id' => uniqid('req_', true)]);
Your first use case will likely be logging an inbound request’s outcome:
$logger->info('Request processed', [
'method' => 'GET',
'uri' => '/health',
'status' => 200,
'duration_ms' => 12.3,
]);
Check out examples/ in the repo if present—otherwise, the minimal API surface means most patterns follow directly from the constructor and withContext() pattern.
Per-Request Logging Scope: In middleware (e.g., PSR-15 or custom), clone the logger with request-specific context:
$logger = $baseLogger->withContext([
'request_id' => $request->getAttribute('request_id') ?? BinGuid::uuid4(),
'user_id' => $request->getAttribute('user_id'),
]);
This avoids cross-request log pollution in persistent workers.
Structured Logging with PSR-3: Since it implements Psr\Log\LoggerInterface, integrate with existing PSR-3–compatible log consumers (e.g., Monolog handlers) if needed, but prefer native output:
$logger->info('User logged in', ['ip' => $ip, 'agent' => $userAgent]);
Request Lifecycle Hooks: Emit logs at key phases (dispatch → controller → response):
$logger->debug('Dispatching controller', ['controller' => $controllerClass, 'action' => $action]);
$logger->debug('Response generated', ['status' => $response->getStatusCode()]);
Integration with RoadRunner Workers: Pass worker to LoggerFactory so it inherits RoadRunner’s worker ID and PID—critical for debugging in multi-worker deployments.
No Dependency Injection Needed: For simplicity, inject the logger into services as a lazy singleton—its context is immutable, and withContext() returns a new instance.
Immutable Context: withContext() returns a new logger instance; forgetting to reassign (e.g., $logger = $logger->withContext(...)) leads to stale context. Always reassign.
Worker Lifecycle ≠ Request Lifecycle: Because workers persist, avoid logging mutable global state. Use per-request cloning explicitly.
No Default Handlers: This package doesn’t attach any log handlers. You control output. To log to stderr (RoadRunner’s default), call error_log() via a custom handler, or rely on RoadRunner’s built-in log collection (ensure roadrunner local or .rr.yaml has logs.mode = 'debug').
Context Overwrites, Not Merges: New keys in withContext() overwrite existing keys—not merge deeply. Use associative arrays carefully if extending context.
Performance: The logger is lightweight, but avoid creating new loggers inside tight loops. Cache and clone only per-request.
Testing: Mock LoggerInterface in unit tests. For integration tests, use LoggerFactory with a mock Worker (e.g., Spiral\RoadRunner\Testing\Worker::createMock()).
Missing Request ID? RoadRunner doesn’t auto-inject one—define a middleware to generate/store a X-Request-Id header value in the context for end-to-end traceability.
How can I help you explore Laravel packages today?