feat: error-codes in flash messages to reference log entries
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Generates and tracks error reference codes for user-facing error messages.
|
||||
*
|
||||
* Produces deterministic codes based on the current request ID, allowing users
|
||||
* to report error codes to support staff who can then search logs by the code.
|
||||
* The code format is E-XXXXXXXX (8 uppercase alphanumeric characters in base36).
|
||||
*/
|
||||
class ErrorCodeService
|
||||
{
|
||||
private ?string $errorCode = null;
|
||||
private bool $hasError = false;
|
||||
|
||||
public function __construct(
|
||||
private readonly RequestStack $requestStack,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error code for the current request.
|
||||
*
|
||||
* Generates a deterministic code from the request ID on first call and
|
||||
* caches it for subsequent calls. Sequence suffixes like _1, _2 are
|
||||
* stripped before hashing to ensure all requests in a sequence share
|
||||
* the same error code.
|
||||
*
|
||||
* @return string|null The error code or null if no request context exists
|
||||
*/
|
||||
public function getErrorCode(): ?string
|
||||
{
|
||||
if (null !== $this->errorCode) {
|
||||
return $this->errorCode;
|
||||
}
|
||||
|
||||
$request = $this->requestStack->getMainRequest();
|
||||
if (null === $request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$requestId = $request->attributes->get('request_id');
|
||||
if (null === $requestId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->errorCode = $this->generateCode((string) $requestId);
|
||||
|
||||
return $this->errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks that an error has occurred during this request.
|
||||
*
|
||||
* Called by the ErrorCodeProcessor when a log entry with ERROR level
|
||||
* or higher is created.
|
||||
*/
|
||||
public function markErrorOccurred(): void
|
||||
{
|
||||
$this->hasError = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an error has been logged during this request.
|
||||
*
|
||||
* @return bool True if an error has been logged, false otherwise
|
||||
*/
|
||||
public function hasError(): bool
|
||||
{
|
||||
return $this->hasError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an error code from a request ID.
|
||||
*
|
||||
* Strips sequence suffixes, hashes the normalized ID, and converts
|
||||
* to a base36 representation prefixed with E-.
|
||||
*
|
||||
* @param string $requestId The request ID to generate a code from
|
||||
*
|
||||
* @return string The generated error code in format E-XXXXXXXX
|
||||
*/
|
||||
private function generateCode(string $requestId): string
|
||||
{
|
||||
$normalizedId = preg_replace('/_\d+$/', '', $requestId);
|
||||
$hash = md5((string) $normalizedId);
|
||||
$hexPart = substr($hash, 0, 12);
|
||||
$decimal = hexdec($hexPart);
|
||||
$base36 = strtoupper(base_convert((string) $decimal, 10, 36));
|
||||
$padded = str_pad($base36, 8, '0', STR_PAD_LEFT);
|
||||
|
||||
return 'E-'.substr($padded, 0, 8);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user