Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions src/Fetch/Concerns/ManagesDebugAndProfiling.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<?php

declare(strict_types=1);

namespace Fetch\Concerns;

use Fetch\Support\DebugInfo;
use Fetch\Support\FetchProfiler;

/**
* Trait ManagesDebugAndProfiling
*
* Provides debugging and profiling capabilities for HTTP requests.
*/
trait ManagesDebugAndProfiling
{
/**
* Whether debug mode is enabled.
*/
protected bool $debugEnabled = false;

/**
* Debug options configuration.
*
* @var array<string, mixed>
*/
protected array $debugOptions = [];

/**
* The profiler instance for performance tracking.
*/
protected ?FetchProfiler $profiler = null;

/**
* The last debug info from the most recent request.
*/
protected ?DebugInfo $lastDebugInfo = null;

/**
* Enable debug mode with specified options.
*
* @param array<string, mixed>|bool $options Debug options or true to enable all
* @return $this
*/
public function withDebug(array|bool $options = true): static
{
$this->debugEnabled = $options !== false;
$this->debugOptions = array_merge(DebugInfo::getDefaultOptions(), is_array($options) ? $options : []);

return $this;
}

/**
* Set a profiler for performance tracking.
*
* @param FetchProfiler $profiler The profiler instance
* @return $this
*/
public function withProfiler(FetchProfiler $profiler): static
{
$this->profiler = $profiler;

return $this;
}

/**
* Get the profiler instance if set.
*/
public function getProfiler(): ?FetchProfiler
{
return $this->profiler;
}

/**
* Check if debug mode is enabled.
*/
public function isDebugEnabled(): bool
{
return $this->debugEnabled;
}

/**
* Get the debug options.
*
* @return array<string, mixed>
*/
public function getDebugOptions(): array
{
return $this->debugOptions;
}

/**
* Get the last debug info from the most recent request.
*/
public function getLastDebugInfo(): ?DebugInfo
{
return $this->lastDebugInfo;
}

/**
* Create debug info for the current request.
*
* @param string $method HTTP method
* @param string $uri Request URI
* @param array<string, mixed> $options Request options
* @param \Psr\Http\Message\ResponseInterface|null $response The response
* @param array<string, float> $timings Timing information
* @param int $memoryUsage Memory usage in bytes
*/
protected function createDebugInfo(
string $method,
string $uri,
array $options,
?\Psr\Http\Message\ResponseInterface $response = null,
array $timings = [],
int $memoryUsage = 0
): DebugInfo {
$this->lastDebugInfo = DebugInfo::create(
$method,
$uri,
$options,
$response,
$timings,
$memoryUsage
);

return $this->lastDebugInfo;
}

/**
* Start profiling for a request.
*
* @param string $method HTTP method
* @param string $uri Request URI
* @return string|null The request ID if profiling is enabled
*/
protected function startProfiling(string $method, string $uri): ?string
{
if ($this->profiler === null || ! $this->profiler->isEnabled()) {
return null;
}

$requestId = FetchProfiler::generateRequestId($method, $uri);
$this->profiler->startProfile($requestId);

return $requestId;
}

/**
* Record a profiling event.
*
* @param string|null $requestId The request ID
* @param string $event The event name
*/
protected function recordProfilingEvent(?string $requestId, string $event): void
{
if ($requestId === null || $this->profiler === null) {
return;
}

$this->profiler->recordEvent($requestId, $event);
}

/**
* End profiling for a request.
*
* @param string|null $requestId The request ID
* @param int|null $statusCode HTTP status code
*/
protected function endProfiling(?string $requestId, ?int $statusCode = null): void
{
if ($requestId === null || $this->profiler === null) {
return;
}

$this->profiler->endProfile($requestId, $statusCode);
}
}
45 changes: 44 additions & 1 deletion src/Fetch/Concerns/PerformsHttpRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,17 +344,55 @@ protected function executeSyncRequest(
array $options,
float $startTime,
): ResponseInterface {
return $this->retryRequest(function () use ($method, $uri, $options, $startTime): ResponseInterface {
// Start profiling if profiler is available
$requestId = null;
if (method_exists($this, 'startProfiling')) {
$requestId = $this->startProfiling($method, $uri);
}

// Track memory for debugging
$startMemory = memory_get_usage(true);

return $this->retryRequest(function () use ($method, $uri, $options, $startTime, $requestId, $startMemory): ResponseInterface {
try {
// Record request sent event for profiling
if ($requestId !== null && method_exists($this, 'recordProfilingEvent')) {
$this->recordProfilingEvent($requestId, 'request_sent');
}

// Send the request to Guzzle
$psrResponse = $this->getHttpClient()->request($method, $uri, $options);

// Record response received event for profiling
if ($requestId !== null && method_exists($this, 'recordProfilingEvent')) {
$this->recordProfilingEvent($requestId, 'response_start');
}

// Calculate duration
$duration = microtime(true) - $startTime;

// Create our response object
$response = Response::createFromBase($psrResponse);

// End profiling
if ($requestId !== null && method_exists($this, 'endProfiling')) {
$this->endProfiling($requestId, $response->getStatusCode());
}

// Create debug info if debug mode is enabled
if (method_exists($this, 'isDebugEnabled') && $this->isDebugEnabled()) {
$memoryUsage = memory_get_usage(true) - $startMemory;
$timings = [
'total_time' => round($duration * 1000, 3),
'start_time' => $startTime,
'end_time' => microtime(true),
];

if (method_exists($this, 'createDebugInfo')) {
$this->createDebugInfo($method, $uri, $options, $response, $timings, $memoryUsage);
}
}

// Trigger retry on configured retryable status codes
if (in_array($response->getStatusCode(), $this->getRetryableStatusCodes(), true)) {
$psrRequest = new GuzzleRequest($method, $uri, $options['headers'] ?? []);
Expand All @@ -369,6 +407,11 @@ protected function executeSyncRequest(

return $response;
} catch (GuzzleException $e) {
// End profiling with error
if ($requestId !== null && method_exists($this, 'endProfiling')) {
$this->endProfiling($requestId, null);
}

// Normalize to Fetch RequestException to participate in retry logic
if ($e instanceof GuzzleRequestException) {
$req = $e->getRequest();
Expand Down
2 changes: 2 additions & 0 deletions src/Fetch/Http/ClientHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Fetch\Concerns\ConfiguresRequests;
use Fetch\Concerns\HandlesMocking;
use Fetch\Concerns\HandlesUris;
use Fetch\Concerns\ManagesDebugAndProfiling;
use Fetch\Concerns\ManagesPromises;
use Fetch\Concerns\ManagesRetries;
use Fetch\Concerns\PerformsHttpRequests;
Expand All @@ -28,6 +29,7 @@ class ClientHandler implements ClientHandlerInterface
use ConfiguresRequests,
HandlesMocking,
HandlesUris,
ManagesDebugAndProfiling,
ManagesPromises,
ManagesRetries,
PerformsHttpRequests;
Expand Down
38 changes: 38 additions & 0 deletions src/Fetch/Interfaces/ClientHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -535,4 +535,42 @@ public function getRetryableExceptions(): array;
* @return static New client handler instance
*/
public function withClonedOptions(array $options): self;

/**
* Enable debug mode with specified options.
*
* @param array<string, mixed>|bool $options Debug options or true to enable all
* @return $this
*/
public function withDebug(array|bool $options = true): self;

/**
* Set a profiler for performance tracking.
*
* @param \Fetch\Support\FetchProfiler $profiler The profiler instance
* @return $this
*/
public function withProfiler(\Fetch\Support\FetchProfiler $profiler): self;

/**
* Get the profiler instance if set.
*/
public function getProfiler(): ?\Fetch\Support\FetchProfiler;

/**
* Check if debug mode is enabled.
*/
public function isDebugEnabled(): bool;

/**
* Get the debug options.
*
* @return array<string, mixed>
*/
public function getDebugOptions(): array;

/**
* Get the last debug info from the most recent request.
*/
public function getLastDebugInfo(): ?\Fetch\Support\DebugInfo;
}
Loading
Loading