PSR-3 structured logging with request tracking and performance monitoring for PSR-15 applications
Automatically add request IDs and performance metrics to all logs in PSR-15 middleware applications. Zero configuration, framework-agnostic, production-ready.
- π Request ID Tracking - Unique ID for every request, added to all logs automatically
- β±οΈ Performance Monitoring - Track execution time and memory usage for requests and operations
- π― PSR-15 Middleware - Drop-in middleware for automatic logging
- π Structured Logs - JSON-compatible context for easy parsing and analysis
- π§ PSR-3 Compatible - Works with any PSR-3 logger (Monolog, etc.)
- π Response Headers - Optionally adds
X-Request-IDheader to responses - π Zero Configuration - Works out-of-the-box with sensible defaults
- π¨ Customizable - Full control over processors and logging behavior
composer require methorz/http-request-loggeruse MethorZ\RequestLogger\Middleware\LoggingMiddleware;
// Mezzio / Laminas
$app->pipe(new LoggingMiddleware($logger));
// Any PSR-15 application
$dispatcher->pipe(new LoggingMiddleware($logger));That's it! Every request will now have:
- Unique request ID in all logs
- Request start/end logging
- Automatic performance metrics
- Exception logging with context
Adds a unique request ID to all log records:
use MethorZ\RequestLogger\Processor\RequestIdProcessor;
use Monolog\Logger;
$logger = new Logger('app');
$logger->pushProcessor(new RequestIdProcessor());
$logger->info('User logged in', ['user_id' => 123]);
// Log output includes: {"message": "User logged in", "extra": {"request_id": "req_..."}"}Custom Request ID:
$processor = new RequestIdProcessor('custom-request-id');Retrieve Request ID:
$requestId = $processor->getRequestId();Track performance metrics for operations:
use MethorZ\RequestLogger\Logger\PerformanceLogger;
$perfLogger = new PerformanceLogger($logger);
// Method 1: Start/End
$perfLogger->start('database-query');
$users = $repository->findAll();
$perfLogger->end('database-query', ['query' => 'SELECT * FROM users']);
// Method 2: Measure callable
$result = $perfLogger->measure('api-call', function () use ($apiClient) {
return $apiClient->fetchData();
}, ['endpoint' => '/api/users']);
// Method 3: Log request performance
$startTime = microtime(true);
// ... handle request ...
$perfLogger->logRequest($startTime, [
'method' => 'POST',
'uri' => '/api/users',
'status' => 201,
]);Logged Performance Metrics:
{
"message": "Performance: database-query",
"context": {
"operation": "database-query",
"duration_ms": 45.23,
"memory_peak_mb": 12.5,
"query": "SELECT * FROM users"
}
}PSR-15 middleware for automatic request logging:
use MethorZ\RequestLogger\Middleware\LoggingMiddleware;
use MethorZ\RequestLogger\Processor\RequestIdProcessor;
// Basic usage
$middleware = new LoggingMiddleware($logger);
// Custom request ID processor
$processor = new RequestIdProcessor('custom-id');
$middleware = new LoggingMiddleware($logger, $processor);
// Disable X-Request-ID header
$middleware = new LoggingMiddleware($logger, null, false);What It Logs:
Request start:
{
"message": "Request started",
"context": {
"method": "POST",
"uri": "https://example.com/api/users",
"request_id": "req_673e5c2f47a0c1.23456789"
}
}Request completion:
{
"message": "Request completed",
"context": {
"method": "POST",
"uri": "https://example.com/api/users",
"status": 201,
"duration_ms": 127.45,
"memory_peak_mb": 15.2,
"request_id": "req_673e5c2f47a0c1.23456789"
}
}Exception:
{
"message": "Request failed with exception",
"context": {
"method": "POST",
"uri": "https://example.com/api/users",
"exception": "User not found",
"exception_class": "App\\Exception\\NotFoundException",
"request_id": "req_673e5c2f47a0c1.23456789"
}
}Correlate logs across services using request IDs:
// Service A
$requestId = $processor->getRequestId();
$client->request('POST', '/api/service-b', [
'headers' => ['X-Request-ID' => $requestId],
]);
// Service B receives request with same ID
// All logs from both services share the request IDTrack slow operations:
$perfLogger->start('slow-operation');
$result = $this->processData($largeDataset);
$perfLogger->end('slow-operation', [
'records_processed' => count($largeDataset),
], LogLevel::WARNING); // Use WARNING level if it's slowFind all logs for a specific request:
# Filter logs by request ID
cat app.log | jq 'select(.extra.request_id == "req_673e5c2f47a0c1.23456789")'// config/autoload/logging.global.php
use MethorZ\RequestLogger\Middleware\LoggingMiddleware;
use MethorZ\RequestLogger\Processor\RequestIdProcessor;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
return [
'dependencies' => [
'factories' => [
LoggerInterface::class => function (ContainerInterface $container): Logger {
$logger = new Logger('app');
$logger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));
return $logger;
},
RequestIdProcessor::class => fn() => new RequestIdProcessor(),
LoggingMiddleware::class => function (ContainerInterface $container): LoggingMiddleware {
return new LoggingMiddleware(
$container->get(LoggerInterface::class),
$container->get(RequestIdProcessor::class),
);
},
],
],
];
// config/pipeline.php
$app->pipe(LoggingMiddleware::class);All logs follow a consistent structure for easy parsing:
{
"message": "User action",
"context": {
"user_id": 123,
"action": "login"
},
"level": 200,
"level_name": "INFO",
"channel": "app",
"datetime": "2024-11-26T10:30:45.123456+00:00",
"extra": {
"request_id": "req_673e5c2f47a0c1.23456789"
}
}Works seamlessly with Monolog:
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
use MethorZ\RequestLogger\Processor\RequestIdProcessor;
$logger = new Logger('app');
// JSON formatter for structured logs
$handler = new StreamHandler('php://stdout', Logger::INFO);
$handler->setFormatter(new JsonFormatter());
$logger->pushHandler($handler);
// Add request ID processor
$logger->pushProcessor(new RequestIdProcessor());# Run tests
composer test
# Static analysis
composer analyze
# Code style
composer cs-check
composer cs-fixThis package is part of the MethorZ HTTP middleware ecosystem:
| Package | Description |
|---|---|
| methorz/http-dto | Automatic HTTP β DTO conversion with validation |
| methorz/http-problem-details | RFC 7807 error handling middleware |
| methorz/http-cache-middleware | HTTP caching with ETag support |
| methorz/http-request-logger | Structured logging (this package) |
| methorz/openapi-generator | Automatic OpenAPI spec generation |
These packages work together seamlessly in PSR-15 applications.
MIT License. See LICENSE for details.
Contributions welcome! See CONTRIBUTING.md for guidelines.