php-http/logger-plugin
PSR-3 logger plugin for HTTPlug (php-http). Logs HTTP requests/responses made through your HTTPlug client, helping you debug and monitor outgoing traffic. Install via Composer and configure with your preferred PSR-3 logger implementation.
Install Dependencies
composer require php-http/logger-plugin
(Laravel already includes Monolog and Guzzle, so only logger-plugin may be needed.)
Basic Setup in Laravel (Updated for 1.5.0)
use Http\Client\Common\PluginClient;
use Http\Client\Plugin\LoggerPlugin;
use Illuminate\Support\Facades\Log;
// Create a Guzzle client (Laravel's Http::client() uses Guzzle under the hood)
$guzzleClient = new \GuzzleHttp\Client();
// Wrap with HTTPlug adapter and logger plugin (1.5.0 now includes URI in log context by default)
$adapter = new \Http\Adapter\Guzzle6Adapter($guzzleClient);
$loggerPlugin = new LoggerPlugin(Log::channel('single'), LoggerPlugin::FORMAT_DEBUG);
$client = new PluginClient($adapter, [$loggerPlugin]);
First Use Case: Log an API Call (Now Includes URI)
$request = new \GuzzleHttp\Psr7\Request('GET', 'https://api.example.com/data');
$response = $client->sendRequest($request);
Output: Logs will now automatically include the request URI in the log context:
[2023-10-01 12:00:00] local.DEBUG: Request URI: https://api.example.com/data | Method: GET | Status: 200
Leverage Laravel’s Http::client() with HTTPlug (URI is now logged by default):
use Http\Client\Common\PluginClient;
use Http\Adapter\Guzzle6Adapter;
$guzzleClient = new \GuzzleHttp\Client();
$adapter = new Guzzle6Adapter($guzzleClient);
$loggerPlugin = new LoggerPlugin(Log::channel('stack'), LoggerPlugin::FORMAT_TIDY);
// Bind to Laravel container (optional)
app()->bind('httplug.logger.client', function () use ($adapter, $loggerPlugin) {
return new PluginClient($adapter, [$loggerPlugin]);
});
public function fetchData()
{
$client = app('httplug.logger.client');
$response = $client->sendRequest(new \GuzzleHttp\Psr7\Request('GET', 'https://api.example.com'));
return json_decode($response->getBody(), true);
}
Log Output:
[2023-10-01 12:00:00] local.DEBUG: Request URI: https://api.example.com | Method: GET | Status: 200
$loggerPlugin = new LoggerPlugin(Log::channel('single'), LoggerPlugin::FORMAT_DEBUG);
$client = new PluginClient($adapter, [$loggerPlugin]);
// Override handleRequest to filter logs (URI is now part of $context)
$filteredClient = new class($client) implements \Http\Client\Plugin {
public function __construct(private $client) {}
public function handleRequest($request, $next, \Http\Promise\PromiseInterface $promise) {
return $next($request)->then(
function ($response) {
if ($response->getStatusCode() >= 400) {
$this->client->getPlugin(LoggerPlugin::class)
->logRequest($request, $response);
// URI is now automatically logged via LoggerPlugin
}
return $response;
}
);
}
};
$loggerPlugin = new LoggerPlugin(Log::channel('single'), LoggerPlugin::FORMAT_TIDY);
$client = new PluginClient($adapter, [$loggerPlugin]);
// Middleware to log specific routes (URI is now part of $request)
$client->addPlugin(new class implements \Http\Client\Plugin {
public function handleRequest($request, $next, \Http\Promise\PromiseInterface $promise) {
if (str_contains($request->getUri(), 'critical-api')) {
return $next($request)->then(
function ($response) use ($request) {
Log::debug('Critical API call', [
'uri' => $request->getUri(), // URI now auto-included
'status' => $response->getStatusCode(),
]);
return $response;
}
);
}
return $next($request);
}
});
Log::channel('single')->pushProcessor(function ($record) {
$record['extra']['user_id'] = auth()->id();
$record['extra']['request_id'] = request()->header('X-Request-ID');
$record['extra']['request_uri'] = $record['context']['request']['uri'] ?? null; // URI now auto-captured
return $record;
});
$loggerPlugin = new LoggerPlugin(Log::channel('single'), function ($message, array $context) {
return sprintf(
"[HTTP] %s | URI: %s | Method: %s | Status: %s",
$context['timestamp'] ?? now()->toIso8601String(),
$context['request']['uri'] ?? 'UNKNOWN', // URI now auto-included
$context['request']['method'] ?? 'UNKNOWN',
$context['response']['status'] ?? 'UNKNOWN'
);
});
$loggerPlugin = new LoggerPlugin(Log::channel('single'), LoggerPlugin::FORMAT_DEBUG);
$loggerPlugin->setMasking(function ($body) {
return preg_replace('/"password":"[^"]+"/', '"password":"[FILTERED]"', $body);
});
Note: The URI is not masked and remains visible in logs.
Double Logging (URI now part of context)
LoggerPlugin both log HTTP calls, logs may duplicate including URIs.// Disable Laravel's default HTTP logging middleware if present
Performance Bottlenecks (URI now auto-logged)
$loggerPlugin = new LoggerPlugin(Log::channel('single'), false); // Disable body logging (URI still logged)
Circular Dependencies (URI now part of request context)
$loggerPlugin->setRequestIdGenerator(function () {
return uniqid('http_', true);
});
Logger Plugin Not Triggering (URI now auto-included)
Log::channel('single') exists).storage/logs/laravel.log permissions).LoggerPlugin::FORMAT_DEBUG for verbose output (URI now auto-included).Log Plugin Execution (URI now visible) Add debug logs to the plugin itself:
$loggerPlugin = new LoggerPlugin(Log::channel('single'), LoggerPlugin::FORMAT_DEBUG);
$loggerPlugin->setLogger(new class(Log::channel('single')) implements \Psr\Log\LoggerInterface {
public function debug($message, array $context = []) {
Log::debug("[LoggerPlugin] $message | URI: " . ($context['request']['uri'] ?? 'N/A'), $context);
$this->logger->debug($message, $context);
}
// Implement other PSR-3 methods...
});
Inspect HTTPlug Client (URI now part of context) Dump the client’s plugins to verify the logger is attached:
dd($client->getPlugins()); // Should include LoggerPlugin (URI now auto-logged)
Check Guzzle Middleware (URI now auto-captured)
How can I help you explore Laravel packages today?