Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\ErrorBoundaries\CircuitBreaker;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
use App\Framework\ErrorBoundaries\BoundaryConfig;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\StateManagement\StateManager;
|
||||
|
||||
/**
|
||||
* Circuit breaker manager for error boundaries using generic state management
|
||||
*/
|
||||
final readonly class BoundaryCircuitBreakerManager
|
||||
{
|
||||
public function __construct(
|
||||
/**
|
||||
* @var StateManager<BoundaryCircuitBreakerState>
|
||||
*/
|
||||
private StateManager $stateManager,
|
||||
private ?Logger $logger = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if circuit is open for given boundary
|
||||
*/
|
||||
public function isCircuitOpen(string $boundaryName, BoundaryConfig $config): bool
|
||||
{
|
||||
$state = $this->getCircuitState($boundaryName);
|
||||
|
||||
if ($state->isOpen()) {
|
||||
// Check if circuit should transition to half-open
|
||||
if ($this->shouldTransitionToHalfOpen($state, $config)) {
|
||||
$newState = $state->transitionToHalfOpen();
|
||||
$this->stateManager->setState($boundaryName, $newState);
|
||||
$this->log('info', "Circuit transitioned to HALF_OPEN for boundary: {$boundaryName}");
|
||||
|
||||
return false; // Allow operations in half-open state
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a successful operation
|
||||
*/
|
||||
public function recordSuccess(string $boundaryName, BoundaryConfig $config): void
|
||||
{
|
||||
$newState = $this->stateManager->updateState(
|
||||
$boundaryName,
|
||||
function (?BoundaryCircuitBreakerState $currentState) use ($config, $boundaryName): BoundaryCircuitBreakerState {
|
||||
$state = $currentState ?? new BoundaryCircuitBreakerState();
|
||||
$newState = $state->recordSuccess();
|
||||
|
||||
// If in half-open state and enough successes, close the circuit
|
||||
if ($state->isHalfOpen() && $newState->meetsSuccessThreshold($config->successThreshold ?? 3)) {
|
||||
$newState = $newState->transitionToClosed();
|
||||
$this->log('info', "Circuit transitioned to CLOSED for boundary: {$boundaryName}");
|
||||
}
|
||||
|
||||
return $newState;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a failed operation
|
||||
*/
|
||||
public function recordFailure(string $boundaryName, BoundaryConfig $config): void
|
||||
{
|
||||
$newState = $this->stateManager->updateState(
|
||||
$boundaryName,
|
||||
function (?BoundaryCircuitBreakerState $currentState) use ($config, $boundaryName): BoundaryCircuitBreakerState {
|
||||
$state = $currentState ?? new BoundaryCircuitBreakerState();
|
||||
$newState = $state->recordFailure();
|
||||
|
||||
// Check if failure threshold is exceeded
|
||||
if ($newState->exceedsFailureThreshold($config->circuitBreakerThreshold)) {
|
||||
$newState = $newState->transitionToOpen();
|
||||
$this->log('warning', "Circuit transitioned to OPEN for boundary: {$boundaryName} after {$config->circuitBreakerThreshold} failures");
|
||||
}
|
||||
|
||||
// If in half-open state, any failure should open the circuit
|
||||
if ($state->isHalfOpen()) {
|
||||
$newState = $newState->transitionToOpen();
|
||||
$this->log('warning', "Circuit transitioned to OPEN for boundary: {$boundaryName} due to failure in HALF_OPEN state");
|
||||
}
|
||||
|
||||
return $newState;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current circuit state
|
||||
*/
|
||||
public function getCircuitState(string $boundaryName): BoundaryCircuitBreakerState
|
||||
{
|
||||
return $this->stateManager->getState($boundaryName) ?? new BoundaryCircuitBreakerState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset circuit breaker state
|
||||
*/
|
||||
public function resetCircuit(string $boundaryName): void
|
||||
{
|
||||
$newState = new BoundaryCircuitBreakerState(); // Default closed state
|
||||
$this->stateManager->setState($boundaryName, $newState);
|
||||
$this->log('info', "Circuit reset for boundary: {$boundaryName}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all circuit breaker states
|
||||
*/
|
||||
public function getAllCircuitStates(): array
|
||||
{
|
||||
$states = $this->stateManager->getAllStates();
|
||||
$result = [];
|
||||
|
||||
foreach ($states as $boundaryName => $state) {
|
||||
$result[$boundaryName] = $this->getCircuitHealth($boundaryName);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check circuit health for monitoring
|
||||
*/
|
||||
public function getCircuitHealth(string $boundaryName): array
|
||||
{
|
||||
$state = $this->getCircuitState($boundaryName);
|
||||
|
||||
return [
|
||||
'boundary_name' => $boundaryName,
|
||||
'state' => $state->state->value,
|
||||
'state_description' => $state->state->getDescription(),
|
||||
'failure_count' => $state->failureCount,
|
||||
'success_count' => $state->successCount,
|
||||
'last_failure_time' => $state->lastFailureTime?->toIsoString(),
|
||||
'opened_at' => $state->openedAt?->toIsoString(),
|
||||
'half_open_attempts' => $state->halfOpenAttempts,
|
||||
'is_healthy' => $state->isClosed(),
|
||||
'severity' => $state->state->getSeverity(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get state manager statistics
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
return $this->stateManager->getStatistics()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment half-open attempts
|
||||
*/
|
||||
public function incrementHalfOpenAttempts(string $boundaryName): void
|
||||
{
|
||||
$this->stateManager->updateState(
|
||||
$boundaryName,
|
||||
function (?BoundaryCircuitBreakerState $currentState): BoundaryCircuitBreakerState {
|
||||
$state = $currentState ?? new BoundaryCircuitBreakerState();
|
||||
|
||||
return $state->incrementHalfOpenAttempts();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function shouldTransitionToHalfOpen(BoundaryCircuitBreakerState $state, BoundaryConfig $config): bool
|
||||
{
|
||||
if (! $state->isOpen() || $state->openedAt === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timeSinceOpened = Timestamp::now()->diff($state->openedAt);
|
||||
|
||||
return $timeSinceOpened->isGreaterThan($config->circuitBreakerTimeout);
|
||||
}
|
||||
|
||||
private function log(string $level, string $message, array $context = []): void
|
||||
{
|
||||
if ($this->logger === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$context['component'] = 'BoundaryCircuitBreakerManager';
|
||||
|
||||
match ($level) {
|
||||
'debug' => $this->logger->debug("[ErrorBoundary] {$message}", $context),
|
||||
'info' => $this->logger->info("[ErrorBoundary] {$message}", $context),
|
||||
'warning' => $this->logger->warning("[ErrorBoundary] {$message}", $context),
|
||||
'error' => $this->logger->error("[ErrorBoundary] {$message}", $context),
|
||||
default => $this->logger->info("[ErrorBoundary] {$message}", $context),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\ErrorBoundaries\CircuitBreaker;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
use App\Framework\StateManagement\SerializableState;
|
||||
|
||||
/**
|
||||
* Value object representing circuit breaker state for error boundaries
|
||||
*/
|
||||
final readonly class BoundaryCircuitBreakerState implements SerializableState
|
||||
{
|
||||
public function __construct(
|
||||
public int $failureCount = 0,
|
||||
public int $successCount = 0,
|
||||
public ?Timestamp $lastFailureTime = null,
|
||||
public ?Timestamp $openedAt = null,
|
||||
public BoundaryCircuitState $state = BoundaryCircuitState::CLOSED,
|
||||
public int $halfOpenAttempts = 0,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if circuit is open
|
||||
*/
|
||||
public function isOpen(): bool
|
||||
{
|
||||
return $this->state === BoundaryCircuitState::OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if circuit is closed
|
||||
*/
|
||||
public function isClosed(): bool
|
||||
{
|
||||
return $this->state === BoundaryCircuitState::CLOSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if circuit is half-open
|
||||
*/
|
||||
public function isHalfOpen(): bool
|
||||
{
|
||||
return $this->state === BoundaryCircuitState::HALF_OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a failure and return new state
|
||||
*/
|
||||
public function recordFailure(): self
|
||||
{
|
||||
return new self(
|
||||
failureCount: $this->failureCount + 1,
|
||||
successCount: $this->successCount,
|
||||
lastFailureTime: Timestamp::now(),
|
||||
openedAt: $this->openedAt,
|
||||
state: $this->state,
|
||||
halfOpenAttempts: $this->halfOpenAttempts,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a success and return new state
|
||||
*/
|
||||
public function recordSuccess(): self
|
||||
{
|
||||
return new self(
|
||||
failureCount: 0, // Reset failure count on success
|
||||
successCount: $this->successCount + 1,
|
||||
lastFailureTime: $this->lastFailureTime,
|
||||
openedAt: null, // Reset opened time
|
||||
state: $this->state,
|
||||
halfOpenAttempts: 0, // Reset half-open attempts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to OPEN state
|
||||
*/
|
||||
public function transitionToOpen(): self
|
||||
{
|
||||
return new self(
|
||||
failureCount: $this->failureCount,
|
||||
successCount: $this->successCount,
|
||||
lastFailureTime: $this->lastFailureTime,
|
||||
openedAt: Timestamp::now(),
|
||||
state: BoundaryCircuitState::OPEN,
|
||||
halfOpenAttempts: 0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to HALF_OPEN state
|
||||
*/
|
||||
public function transitionToHalfOpen(): self
|
||||
{
|
||||
return new self(
|
||||
failureCount: $this->failureCount,
|
||||
successCount: $this->successCount,
|
||||
lastFailureTime: $this->lastFailureTime,
|
||||
openedAt: $this->openedAt,
|
||||
state: BoundaryCircuitState::HALF_OPEN,
|
||||
halfOpenAttempts: 0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to CLOSED state
|
||||
*/
|
||||
public function transitionToClosed(): self
|
||||
{
|
||||
return new self(
|
||||
failureCount: 0, // Reset failure count
|
||||
successCount: $this->successCount,
|
||||
lastFailureTime: null, // Reset last failure time
|
||||
openedAt: null, // Reset opened time
|
||||
state: BoundaryCircuitState::CLOSED,
|
||||
halfOpenAttempts: 0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment half-open attempts
|
||||
*/
|
||||
public function incrementHalfOpenAttempts(): self
|
||||
{
|
||||
return new self(
|
||||
failureCount: $this->failureCount,
|
||||
successCount: $this->successCount,
|
||||
lastFailureTime: $this->lastFailureTime,
|
||||
openedAt: $this->openedAt,
|
||||
state: $this->state,
|
||||
halfOpenAttempts: $this->halfOpenAttempts + 1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if failure threshold is exceeded
|
||||
*/
|
||||
public function exceedsFailureThreshold(int $threshold): bool
|
||||
{
|
||||
return $this->failureCount >= $threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if success threshold is met for half-open state
|
||||
*/
|
||||
public function meetsSuccessThreshold(int $threshold): bool
|
||||
{
|
||||
return $this->successCount >= $threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if half-open attempts exceed maximum
|
||||
*/
|
||||
public function exceedsHalfOpenAttempts(int $maxAttempts): bool
|
||||
{
|
||||
return $this->halfOpenAttempts >= $maxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to array for serialization
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'failure_count' => $this->failureCount,
|
||||
'success_count' => $this->successCount,
|
||||
'last_failure_time' => $this->lastFailureTime?->toFloat(),
|
||||
'opened_at' => $this->openedAt?->toFloat(),
|
||||
'state' => $this->state->value,
|
||||
'half_open_attempts' => $this->halfOpenAttempts,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from array (deserialization)
|
||||
*/
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
failureCount: $data['failure_count'] ?? 0,
|
||||
successCount: $data['success_count'] ?? 0,
|
||||
lastFailureTime: isset($data['last_failure_time'])
|
||||
? Timestamp::fromFloat($data['last_failure_time'])
|
||||
: null,
|
||||
openedAt: isset($data['opened_at'])
|
||||
? Timestamp::fromFloat($data['opened_at'])
|
||||
: null,
|
||||
state: BoundaryCircuitState::from($data['state'] ?? 'closed'),
|
||||
halfOpenAttempts: $data['half_open_attempts'] ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\ErrorBoundaries\CircuitBreaker;
|
||||
|
||||
/**
|
||||
* Circuit breaker states for error boundaries
|
||||
*/
|
||||
enum BoundaryCircuitState: string
|
||||
{
|
||||
case CLOSED = 'closed';
|
||||
case OPEN = 'open';
|
||||
case HALF_OPEN = 'half_open';
|
||||
|
||||
/**
|
||||
* Get human-readable description
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::CLOSED => 'Circuit is closed - operations are allowed',
|
||||
self::OPEN => 'Circuit is open - operations are blocked',
|
||||
self::HALF_OPEN => 'Circuit is half-open - limited operations are allowed for testing',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if operations are allowed in this state
|
||||
*/
|
||||
public function allowsOperations(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::CLOSED => true,
|
||||
self::OPEN => false,
|
||||
self::HALF_OPEN => true, // Limited operations allowed
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the severity level of this state
|
||||
*/
|
||||
public function getSeverity(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::CLOSED => 'info',
|
||||
self::OPEN => 'error',
|
||||
self::HALF_OPEN => 'warning',
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user