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:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -15,11 +15,12 @@ final readonly class CacheMiddleware implements QueryMiddleware
private int $defaultTtlSeconds = 300, // 5 Minuten
private bool $enabled = true,
private array $cacheableOperations = ['query', 'queryOne', 'queryColumn', 'queryScalar']
) {}
) {
}
public function process(QueryContext $context, callable $next): mixed
{
if (!$this->enabled || !$this->isCacheable($context)) {
if (! $this->enabled || ! $this->isCacheable($context)) {
return $next($context);
}
@@ -60,7 +61,7 @@ final readonly class CacheMiddleware implements QueryMiddleware
private function isCacheable(QueryContext $context): bool
{
// Nur bestimmte Operationen sind cacheable
if (!in_array($context->operation, $this->cacheableOperations, true)) {
if (! in_array($context->operation, $this->cacheableOperations, true)) {
return false;
}
@@ -73,7 +74,7 @@ final readonly class CacheMiddleware implements QueryMiddleware
$sql = strtoupper(trim($context->sql));
// SELECT-Statements sind normalerweise cacheable
if (!str_starts_with($sql, 'SELECT')) {
if (! str_starts_with($sql, 'SELECT')) {
return false;
}

View File

@@ -16,12 +16,12 @@ final readonly class HealthCheckMiddleware implements QueryMiddleware
private int $healthCheckInterval = 30, // Sekunden
private bool $enabled = true
) {
$this->healthChecker = new ConnectionHealthChecker();
$this->healthChecker = ConnectionHealthChecker::quick();
}
public function process(QueryContext $context, callable $next): mixed
{
if (!$this->enabled) {
if (! $this->enabled) {
return $next($context);
}
@@ -46,7 +46,7 @@ final readonly class HealthCheckMiddleware implements QueryMiddleware
{
$result = $this->healthChecker->checkHealth($context->connection);
if (!$result->isHealthy) {
if (! $result->isHealthy) {
throw new DatabaseException(
"Health check failed: {$result->message}",
0,

View File

@@ -12,6 +12,7 @@ final class MiddlewarePipeline
public function add(QueryMiddleware $middleware): self
{
$this->middleware[] = $middleware;
return $this;
}
@@ -20,6 +21,7 @@ final class MiddlewarePipeline
foreach ($middleware as $m) {
$this->add($m);
}
return $this;
}
@@ -27,14 +29,14 @@ final class MiddlewarePipeline
{
// Sortiere Middleware nach Priorität (höchste zuerst)
$sortedMiddleware = $this->middleware;
usort($sortedMiddleware, fn($a, $b) => $b->getPriority() <=> $a->getPriority());
usort($sortedMiddleware, fn ($a, $b) => $b->getPriority() <=> $a->getPriority());
// Baue die Pipeline von hinten nach vorne auf
$pipeline = $finalHandler;
for ($i = count($sortedMiddleware) - 1; $i >= 0; $i--) {
$middleware = $sortedMiddleware[$i];
$pipeline = fn(QueryContext $ctx) => $middleware->process($ctx, $pipeline);
$pipeline = fn (QueryContext $ctx) => $middleware->process($ctx, $pipeline);
}
return $pipeline($context);
@@ -52,6 +54,7 @@ final class MiddlewarePipeline
return true;
}
}
return false;
}
@@ -62,6 +65,7 @@ final class MiddlewarePipeline
return $middleware;
}
}
return null;
}
}

View File

@@ -14,12 +14,14 @@ final class QueryContext
public readonly array $parameters,
public readonly ConnectionInterface $connection,
public array $metadata = []
) {}
) {
}
public function withMetadata(string $key, mixed $value): self
{
$new = clone $this;
$new->metadata[$key] = $value;
return $new;
}

View File

@@ -4,9 +4,6 @@ declare(strict_types=1);
namespace App\Framework\Database\Middleware;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\ResultInterface;
interface QueryMiddleware
{
/**

View File

@@ -4,14 +4,17 @@ declare(strict_types=1);
namespace App\Framework\Database\Middleware;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\HealthCheck\ConnectionHealthChecker;
use App\Framework\DateTime\Timer;
final readonly class RetryMiddleware implements QueryMiddleware
{
private ConnectionHealthChecker $healthChecker;
public function __construct(
private Timer $timer,
private int $maxRetries = 3,
private int $retryDelayMs = 100,
private array $retryableExceptions = [
@@ -19,7 +22,7 @@ final readonly class RetryMiddleware implements QueryMiddleware
DatabaseException::class,
]
) {
$this->healthChecker = new ConnectionHealthChecker();
$this->healthChecker = new ConnectionHealthChecker($timer);
}
public function process(QueryContext $context, callable $next): mixed
@@ -49,7 +52,7 @@ final readonly class RetryMiddleware implements QueryMiddleware
$lastException = $e;
// Prüfe ob Exception retry-bar ist
if (!$this->isRetryableException($e)) {
if (! $this->isRetryableException($e)) {
throw $e;
}
@@ -61,14 +64,14 @@ final readonly class RetryMiddleware implements QueryMiddleware
$attempt++;
// Health Check vor Retry
if (!$this->healthChecker->checkConnectionAlive($context->connection)) {
if (! $this->healthChecker->checkConnectionAlive($context->connection)) {
// Für LazyGhost: Connection ist automatisch lazy und wird bei nächstem Zugriff neu initialisiert
// Keine explizite Aktion nötig - LazyGhost handled das automatisch
}
// Exponential Backoff
$delay = $this->retryDelayMs * (2 ** ($attempt - 1));
usleep($delay * 1000); // usleep benötigt Mikrosekunden
$this->timer->sleep(Duration::fromMilliseconds($delay));
}
}
@@ -87,9 +90,11 @@ final readonly class RetryMiddleware implements QueryMiddleware
if ($exception instanceof \PDOException) {
return $this->isPdoExceptionRetryable($exception);
}
return true;
}
}
return false;
}
@@ -104,6 +109,7 @@ final readonly class RetryMiddleware implements QueryMiddleware
];
$sqlState = $exception->getCode();
return !in_array($sqlState, $nonRetryableCodes, true);
return ! in_array($sqlState, $nonRetryableCodes, true);
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace App\Framework\Database\Middleware;
use App\Framework\Core\Events\EventDispatcherInterface;
use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\HealthCheck\ConnectionHealthChecker;
use App\Framework\DateTime\Clock;
use App\Framework\Retry\RetryManager;
use App\Framework\Retry\Strategies\ExponentialBackoffStrategy;
/**
* Database Retry-Middleware basierend auf dem neuen Retry-System
*
* Ersetzt die alte RetryMiddleware mit einheitlicher Retry-Logik
*/
final readonly class UnifiedRetryMiddleware implements QueryMiddleware
{
private RetryManager $retryManager;
public function __construct(
Clock $clock,
?EventDispatcherInterface $eventDispatcher = null,
private ConnectionHealthChecker $healthChecker = new ConnectionHealthChecker()
) {
$this->retryManager = RetryManager::create($clock)
->withStrategy(ExponentialBackoffStrategy::forDatabase())
->withContext(['middleware' => 'database']);
if ($eventDispatcher !== null) {
$this->retryManager = $this->retryManager->withEventDispatcher($eventDispatcher);
}
}
public function process(QueryContext $context, callable $next): mixed
{
// Transaktions-Commits und Rollbacks nicht wiederholen
if ($this->isTransactionCommand($context)) {
return $next($context);
}
// Health-Check vor Retry
if (! $this->healthChecker->isHealthy($context->connection)) {
throw new DatabaseException('Database connection is unhealthy');
}
// Retry-Kontext erweitern
$retryManager = $this->retryManager->withContext([
'query_type' => $this->getQueryType($context),
'connection_name' => $context->connection->getName() ?? 'default',
]);
// Retry-Operation ausführen
$result = $retryManager->execute(function () use ($context, $next) {
return $next($context);
});
return $result->getResult();
}
private function isTransactionCommand(QueryContext $context): bool
{
$sql = strtoupper(trim($context->sql));
return str_starts_with($sql, 'COMMIT') ||
str_starts_with($sql, 'ROLLBACK') ||
str_starts_with($sql, 'START TRANSACTION') ||
str_starts_with($sql, 'BEGIN');
}
private function getQueryType(QueryContext $context): string
{
$sql = strtoupper(trim($context->sql));
return match (true) {
str_starts_with($sql, 'SELECT') => 'select',
str_starts_with($sql, 'INSERT') => 'insert',
str_starts_with($sql, 'UPDATE') => 'update',
str_starts_with($sql, 'DELETE') => 'delete',
str_starts_with($sql, 'CREATE') => 'ddl',
str_starts_with($sql, 'ALTER') => 'ddl',
str_starts_with($sql, 'DROP') => 'ddl',
default => 'other'
};
}
public function getPriority(): int
{
return 100;
}
}