Client-Error-Endpoint POST /api/vitec/client-error (0072-FE / 0077-BE)
Neue PSR-15-Middleware ClientErrorMiddleware: nimmt Browser-Fehler des
React-Frontends als JSON {url, message, stack, release, userAgent}
entgegen und antwortet 204 ohne Body. Schutz: 30 Meldungen pro Minute
und IP (Sliding Window ueber TYPO3-RateLimiter, 429 + Retry-After),
8-KB-Body-Limit (413, Content-Length und tatsaechliche Bytes), kein
JSON-Objekt oder leere message = 400, GET = 405. Keine Session, keine
Cookies; der /_json-Praefix funktioniert ebenfalls.
Speicherung in der neuen Tabelle tx_vitec_clienterrorlog mit
anonymisierter IP (IPv4 letztes Oktett 0, IPv6 /48). Sichtbar als
Tabelle "Client errors (last 24 h)" im Modul VITEC SSR (mit Stack als
Aufklapper) und als Zeile "Client errors reported (24 h / 7 d)" in der
Dashboard-Kachel VITEC: SSR statistics.
CLI vitec:client-error-cleanup [--days=30] loescht Eintraege aelter als
30 Tage (taeglich per Cron).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
ff6f86b829
commit
5121eaa000
7 files changed
+360
No files matched your search
@@ -11,6 +11,7 @@ use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
@@ -73,10 +74,51 @@ final class SsrController
|
||||
'healthError' => $healthError,
|
||||
'purgeLog' => $this->rows('tx_vitec_ssrpurgelog', 30),
|
||||
'healthLog' => $this->rows('tx_vitec_ssrhealthlog', 10),
|
||||
'clientErrors' => $this->clientErrors(50),
|
||||
'clientErrorCount24h' => $this->clientErrorCount24h(),
|
||||
]);
|
||||
return $view->renderResponse('Ssr/Index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Client error reports of the last 24 hours (POST /api/vitec/client-error,
|
||||
* ClientErrorMiddleware), newest first. Rows carry a "host" (URL without
|
||||
* scheme/query) and a shortened user agent for the table; the full
|
||||
* values stay in the row for the details toggle.
|
||||
*/
|
||||
private function clientErrors(int $limit): array
|
||||
{
|
||||
try {
|
||||
$qb = $this->connectionPool->getQueryBuilderForTable('tx_vitec_clienterrorlog');
|
||||
$rows = $qb->select('*')->from('tx_vitec_clienterrorlog')
|
||||
->where($qb->expr()->gt('crdate', $qb->createNamedParameter(time() - 86400, Connection::PARAM_INT)))
|
||||
->orderBy('uid', 'DESC')->setMaxResults($limit)
|
||||
->executeQuery()->fetchAllAssociative();
|
||||
} catch (\Throwable) {
|
||||
return [];
|
||||
}
|
||||
foreach ($rows as &$row) {
|
||||
$path = (string)(parse_url((string)$row['url'], PHP_URL_PATH) ?? '');
|
||||
$row['path'] = $path !== '' ? $path : (string)$row['url'];
|
||||
$ua = (string)$row['user_agent'];
|
||||
$row['ua_short'] = mb_strlen($ua) > 60 ? mb_substr($ua, 0, 57) . '...' : $ua;
|
||||
$row['is_chunk_reload'] = str_contains(strtolower((string)$row['message']), 'chunk reload');
|
||||
}
|
||||
unset($row);
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function clientErrorCount24h(): int
|
||||
{
|
||||
try {
|
||||
return (int)$this->connectionPool->getConnectionForTable('tx_vitec_clienterrorlog')
|
||||
->executeQuery('SELECT COUNT(*) FROM tx_vitec_clienterrorlog WHERE crdate > ?', [time() - 86400])
|
||||
->fetchOne();
|
||||
} catch (\Throwable) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function purgeAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$body = (array)$request->getParsedBody();
|
||||
|
||||
Reference in new issue
Block a user