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
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user