csa/guzzle-stopwatch-middleware
Adds a Guzzle middleware that measures request/response timing with a stopwatch, making it easy to profile HTTP calls in your app. Install via Composer and plug into your Guzzle handler stack to track durations per request.
Installation
composer require csa/guzzle-stopwatch-middleware
Add the middleware to your Guzzle client stack:
use CSA\GuzzleStopwatchMiddleware\StopwatchMiddleware;
$stack = HandlerStack::create();
$stack->push(StopwatchMiddleware::class);
$client = new Client(['handler' => $stack]);
First Use Case Inject the client into a service and make a request:
$response = $client->get('https://api.example.com/data');
The middleware will automatically log request duration to monolog (if configured) or output to stderr.
config/guzzle-stopwatch.php (if published via service provider).config/logging.php.StopwatchMiddleware for customization points (e.g., onRequest, onResponse).Middleware Injection Add to your Guzzle client stack in a service provider or config:
$stack->push(StopwatchMiddleware::class, [
'logger' => $this->app->make('logger'), // Optional: Inject custom logger
'log_level' => 'debug', // Optional: Override log level
]);
Request/Response Hooks Extend the middleware for custom logic:
class CustomStopwatchMiddleware extends StopwatchMiddleware
{
protected function onRequest(RequestInterface $request)
{
// Pre-request logic (e.g., tagging)
$request->setMetadata('custom_tag', 'value');
}
protected function onResponse(ResponseInterface $response, $duration)
{
// Post-response logic (e.g., alerting)
if ($duration > 1.0) {
$this->logger->warning("Slow request: {$duration}s");
}
}
}
Integration with Laravel HTTP Clients Use with Laravel’s HTTP client facade:
$client = app(\Illuminate\Http\Client\PendingRequest::class);
$client->withOptions(['handler' => $stack]);
Conditional Logging Filter requests by URI or method:
$stack->push(StopwatchMiddleware::class, [
'filter' => function ($request) {
return strpos($request->getUri(), '/admin') !== false;
}
]);
Performance Metrics Store durations in a database or cache for analytics:
$this->onResponse(function (ResponseInterface $response, $duration) {
cache()->put("request_duration_{$response->getRequest()->getUri()}", $duration, 3600);
});
Async Processing Use with Guzzle’s async requests:
$promises = [];
foreach ($urls as $url) {
$promises[] = $client->getAsync($url)->then(
function ($response) use ($url) {
$this->logger->info("Async request to {$url} completed");
}
);
}
Logger Misconfiguration
config/logging.php includes a valid channel (e.g., single or stack).stderr:
$stack->push(StopwatchMiddleware::class, ['logger' => new \Monolog\Logger('test', [new \Monolog\Handler\StreamHandler('php://stderr')])]);
Middleware Order
$stack->push(StopwatchMiddleware::class); // Earliest position
Async Requests
getAsync) may not log durations if not handled explicitly.then() or wait() to ensure completion:
$client->getAsync($url)->then(function () {
// Log duration via custom logic
})->wait();
Check Middleware Execution
Override onRequest/onResponse to verify timing:
protected function onRequest(RequestInterface $request)
{
$this->logger->debug('Stopwatch started for: ' . $request->getUri());
}
Log Duration Manually For complex workflows, manually track time:
$start = microtime(true);
$response = $client->get($url);
$this->logger->info("Manual duration: " . (microtime(true) - $start));
Custom Logging Extend the middleware to support structured logging (e.g., ELK):
protected function logDuration($duration, $request, $response)
{
$this->logger->info([
'event' => 'http.request',
'duration_ms' => $duration * 1000,
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'status' => $response->getStatusCode(),
]);
}
Metrics Integration Publish durations to Prometheus or Datadog:
use Prometheus\CollectorRegistry;
protected function onResponse(ResponseInterface $response, $duration)
{
$registry->getOrRegisterCounter('http_requests_total', 'HTTP requests')
->inc();
$registry->getOrRegisterGauge('http_request_duration_seconds', 'HTTP request duration')
->set($duration, ['uri' => $response->getRequest()->getUri()]);
}
Conditional Middleware Dynamically enable/disable based on environment:
if (app()->environment('production')) {
$stack->push(StopwatchMiddleware::class);
}
Default Log Level
The middleware defaults to debug. Change it in the constructor or config:
$stack->push(StopwatchMiddleware::class, ['log_level' => 'info']);
Time Unit Durations are logged in seconds. Multiply by 1000 for milliseconds in logs:
$this->logger->info(sprintf("Duration: %.3fms", $duration * 1000));
How can I help you explore Laravel packages today?