fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled

- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -0,0 +1,335 @@
<?php
declare(strict_types=1);
namespace App\Framework\ExceptionHandling\Serialization;
use App\Framework\ExceptionHandling\Context\ExceptionContextData;
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
use App\Framework\Logging\ValueObjects\ExceptionContext as LoggingExceptionContext;
use Throwable;
/**
* Exception Serializer
*
* Provides standardized serialization of exceptions with context for:
* - JSON serialization
* - Structured log format
* - Storage format
*
* Integrates with ExceptionContextProvider to include external context.
*/
final readonly class ExceptionSerializer
{
public function __construct(
private ?ExceptionContextProvider $contextProvider = null
) {
}
/**
* Serialize exception to JSON
*
* @param Throwable $exception
* @param array<string, mixed> $options Serialization options
* @return string JSON string
*/
public function toJson(Throwable $exception, array $options = []): string
{
$data = $this->toArray($exception, $options);
return json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
/**
* Serialize exception to array
*
* @param Throwable $exception
* @param array<string, mixed> $options Serialization options
* @return array<string, mixed>
*/
public function toArray(Throwable $exception, array $options = []): array
{
$includeStackTrace = $options['include_stack_trace'] ?? true;
$includePrevious = $options['include_previous'] ?? true;
$includeContext = $options['include_context'] ?? true;
$compact = $options['compact'] ?? false;
$data = [
'exception' => $this->serializeException($exception, $includeStackTrace, $includePrevious, $compact),
];
// Add external context if available
if ($includeContext && $this->contextProvider !== null) {
$context = $this->contextProvider->get($exception);
if ($context !== null) {
$data['context'] = $this->serializeContext($context, $compact);
}
}
// Add metadata
$data['timestamp'] = (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM);
$data['serializer_version'] = '1.0';
return $data;
}
/**
* Serialize exception to structured log format
*
* Optimized for logging systems (e.g., ELK, Splunk).
*
* @param Throwable $exception
* @return array<string, mixed>
*/
public function toLogFormat(Throwable $exception): array
{
$context = $this->contextProvider?->get($exception);
$logData = [
'exception_class' => get_class($exception),
'exception_message' => $exception->getMessage(),
'exception_code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'timestamp' => (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM),
];
// Add context data if available
if ($context !== null) {
$logData = array_merge($logData, [
'operation' => $context->operation,
'component' => $context->component,
'user_id' => $context->userId,
'request_id' => $this->serializeValue($context->requestId),
'session_id' => $this->serializeValue($context->sessionId),
'client_ip' => $this->serializeValue($context->clientIp),
'user_agent' => $this->serializeValue($context->userAgent),
'tags' => $context->tags,
'auditable' => $context->auditable,
'audit_level' => $context->auditLevel,
]);
// Add domain data
if (!empty($context->data)) {
$logData['domain_data'] = $context->data;
}
// Add metadata
if (!empty($context->metadata)) {
$logData['metadata'] = $context->metadata;
}
}
// Add stack trace (limited depth for logs)
$logData['stack_trace'] = $this->serializeStackTrace($exception, maxDepth: 10);
// Add previous exception if available
if ($exception->getPrevious() !== null) {
$logData['previous_exception'] = [
'class' => get_class($exception->getPrevious()),
'message' => $exception->getPrevious()->getMessage(),
'code' => $exception->getPrevious()->getCode(),
];
}
return $logData;
}
/**
* Serialize exception to storage format
*
* Optimized for database storage (compact, indexed fields).
*
* @param Throwable $exception
* @return array<string, mixed>
*/
public function toStorageFormat(Throwable $exception): array
{
$context = $this->contextProvider?->get($exception);
$storageData = [
'exception_class' => get_class($exception),
'exception_message' => $exception->getMessage(),
'exception_code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'occurred_at' => $context?->occurredAt->format(\DateTimeInterface::ATOM) ?? (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM),
];
// Add context data (indexed fields)
if ($context !== null) {
$storageData = array_merge($storageData, [
'operation' => $context->operation,
'component' => $context->component,
'user_id' => $context->userId,
'request_id' => $this->serializeValue($context->requestId),
'session_id' => $this->serializeValue($context->sessionId),
'client_ip' => $this->serializeValue($context->clientIp),
'user_agent' => $this->serializeValue($context->userAgent),
'tags' => json_encode($context->tags, JSON_THROW_ON_ERROR),
'auditable' => $context->auditable,
'audit_level' => $context->auditLevel,
]);
// Serialize complex data as JSON
$storageData['data'] = json_encode($context->data, JSON_THROW_ON_ERROR);
$storageData['debug'] = json_encode($context->debug, JSON_THROW_ON_ERROR);
$storageData['metadata'] = json_encode($context->metadata, JSON_THROW_ON_ERROR);
}
// Serialize stack trace as JSON (full depth)
$storageData['stack_trace'] = json_encode($this->serializeStackTrace($exception), JSON_THROW_ON_ERROR);
// Serialize previous exception chain as JSON
if ($exception->getPrevious() !== null) {
$storageData['previous_exception'] = json_encode(
$this->serializeException($exception->getPrevious(), includeStackTrace: true, includePrevious: true, compact: false),
JSON_THROW_ON_ERROR
);
}
return $storageData;
}
/**
* Serialize exception details
*
* @param Throwable $exception
* @param bool $includeStackTrace
* @param bool $includePrevious
* @param bool $compact
* @return array<string, mixed>
*/
private function serializeException(
Throwable $exception,
bool $includeStackTrace,
bool $includePrevious,
bool $compact
): array {
$data = [
'class' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
];
if ($includeStackTrace) {
$data['stack_trace'] = $this->serializeStackTrace($exception, compact: $compact);
}
if ($includePrevious && $exception->getPrevious() !== null) {
$data['previous'] = $this->serializeException(
$exception->getPrevious(),
$includeStackTrace,
$includePrevious,
$compact
);
}
return $data;
}
/**
* Serialize stack trace
*
* @param Throwable $exception
* @param int|null $maxDepth Maximum depth (null = unlimited)
* @param bool $compact Compact format
* @return array<int, array<string, mixed>>
*/
private function serializeStackTrace(Throwable $exception, ?int $maxDepth = null, bool $compact = false): array
{
$trace = $exception->getTrace();
if ($maxDepth !== null) {
$trace = array_slice($trace, 0, $maxDepth);
}
$serialized = [];
foreach ($trace as $frame) {
if ($compact) {
$serialized[] = [
'file' => $frame['file'] ?? 'unknown',
'line' => $frame['line'] ?? 0,
'function' => $frame['function'] ?? 'unknown',
];
} else {
$serialized[] = [
'file' => $frame['file'] ?? 'unknown',
'line' => $frame['line'] ?? 0,
'function' => $frame['function'] ?? 'unknown',
'class' => $frame['class'] ?? null,
'type' => $frame['type'] ?? null,
'args' => $this->serializeArgs($frame['args'] ?? []),
];
}
}
return $serialized;
}
/**
* Serialize function arguments (safe for serialization)
*
* @param array<int, mixed> $args
* @return array<int, mixed>
*/
private function serializeArgs(array $args): array
{
$serialized = [];
foreach ($args as $arg) {
if (is_object($arg)) {
$serialized[] = [
'type' => get_class($arg),
'value' => method_exists($arg, '__toString') ? (string) $arg : '[object]',
];
} elseif (is_array($arg)) {
$serialized[] = '[array:' . count($arg) . ']';
} elseif (is_resource($arg)) {
$serialized[] = '[resource]';
} else {
$serialized[] = $arg;
}
}
return $serialized;
}
/**
* Serialize context data
*
* @param ExceptionContextData $context
* @param bool $compact
* @return array<string, mixed>
*/
private function serializeContext(ExceptionContextData $context, bool $compact): array
{
return $context->toArray();
}
/**
* Serialize value (Value Object or primitive)
*
* @param mixed $value
* @return string|null
*/
private function serializeValue(mixed $value): ?string
{
if ($value === null) {
return null;
}
if (is_string($value)) {
return $value;
}
if (is_object($value) && method_exists($value, 'toString')) {
return $value->toString();
}
if (is_object($value) && property_exists($value, 'value')) {
return (string) $value->value;
}
return (string) $value;
}
}