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:
534
src/Framework/Monitoring/AdvancedMonitoring.php
Normal file
534
src/Framework/Monitoring/AdvancedMonitoring.php
Normal file
@@ -0,0 +1,534 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Monitoring;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Core\ValueObjects\Percentage;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
use App\Framework\DateTime\Clock;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\StateManagement\StateManager;
|
||||
|
||||
/**
|
||||
* Advanced monitoring system for circuit breakers and error boundaries
|
||||
*/
|
||||
final readonly class AdvancedMonitoring
|
||||
{
|
||||
public function __construct(
|
||||
private StateManager $stateManager,
|
||||
private Clock $clock,
|
||||
private ?Logger $logger = null,
|
||||
private MonitoringConfig $config = new MonitoringConfig(),
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Record circuit breaker metrics
|
||||
*/
|
||||
public function recordCircuitBreakerMetrics(string $circuitName, CircuitBreakerMetrics $metrics): void
|
||||
{
|
||||
$timestamp = $this->clock->time();
|
||||
$key = "monitoring:circuit:{$circuitName}:{$timestamp->toTimestamp()}";
|
||||
|
||||
$this->stateManager->setState(
|
||||
$key,
|
||||
$metrics->toArray(),
|
||||
$this->config->metricsRetention
|
||||
);
|
||||
|
||||
// Update aggregated metrics
|
||||
$this->updateAggregatedMetrics($circuitName, $metrics, $timestamp);
|
||||
|
||||
// Check for alerts
|
||||
$this->checkCircuitBreakerAlerts($circuitName, $metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record error boundary metrics
|
||||
*/
|
||||
public function recordErrorBoundaryMetrics(string $boundaryName, ErrorBoundaryMetrics $metrics): void
|
||||
{
|
||||
$timestamp = $this->clock->time();
|
||||
$key = "monitoring:boundary:{$boundaryName}:{$timestamp->toTimestamp()}";
|
||||
|
||||
$this->stateManager->setState(
|
||||
$key,
|
||||
$metrics->toArray(),
|
||||
$this->config->metricsRetention
|
||||
);
|
||||
|
||||
// Update aggregated metrics
|
||||
$this->updateBoundaryAggregatedMetrics($boundaryName, $metrics, $timestamp);
|
||||
|
||||
// Check for alerts
|
||||
$this->checkErrorBoundaryAlerts($boundaryName, $metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get circuit breaker health dashboard
|
||||
*/
|
||||
public function getCircuitBreakerDashboard(): array
|
||||
{
|
||||
$circuits = $this->getActiveCircuits();
|
||||
$dashboard = [
|
||||
'timestamp' => $this->clock->time()->toIsoString(),
|
||||
'total_circuits' => count($circuits),
|
||||
'healthy_circuits' => 0,
|
||||
'degraded_circuits' => 0,
|
||||
'failed_circuits' => 0,
|
||||
'circuits' => [],
|
||||
];
|
||||
|
||||
foreach ($circuits as $circuitName) {
|
||||
$health = $this->getCircuitHealth($circuitName);
|
||||
$dashboard['circuits'][$circuitName] = $health;
|
||||
|
||||
match ($health['status']) {
|
||||
'healthy' => $dashboard['healthy_circuits']++,
|
||||
'degraded' => $dashboard['degraded_circuits']++,
|
||||
'failed' => $dashboard['failed_circuits']++,
|
||||
};
|
||||
}
|
||||
|
||||
return $dashboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error boundary dashboard
|
||||
*/
|
||||
public function getErrorBoundaryDashboard(): array
|
||||
{
|
||||
$boundaries = $this->getActiveBoundaries();
|
||||
$dashboard = [
|
||||
'timestamp' => $this->clock->time()->toIsoString(),
|
||||
'total_boundaries' => count($boundaries),
|
||||
'healthy_boundaries' => 0,
|
||||
'degraded_boundaries' => 0,
|
||||
'failed_boundaries' => 0,
|
||||
'boundaries' => [],
|
||||
];
|
||||
|
||||
foreach ($boundaries as $boundaryName) {
|
||||
$health = $this->getBoundaryHealth($boundaryName);
|
||||
$dashboard['boundaries'][$boundaryName] = $health;
|
||||
|
||||
match ($health['status']) {
|
||||
'healthy' => $dashboard['healthy_boundaries']++,
|
||||
'degraded' => $dashboard['degraded_boundaries']++,
|
||||
'failed' => $dashboard['failed_boundaries']++,
|
||||
};
|
||||
}
|
||||
|
||||
return $dashboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system-wide health summary
|
||||
*/
|
||||
public function getSystemHealthSummary(): SystemHealthSummary
|
||||
{
|
||||
$circuitMetrics = $this->getAggregatedCircuitMetrics();
|
||||
$boundaryMetrics = $this->getAggregatedBoundaryMetrics();
|
||||
|
||||
$totalComponents = count($circuitMetrics) + count($boundaryMetrics);
|
||||
$healthyComponents = 0;
|
||||
$degradedComponents = 0;
|
||||
$failedComponents = 0;
|
||||
|
||||
foreach ($circuitMetrics as $metrics) {
|
||||
match ($this->calculateHealthStatus($metrics)) {
|
||||
'healthy' => $healthyComponents++,
|
||||
'degraded' => $degradedComponents++,
|
||||
'failed' => $failedComponents++,
|
||||
};
|
||||
}
|
||||
|
||||
foreach ($boundaryMetrics as $metrics) {
|
||||
match ($this->calculateBoundaryHealthStatus($metrics)) {
|
||||
'healthy' => $healthyComponents++,
|
||||
'degraded' => $degradedComponents++,
|
||||
'failed' => $failedComponents++,
|
||||
};
|
||||
}
|
||||
|
||||
$overallHealthScore = $totalComponents > 0
|
||||
? Percentage::fromFloat($healthyComponents / $totalComponents)
|
||||
: Percentage::fromFloat(1.0);
|
||||
|
||||
return new SystemHealthSummary(
|
||||
timestamp: $this->clock->time(),
|
||||
totalComponents: $totalComponents,
|
||||
healthyComponents: $healthyComponents,
|
||||
degradedComponents: $degradedComponents,
|
||||
failedComponents: $failedComponents,
|
||||
overallHealthScore: $overallHealthScore,
|
||||
circuitBreakerCount: count($circuitMetrics),
|
||||
errorBoundaryCount: count($boundaryMetrics),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trend analysis for circuit breaker
|
||||
*/
|
||||
public function getCircuitBreakerTrends(string $circuitName, Duration $timeWindow): array
|
||||
{
|
||||
$endTime = $this->clock->time();
|
||||
$startTime = Timestamp::fromFloat($endTime->toFloat() - $timeWindow->toSeconds());
|
||||
|
||||
$metrics = $this->getMetricsInTimeRange(
|
||||
"monitoring:circuit:{$circuitName}",
|
||||
$startTime,
|
||||
$endTime
|
||||
);
|
||||
|
||||
return $this->analyzeTrends($metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get performance analytics
|
||||
*/
|
||||
public function getPerformanceAnalytics(Duration $timeWindow): array
|
||||
{
|
||||
$endTime = $this->clock->time();
|
||||
$startTime = Timestamp::fromFloat($endTime->toFloat() - $timeWindow->toSeconds());
|
||||
|
||||
$circuits = $this->getActiveCircuits();
|
||||
$boundaries = $this->getActiveBoundaries();
|
||||
|
||||
$analytics = [
|
||||
'time_range' => [
|
||||
'start' => $startTime->toIsoString(),
|
||||
'end' => $endTime->toIsoString(),
|
||||
'duration' => $timeWindow->toHumanReadable(),
|
||||
],
|
||||
'circuit_breakers' => [],
|
||||
'error_boundaries' => [],
|
||||
'system_summary' => [
|
||||
'total_requests' => 0,
|
||||
'total_failures' => 0,
|
||||
'average_response_time' => null,
|
||||
'peak_response_time' => null,
|
||||
'error_rate' => null,
|
||||
],
|
||||
];
|
||||
|
||||
$totalRequests = 0;
|
||||
$totalFailures = 0;
|
||||
$totalResponseTime = 0;
|
||||
$peakResponseTime = Duration::zero();
|
||||
|
||||
// Analyze circuit breakers
|
||||
foreach ($circuits as $circuitName) {
|
||||
$metrics = $this->getMetricsInTimeRange(
|
||||
"monitoring:circuit:{$circuitName}",
|
||||
$startTime,
|
||||
$endTime
|
||||
);
|
||||
|
||||
$circuitAnalysis = $this->analyzeCircuitPerformance($metrics);
|
||||
$analytics['circuit_breakers'][$circuitName] = $circuitAnalysis;
|
||||
|
||||
$totalRequests += $circuitAnalysis['total_requests'];
|
||||
$totalFailures += $circuitAnalysis['total_failures'];
|
||||
$totalResponseTime += $circuitAnalysis['total_response_time_ms'];
|
||||
|
||||
if ($circuitAnalysis['peak_response_time'] > $peakResponseTime->toMilliseconds()) {
|
||||
$peakResponseTime = Duration::fromMilliseconds($circuitAnalysis['peak_response_time']);
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze error boundaries
|
||||
foreach ($boundaries as $boundaryName) {
|
||||
$metrics = $this->getMetricsInTimeRange(
|
||||
"monitoring:boundary:{$boundaryName}",
|
||||
$startTime,
|
||||
$endTime
|
||||
);
|
||||
|
||||
$boundaryAnalysis = $this->analyzeBoundaryPerformance($metrics);
|
||||
$analytics['error_boundaries'][$boundaryName] = $boundaryAnalysis;
|
||||
}
|
||||
|
||||
// System summary
|
||||
if ($totalRequests > 0) {
|
||||
$analytics['system_summary'] = [
|
||||
'total_requests' => $totalRequests,
|
||||
'total_failures' => $totalFailures,
|
||||
'average_response_time' => Duration::fromMilliseconds($totalResponseTime / $totalRequests),
|
||||
'peak_response_time' => $peakResponseTime,
|
||||
'error_rate' => Percentage::fromFloat($totalFailures / $totalRequests),
|
||||
];
|
||||
}
|
||||
|
||||
return $analytics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate monitoring alerts
|
||||
*/
|
||||
public function generateAlerts(): array
|
||||
{
|
||||
$alerts = [];
|
||||
|
||||
// Check circuit breaker alerts
|
||||
foreach ($this->getActiveCircuits() as $circuitName) {
|
||||
$health = $this->getCircuitHealth($circuitName);
|
||||
if ($health['status'] !== 'healthy') {
|
||||
$alerts[] = new MonitoringAlert(
|
||||
type: 'circuit_breaker',
|
||||
severity: $health['status'] === 'failed' ? 'critical' : 'warning',
|
||||
component: $circuitName,
|
||||
message: "Circuit breaker '{$circuitName}' is in {$health['status']} state",
|
||||
timestamp: $this->clock->time(),
|
||||
metrics: $health,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check error boundary alerts
|
||||
foreach ($this->getActiveBoundaries() as $boundaryName) {
|
||||
$health = $this->getBoundaryHealth($boundaryName);
|
||||
if ($health['status'] !== 'healthy') {
|
||||
$alerts[] = new MonitoringAlert(
|
||||
type: 'error_boundary',
|
||||
severity: $health['status'] === 'failed' ? 'critical' : 'warning',
|
||||
component: $boundaryName,
|
||||
message: "Error boundary '{$boundaryName}' is in {$health['status']} state",
|
||||
timestamp: $this->clock->time(),
|
||||
metrics: $health,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $alerts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get circuit health status
|
||||
*/
|
||||
private function getCircuitHealth(string $circuitName): array
|
||||
{
|
||||
$aggregatedKey = "monitoring:circuit:aggregated:{$circuitName}";
|
||||
$metrics = $this->stateManager->getState($aggregatedKey);
|
||||
|
||||
if ($metrics === null) {
|
||||
return [
|
||||
'status' => 'unknown',
|
||||
'last_updated' => null,
|
||||
'metrics' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$status = $this->calculateHealthStatus($metrics);
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'last_updated' => $metrics['last_updated'] ?? null,
|
||||
'metrics' => $metrics,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get boundary health status
|
||||
*/
|
||||
private function getBoundaryHealth(string $boundaryName): array
|
||||
{
|
||||
$aggregatedKey = "monitoring:boundary:aggregated:{$boundaryName}";
|
||||
$metrics = $this->stateManager->getState($aggregatedKey);
|
||||
|
||||
if ($metrics === null) {
|
||||
return [
|
||||
'status' => 'unknown',
|
||||
'last_updated' => null,
|
||||
'metrics' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$status = $this->calculateBoundaryHealthStatus($metrics);
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'last_updated' => $metrics['last_updated'] ?? null,
|
||||
'metrics' => $metrics,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate health status from metrics
|
||||
*/
|
||||
private function calculateHealthStatus(array $metrics): string
|
||||
{
|
||||
$errorRate = $metrics['error_rate'] ?? 0.0;
|
||||
$responseTime = $metrics['average_response_time'] ?? 0.0;
|
||||
|
||||
if ($errorRate > $this->config->criticalErrorRateThreshold) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
if ($errorRate > $this->config->warningErrorRateThreshold ||
|
||||
$responseTime > $this->config->slowResponseThreshold->toMilliseconds()) {
|
||||
return 'degraded';
|
||||
}
|
||||
|
||||
return 'healthy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate boundary health status from metrics
|
||||
*/
|
||||
private function calculateBoundaryHealthStatus(array $metrics): string
|
||||
{
|
||||
$fallbackRate = $metrics['fallback_rate'] ?? 0.0;
|
||||
$errorRate = $metrics['error_rate'] ?? 0.0;
|
||||
|
||||
if ($fallbackRate > $this->config->criticalFallbackRateThreshold) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
if ($fallbackRate > $this->config->warningFallbackRateThreshold ||
|
||||
$errorRate > $this->config->warningErrorRateThreshold) {
|
||||
return 'degraded';
|
||||
}
|
||||
|
||||
return 'healthy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update aggregated metrics for circuit breaker
|
||||
*/
|
||||
private function updateAggregatedMetrics(
|
||||
string $circuitName,
|
||||
CircuitBreakerMetrics $metrics,
|
||||
Timestamp $timestamp
|
||||
): void {
|
||||
$aggregatedKey = "monitoring:circuit:aggregated:{$circuitName}";
|
||||
|
||||
$this->stateManager->updateState($aggregatedKey, function ($existing) use ($metrics, $timestamp) {
|
||||
return $this->aggregateCircuitMetrics($existing, $metrics, $timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update aggregated metrics for error boundary
|
||||
*/
|
||||
private function updateBoundaryAggregatedMetrics(
|
||||
string $boundaryName,
|
||||
ErrorBoundaryMetrics $metrics,
|
||||
Timestamp $timestamp
|
||||
): void {
|
||||
$aggregatedKey = "monitoring:boundary:aggregated:{$boundaryName}";
|
||||
|
||||
$this->stateManager->updateState($aggregatedKey, function ($existing) use ($metrics, $timestamp) {
|
||||
return $this->aggregateBoundaryMetrics($existing, $metrics, $timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate circuit breaker metrics
|
||||
*/
|
||||
private function aggregateCircuitMetrics(?array $existing, CircuitBreakerMetrics $metrics, Timestamp $timestamp): array
|
||||
{
|
||||
if ($existing === null) {
|
||||
return array_merge($metrics->toArray(), [
|
||||
'first_seen' => $timestamp->toIsoString(),
|
||||
'last_updated' => $timestamp->toIsoString(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Simple aggregation - in production you'd want more sophisticated algorithms
|
||||
$totalRequests = ($existing['total_requests'] ?? 0) + $metrics->totalRequests;
|
||||
$totalFailures = ($existing['total_failures'] ?? 0) + $metrics->totalFailures;
|
||||
|
||||
return [
|
||||
'total_requests' => $totalRequests,
|
||||
'total_failures' => $totalFailures,
|
||||
'error_rate' => $totalRequests > 0 ? $totalFailures / $totalRequests : 0.0,
|
||||
'average_response_time' => $metrics->averageResponseTime->toMilliseconds(),
|
||||
'peak_response_time' => max(
|
||||
$existing['peak_response_time'] ?? 0,
|
||||
$metrics->peakResponseTime->toMilliseconds()
|
||||
),
|
||||
'first_seen' => $existing['first_seen'],
|
||||
'last_updated' => $timestamp->toIsoString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate boundary metrics
|
||||
*/
|
||||
private function aggregateBoundaryMetrics(?array $existing, ErrorBoundaryMetrics $metrics, Timestamp $timestamp): array
|
||||
{
|
||||
if ($existing === null) {
|
||||
return array_merge($metrics->toArray(), [
|
||||
'first_seen' => $timestamp->toIsoString(),
|
||||
'last_updated' => $timestamp->toIsoString(),
|
||||
]);
|
||||
}
|
||||
|
||||
$totalExecutions = ($existing['total_executions'] ?? 0) + $metrics->totalExecutions;
|
||||
$totalFallbacks = ($existing['total_fallbacks'] ?? 0) + $metrics->totalFallbacks;
|
||||
$totalErrors = ($existing['total_errors'] ?? 0) + $metrics->totalErrors;
|
||||
|
||||
return [
|
||||
'total_executions' => $totalExecutions,
|
||||
'total_fallbacks' => $totalFallbacks,
|
||||
'total_errors' => $totalErrors,
|
||||
'fallback_rate' => $totalExecutions > 0 ? $totalFallbacks / $totalExecutions : 0.0,
|
||||
'error_rate' => $totalExecutions > 0 ? $totalErrors / $totalExecutions : 0.0,
|
||||
'average_execution_time' => $metrics->averageExecutionTime->toMilliseconds(),
|
||||
'first_seen' => $existing['first_seen'],
|
||||
'last_updated' => $timestamp->toIsoString(),
|
||||
];
|
||||
}
|
||||
|
||||
// Placeholder methods - implement based on your specific needs
|
||||
private function getActiveCircuits(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function getActiveBoundaries(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function getAggregatedCircuitMetrics(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function getAggregatedBoundaryMetrics(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function getMetricsInTimeRange(string $prefix, Timestamp $start, Timestamp $end): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function analyzeTrends(array $metrics): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function analyzeCircuitPerformance(array $metrics): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function analyzeBoundaryPerformance(array $metrics): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function checkCircuitBreakerAlerts(string $name, CircuitBreakerMetrics $metrics): void
|
||||
{
|
||||
}
|
||||
|
||||
private function checkErrorBoundaryAlerts(string $name, ErrorBoundaryMetrics $metrics): void
|
||||
{
|
||||
}
|
||||
}
|
||||
149
src/Framework/Monitoring/CircuitBreakerMetrics.php
Normal file
149
src/Framework/Monitoring/CircuitBreakerMetrics.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Monitoring;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Core\ValueObjects\Percentage;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
|
||||
/**
|
||||
* Metrics value object for circuit breaker monitoring
|
||||
*/
|
||||
final readonly class CircuitBreakerMetrics
|
||||
{
|
||||
public function __construct(
|
||||
public string $circuitName,
|
||||
public int $totalRequests,
|
||||
public int $totalFailures,
|
||||
public int $totalSuccesses,
|
||||
public Duration $averageResponseTime,
|
||||
public Duration $peakResponseTime,
|
||||
public string $currentState, // OPEN, CLOSED, HALF_OPEN
|
||||
public Timestamp $lastStateChange,
|
||||
public int $consecutiveFailures,
|
||||
public ?Duration $timeUntilRetry = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get failure rate as percentage
|
||||
*/
|
||||
public function getFailureRate(): Percentage
|
||||
{
|
||||
if ($this->totalRequests === 0) {
|
||||
return Percentage::fromFloat(0.0);
|
||||
}
|
||||
|
||||
return Percentage::fromFloat($this->totalFailures / $this->totalRequests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get success rate as percentage
|
||||
*/
|
||||
public function getSuccessRate(): Percentage
|
||||
{
|
||||
if ($this->totalRequests === 0) {
|
||||
return Percentage::fromFloat(0.0);
|
||||
}
|
||||
|
||||
return Percentage::fromFloat($this->totalSuccesses / $this->totalRequests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if circuit is healthy
|
||||
*/
|
||||
public function isHealthy(): bool
|
||||
{
|
||||
return $this->currentState === 'CLOSED' &&
|
||||
$this->getFailureRate()->toFloat() < 0.05; // Less than 5% failure rate
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if circuit is degraded
|
||||
*/
|
||||
public function isDegraded(): bool
|
||||
{
|
||||
return $this->currentState === 'HALF_OPEN' ||
|
||||
($this->currentState === 'CLOSED' && $this->getFailureRate()->toFloat() >= 0.05);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if circuit is failed
|
||||
*/
|
||||
public function isFailed(): bool
|
||||
{
|
||||
return $this->currentState === 'OPEN';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get health status string
|
||||
*/
|
||||
public function getHealthStatus(): string
|
||||
{
|
||||
if ($this->isFailed()) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
if ($this->isDegraded()) {
|
||||
return 'degraded';
|
||||
}
|
||||
|
||||
return 'healthy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get time since last state change
|
||||
*/
|
||||
public function getTimeSinceLastStateChange(Timestamp $currentTime): Duration
|
||||
{
|
||||
return $this->lastStateChange->diff($currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to array for serialization
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'circuit_name' => $this->circuitName,
|
||||
'total_requests' => $this->totalRequests,
|
||||
'total_failures' => $this->totalFailures,
|
||||
'total_successes' => $this->totalSuccesses,
|
||||
'failure_rate' => $this->getFailureRate()->toFloat(),
|
||||
'success_rate' => $this->getSuccessRate()->toFloat(),
|
||||
'average_response_time_ms' => $this->averageResponseTime->toMilliseconds(),
|
||||
'peak_response_time_ms' => $this->peakResponseTime->toMilliseconds(),
|
||||
'current_state' => $this->currentState,
|
||||
'last_state_change' => $this->lastStateChange->toIsoString(),
|
||||
'consecutive_failures' => $this->consecutiveFailures,
|
||||
'time_until_retry_ms' => $this->timeUntilRetry?->toMilliseconds(),
|
||||
'health_status' => $this->getHealthStatus(),
|
||||
'is_healthy' => $this->isHealthy(),
|
||||
'is_degraded' => $this->isDegraded(),
|
||||
'is_failed' => $this->isFailed(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from array
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
return new self(
|
||||
circuitName: $data['circuit_name'],
|
||||
totalRequests: $data['total_requests'],
|
||||
totalFailures: $data['total_failures'],
|
||||
totalSuccesses: $data['total_successes'],
|
||||
averageResponseTime: Duration::fromMilliseconds($data['average_response_time_ms']),
|
||||
peakResponseTime: Duration::fromMilliseconds($data['peak_response_time_ms']),
|
||||
currentState: $data['current_state'],
|
||||
lastStateChange: Timestamp::fromFloat(strtotime($data['last_state_change'])),
|
||||
consecutiveFailures: $data['consecutive_failures'],
|
||||
timeUntilRetry: isset($data['time_until_retry_ms'])
|
||||
? Duration::fromMilliseconds($data['time_until_retry_ms'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
208
src/Framework/Monitoring/ErrorBoundaryMetrics.php
Normal file
208
src/Framework/Monitoring/ErrorBoundaryMetrics.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Monitoring;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Core\ValueObjects\Percentage;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
|
||||
/**
|
||||
* Metrics value object for error boundary monitoring
|
||||
*/
|
||||
final readonly class ErrorBoundaryMetrics
|
||||
{
|
||||
public function __construct(
|
||||
public string $boundaryName,
|
||||
public int $totalExecutions,
|
||||
public int $totalSuccesses,
|
||||
public int $totalFallbacks,
|
||||
public int $totalErrors,
|
||||
public Duration $averageExecutionTime,
|
||||
public Duration $peakExecutionTime,
|
||||
public Timestamp $lastExecution,
|
||||
public int $consecutiveFailures,
|
||||
public array $errorTypes = [], // Array of error class names with counts
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get success rate as percentage
|
||||
*/
|
||||
public function getSuccessRate(): Percentage
|
||||
{
|
||||
if ($this->totalExecutions === 0) {
|
||||
return Percentage::fromFloat(0.0);
|
||||
}
|
||||
|
||||
return Percentage::fromFloat($this->totalSuccesses / $this->totalExecutions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fallback rate as percentage
|
||||
*/
|
||||
public function getFallbackRate(): Percentage
|
||||
{
|
||||
if ($this->totalExecutions === 0) {
|
||||
return Percentage::fromFloat(0.0);
|
||||
}
|
||||
|
||||
return Percentage::fromFloat($this->totalFallbacks / $this->totalExecutions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error rate as percentage
|
||||
*/
|
||||
public function getErrorRate(): Percentage
|
||||
{
|
||||
if ($this->totalExecutions === 0) {
|
||||
return Percentage::fromFloat(0.0);
|
||||
}
|
||||
|
||||
return Percentage::fromFloat($this->totalErrors / $this->totalExecutions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if boundary is healthy
|
||||
*/
|
||||
public function isHealthy(): bool
|
||||
{
|
||||
return $this->getFallbackRate()->toFloat() < 0.1 && // Less than 10% fallback rate
|
||||
$this->getErrorRate()->toFloat() < 0.05; // Less than 5% error rate
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if boundary is degraded
|
||||
*/
|
||||
public function isDegraded(): bool
|
||||
{
|
||||
return ($this->getFallbackRate()->toFloat() >= 0.1 && $this->getFallbackRate()->toFloat() < 0.25) ||
|
||||
($this->getErrorRate()->toFloat() >= 0.05 && $this->getErrorRate()->toFloat() < 0.15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if boundary is failed
|
||||
*/
|
||||
public function isFailed(): bool
|
||||
{
|
||||
return $this->getFallbackRate()->toFloat() >= 0.25 || // More than 25% fallback rate
|
||||
$this->getErrorRate()->toFloat() >= 0.15; // More than 15% error rate
|
||||
}
|
||||
|
||||
/**
|
||||
* Get health status string
|
||||
*/
|
||||
public function getHealthStatus(): string
|
||||
{
|
||||
if ($this->isFailed()) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
if ($this->isDegraded()) {
|
||||
return 'degraded';
|
||||
}
|
||||
|
||||
return 'healthy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get time since last execution
|
||||
*/
|
||||
public function getTimeSinceLastExecution(Timestamp $currentTime): Duration
|
||||
{
|
||||
return $this->lastExecution->diff($currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get most common error type
|
||||
*/
|
||||
public function getMostCommonErrorType(): ?string
|
||||
{
|
||||
if (empty($this->errorTypes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$maxCount = 0;
|
||||
$mostCommon = null;
|
||||
|
||||
foreach ($this->errorTypes as $errorType => $count) {
|
||||
if ($count > $maxCount) {
|
||||
$maxCount = $count;
|
||||
$mostCommon = $errorType;
|
||||
}
|
||||
}
|
||||
|
||||
return $mostCommon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error type distribution
|
||||
*/
|
||||
public function getErrorTypeDistribution(): array
|
||||
{
|
||||
if (empty($this->errorTypes) || $this->totalErrors === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$distribution = [];
|
||||
foreach ($this->errorTypes as $errorType => $count) {
|
||||
$distribution[$errorType] = [
|
||||
'count' => $count,
|
||||
'percentage' => ($count / $this->totalErrors) * 100,
|
||||
];
|
||||
}
|
||||
|
||||
// Sort by count descending
|
||||
uasort($distribution, fn ($a, $b) => $b['count'] <=> $a['count']);
|
||||
|
||||
return $distribution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to array for serialization
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'boundary_name' => $this->boundaryName,
|
||||
'total_executions' => $this->totalExecutions,
|
||||
'total_successes' => $this->totalSuccesses,
|
||||
'total_fallbacks' => $this->totalFallbacks,
|
||||
'total_errors' => $this->totalErrors,
|
||||
'success_rate' => $this->getSuccessRate()->toFloat(),
|
||||
'fallback_rate' => $this->getFallbackRate()->toFloat(),
|
||||
'error_rate' => $this->getErrorRate()->toFloat(),
|
||||
'average_execution_time_ms' => $this->averageExecutionTime->toMilliseconds(),
|
||||
'peak_execution_time_ms' => $this->peakExecutionTime->toMilliseconds(),
|
||||
'last_execution' => $this->lastExecution->toIsoString(),
|
||||
'consecutive_failures' => $this->consecutiveFailures,
|
||||
'error_types' => $this->errorTypes,
|
||||
'most_common_error_type' => $this->getMostCommonErrorType(),
|
||||
'error_type_distribution' => $this->getErrorTypeDistribution(),
|
||||
'health_status' => $this->getHealthStatus(),
|
||||
'is_healthy' => $this->isHealthy(),
|
||||
'is_degraded' => $this->isDegraded(),
|
||||
'is_failed' => $this->isFailed(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from array
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
return new self(
|
||||
boundaryName: $data['boundary_name'],
|
||||
totalExecutions: $data['total_executions'],
|
||||
totalSuccesses: $data['total_successes'],
|
||||
totalFallbacks: $data['total_fallbacks'],
|
||||
totalErrors: $data['total_errors'],
|
||||
averageExecutionTime: Duration::fromMilliseconds($data['average_execution_time_ms']),
|
||||
peakExecutionTime: Duration::fromMilliseconds($data['peak_execution_time_ms']),
|
||||
lastExecution: Timestamp::fromFloat(strtotime($data['last_execution'])),
|
||||
consecutiveFailures: $data['consecutive_failures'],
|
||||
errorTypes: $data['error_types'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
87
src/Framework/Monitoring/MonitoringAlert.php
Normal file
87
src/Framework/Monitoring/MonitoringAlert.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Monitoring;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
|
||||
/**
|
||||
* Monitoring alert value object
|
||||
*/
|
||||
final readonly class MonitoringAlert
|
||||
{
|
||||
public function __construct(
|
||||
public string $type, // 'circuit_breaker', 'error_boundary', 'system'
|
||||
public string $severity, // 'info', 'warning', 'critical'
|
||||
public string $component,
|
||||
public string $message,
|
||||
public Timestamp $timestamp,
|
||||
public array $metrics = [],
|
||||
public ?string $alertId = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get alert priority (1-5, 5 being highest)
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return match ($this->severity) {
|
||||
'info' => 1,
|
||||
'warning' => 3,
|
||||
'critical' => 5,
|
||||
default => 2,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if alert requires immediate attention
|
||||
*/
|
||||
public function requiresImmediateAttention(): bool
|
||||
{
|
||||
return $this->severity === 'critical';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get alert age
|
||||
*/
|
||||
public function getAge(Timestamp $currentTime): \App\Framework\Core\ValueObjects\Duration
|
||||
{
|
||||
return $this->timestamp->diff($currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to array for serialization
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'alert_id' => $this->alertId ?? uniqid('alert_', true),
|
||||
'type' => $this->type,
|
||||
'severity' => $this->severity,
|
||||
'priority' => $this->getPriority(),
|
||||
'component' => $this->component,
|
||||
'message' => $this->message,
|
||||
'timestamp' => $this->timestamp->toIsoString(),
|
||||
'requires_immediate_attention' => $this->requiresImmediateAttention(),
|
||||
'metrics' => $this->metrics,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from array
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
return new self(
|
||||
type: $data['type'],
|
||||
severity: $data['severity'],
|
||||
component: $data['component'],
|
||||
message: $data['message'],
|
||||
timestamp: Timestamp::fromFloat(strtotime($data['timestamp'])),
|
||||
metrics: $data['metrics'] ?? [],
|
||||
alertId: $data['alert_id'] ?? null,
|
||||
);
|
||||
}
|
||||
}
|
||||
92
src/Framework/Monitoring/MonitoringConfig.php
Normal file
92
src/Framework/Monitoring/MonitoringConfig.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Monitoring;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
|
||||
/**
|
||||
* Configuration for advanced monitoring system
|
||||
*/
|
||||
final readonly class MonitoringConfig
|
||||
{
|
||||
public function __construct(
|
||||
public Duration $metricsRetention = new Duration(86400.0), // 24 hours
|
||||
public Duration $aggregationInterval = new Duration(300.0), // 5 minutes
|
||||
public Duration $slowResponseThreshold = new Duration(1.0), // 1 second
|
||||
public float $warningErrorRateThreshold = 0.05, // 5%
|
||||
public float $criticalErrorRateThreshold = 0.15, // 15%
|
||||
public float $warningFallbackRateThreshold = 0.10, // 10%
|
||||
public float $criticalFallbackRateThreshold = 0.25, // 25%
|
||||
public bool $enableRealTimeAlerts = true,
|
||||
public bool $enableTrendAnalysis = true,
|
||||
public bool $enablePerformanceAnalytics = true,
|
||||
public int $maxMetricsHistory = 10000,
|
||||
public array $alertChannels = ['log', 'webhook'],
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create production configuration
|
||||
*/
|
||||
public static function production(): self
|
||||
{
|
||||
return new self(
|
||||
metricsRetention: Duration::fromDays(7),
|
||||
aggregationInterval: Duration::fromMinutes(1),
|
||||
slowResponseThreshold: Duration::fromMilliseconds(500),
|
||||
warningErrorRateThreshold: 0.02, // 2%
|
||||
criticalErrorRateThreshold: 0.05, // 5%
|
||||
warningFallbackRateThreshold: 0.05, // 5%
|
||||
criticalFallbackRateThreshold: 0.15, // 15%
|
||||
enableRealTimeAlerts: true,
|
||||
enableTrendAnalysis: true,
|
||||
enablePerformanceAnalytics: true,
|
||||
maxMetricsHistory: 50000,
|
||||
alertChannels: ['log', 'webhook', 'email'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create development configuration
|
||||
*/
|
||||
public static function development(): self
|
||||
{
|
||||
return new self(
|
||||
metricsRetention: Duration::fromHours(4),
|
||||
aggregationInterval: Duration::fromMinutes(5),
|
||||
slowResponseThreshold: Duration::fromSeconds(2),
|
||||
warningErrorRateThreshold: 0.10, // 10%
|
||||
criticalErrorRateThreshold: 0.25, // 25%
|
||||
warningFallbackRateThreshold: 0.20, // 20%
|
||||
criticalFallbackRateThreshold: 0.40, // 40%
|
||||
enableRealTimeAlerts: false,
|
||||
enableTrendAnalysis: true,
|
||||
enablePerformanceAnalytics: true,
|
||||
maxMetricsHistory: 1000,
|
||||
alertChannels: ['log'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create high-performance configuration
|
||||
*/
|
||||
public static function highPerformance(): self
|
||||
{
|
||||
return new self(
|
||||
metricsRetention: Duration::fromHours(12),
|
||||
aggregationInterval: Duration::fromSeconds(30),
|
||||
slowResponseThreshold: Duration::fromMilliseconds(100),
|
||||
warningErrorRateThreshold: 0.001, // 0.1%
|
||||
criticalErrorRateThreshold: 0.01, // 1%
|
||||
warningFallbackRateThreshold: 0.02, // 2%
|
||||
criticalFallbackRateThreshold: 0.05, // 5%
|
||||
enableRealTimeAlerts: true,
|
||||
enableTrendAnalysis: true,
|
||||
enablePerformanceAnalytics: true,
|
||||
maxMetricsHistory: 100000,
|
||||
alertChannels: ['webhook', 'metrics'],
|
||||
);
|
||||
}
|
||||
}
|
||||
181
src/Framework/Monitoring/SystemHealthSummary.php
Normal file
181
src/Framework/Monitoring/SystemHealthSummary.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Monitoring;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Percentage;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
|
||||
/**
|
||||
* System-wide health summary value object
|
||||
*/
|
||||
final readonly class SystemHealthSummary
|
||||
{
|
||||
public function __construct(
|
||||
public Timestamp $timestamp,
|
||||
public int $totalComponents,
|
||||
public int $healthyComponents,
|
||||
public int $degradedComponents,
|
||||
public int $failedComponents,
|
||||
public Percentage $overallHealthScore,
|
||||
public int $circuitBreakerCount,
|
||||
public int $errorBoundaryCount,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get overall system status
|
||||
*/
|
||||
public function getSystemStatus(): string
|
||||
{
|
||||
if ($this->failedComponents > 0) {
|
||||
$failureRate = $this->failedComponents / $this->totalComponents;
|
||||
|
||||
if ($failureRate >= 0.5) {
|
||||
return 'critical';
|
||||
}
|
||||
|
||||
if ($failureRate >= 0.25) {
|
||||
return 'major_issues';
|
||||
}
|
||||
|
||||
return 'minor_issues';
|
||||
}
|
||||
|
||||
if ($this->degradedComponents > 0) {
|
||||
$degradedRate = $this->degradedComponents / $this->totalComponents;
|
||||
|
||||
if ($degradedRate >= 0.25) {
|
||||
return 'degraded';
|
||||
}
|
||||
|
||||
return 'mostly_healthy';
|
||||
}
|
||||
|
||||
return 'healthy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get health percentage for healthy components
|
||||
*/
|
||||
public function getHealthyPercentage(): Percentage
|
||||
{
|
||||
if ($this->totalComponents === 0) {
|
||||
return Percentage::fromFloat(0.0);
|
||||
}
|
||||
|
||||
return Percentage::fromFloat($this->healthyComponents / $this->totalComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get degraded percentage
|
||||
*/
|
||||
public function getDegradedPercentage(): Percentage
|
||||
{
|
||||
if ($this->totalComponents === 0) {
|
||||
return Percentage::fromFloat(0.0);
|
||||
}
|
||||
|
||||
return Percentage::fromFloat($this->degradedComponents / $this->totalComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get failed percentage
|
||||
*/
|
||||
public function getFailedPercentage(): Percentage
|
||||
{
|
||||
if ($this->totalComponents === 0) {
|
||||
return Percentage::fromFloat(0.0);
|
||||
}
|
||||
|
||||
return Percentage::fromFloat($this->failedComponents / $this->totalComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if system is fully operational
|
||||
*/
|
||||
public function isFullyOperational(): bool
|
||||
{
|
||||
return $this->failedComponents === 0 && $this->degradedComponents === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if system has critical issues
|
||||
*/
|
||||
public function hasCriticalIssues(): bool
|
||||
{
|
||||
return $this->getSystemStatus() === 'critical';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get severity level (0-4: healthy, mostly_healthy, degraded, minor_issues, major_issues, critical)
|
||||
*/
|
||||
public function getSeverityLevel(): int
|
||||
{
|
||||
return match ($this->getSystemStatus()) {
|
||||
'healthy' => 0,
|
||||
'mostly_healthy' => 1,
|
||||
'degraded' => 2,
|
||||
'minor_issues' => 3,
|
||||
'major_issues' => 4,
|
||||
'critical' => 5,
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get summary statistics
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
return [
|
||||
'timestamp' => $this->timestamp->toIsoString(),
|
||||
'system_status' => $this->getSystemStatus(),
|
||||
'severity_level' => $this->getSeverityLevel(),
|
||||
'is_fully_operational' => $this->isFullyOperational(),
|
||||
'has_critical_issues' => $this->hasCriticalIssues(),
|
||||
'components' => [
|
||||
'total' => $this->totalComponents,
|
||||
'healthy' => $this->healthyComponents,
|
||||
'degraded' => $this->degradedComponents,
|
||||
'failed' => $this->failedComponents,
|
||||
],
|
||||
'percentages' => [
|
||||
'healthy' => $this->getHealthyPercentage()->toFloat(),
|
||||
'degraded' => $this->getDegradedPercentage()->toFloat(),
|
||||
'failed' => $this->getFailedPercentage()->toFloat(),
|
||||
],
|
||||
'overall_health_score' => $this->overallHealthScore->toFloat(),
|
||||
'component_breakdown' => [
|
||||
'circuit_breakers' => $this->circuitBreakerCount,
|
||||
'error_boundaries' => $this->errorBoundaryCount,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to array for serialization
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->getStatistics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from array
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
return new self(
|
||||
timestamp: Timestamp::fromFloat(strtotime($data['timestamp'])),
|
||||
totalComponents: $data['components']['total'],
|
||||
healthyComponents: $data['components']['healthy'],
|
||||
degradedComponents: $data['components']['degraded'],
|
||||
failedComponents: $data['components']['failed'],
|
||||
overallHealthScore: Percentage::fromFloat($data['overall_health_score']),
|
||||
circuitBreakerCount: $data['component_breakdown']['circuit_breakers'],
|
||||
errorBoundaryCount: $data['component_breakdown']['error_boundaries'],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user