2023-08-30 09:01:51 +00:00
|
|
|
<?php
|
|
|
|
|
|
2026-06-14 21:32:07 +02:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2023-08-30 09:01:51 +00:00
|
|
|
namespace App\Lib\Helpers;
|
|
|
|
|
|
2026-06-14 21:32:07 +02:00
|
|
|
use Sentry\Event;
|
|
|
|
|
use Sentry\ExceptionDataBag;
|
|
|
|
|
use Sentry\Stacktrace;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Provides utilities to sanitize and anonymize exceptions before reporting to Sentry.
|
|
|
|
|
*/
|
2023-08-30 09:01:51 +00:00
|
|
|
class ExceptionsHelper
|
|
|
|
|
{
|
|
|
|
|
/**
|
2026-06-14 21:32:07 +02:00
|
|
|
* Handle sentry event.
|
2023-08-30 09:01:51 +00:00
|
|
|
*/
|
2026-06-14 21:32:07 +02:00
|
|
|
public static function sentryEventHandler(Event $event): ?Event
|
2023-08-30 09:01:51 +00:00
|
|
|
{
|
|
|
|
|
$event->setModules([]);
|
|
|
|
|
$event->setOsContext(null);
|
|
|
|
|
|
2026-06-14 21:32:07 +02:00
|
|
|
$exceptions = array_map(
|
|
|
|
|
[self::class, 'getAnonymizedSentryException'],
|
|
|
|
|
$event->getExceptions()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$event->setExceptions($exceptions);
|
|
|
|
|
|
2023-08-30 09:01:51 +00:00
|
|
|
return $event;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-14 21:32:07 +02:00
|
|
|
* Anonymize Sentry Exception bag.
|
2023-08-30 09:01:51 +00:00
|
|
|
*/
|
2026-06-14 21:32:07 +02:00
|
|
|
private static function getAnonymizedSentryException(ExceptionDataBag $exceptionBag): ExceptionDataBag
|
|
|
|
|
{
|
|
|
|
|
$exceptionBag->setValue(self::getAnonymizedMessage($exceptionBag->getValue()));
|
|
|
|
|
|
|
|
|
|
if ($stackTrace = $exceptionBag->getStacktrace()) {
|
|
|
|
|
$exceptionBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
|
2023-08-30 09:01:51 +00:00
|
|
|
}
|
2026-06-14 21:32:07 +02:00
|
|
|
|
|
|
|
|
return $exceptionBag;
|
2023-08-30 09:01:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-14 21:32:07 +02:00
|
|
|
* Anonymize sentry stacktrace.
|
2023-08-30 09:01:51 +00:00
|
|
|
*/
|
2026-06-14 21:32:07 +02:00
|
|
|
private static function getAnonymizedSentryStackTrace(Stacktrace $stackTrace): Stacktrace
|
2023-08-30 09:01:51 +00:00
|
|
|
{
|
2026-06-14 21:32:07 +02:00
|
|
|
$frames = $stackTrace->getFrames();
|
|
|
|
|
if (empty($frames)) {
|
|
|
|
|
return $stackTrace;
|
2023-08-30 09:01:51 +00:00
|
|
|
}
|
2026-06-14 21:32:07 +02:00
|
|
|
|
|
|
|
|
$firstFrame = $frames[0];
|
|
|
|
|
|
|
|
|
|
$firstFrame->setPostContext([]);
|
|
|
|
|
$firstFrame->setPreContext([]);
|
|
|
|
|
$firstFrame->setContextLine('');
|
|
|
|
|
$firstFrame->setVars(self::getAnonymizedSentryParams($firstFrame->getVars()));
|
|
|
|
|
|
|
|
|
|
return new Stacktrace([$firstFrame]);
|
2023-08-30 09:01:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-14 21:32:07 +02:00
|
|
|
* Get anonymized sentry params.
|
|
|
|
|
*
|
|
|
|
|
* @template T of mixed
|
2023-08-30 09:01:51 +00:00
|
|
|
*
|
2026-10-28 08:53:34 +01:00
|
|
|
* @param array<array-key, T> $params
|
|
|
|
|
* @return array<string|T>
|
2023-08-30 09:01:51 +00:00
|
|
|
*/
|
|
|
|
|
private static function getAnonymizedSentryParams(array $params): array
|
|
|
|
|
{
|
|
|
|
|
foreach ($params as &$param) {
|
2026-06-14 21:32:07 +02:00
|
|
|
$type = gettype($param);
|
|
|
|
|
|
|
|
|
|
$param = match (true) {
|
|
|
|
|
is_object($param) => $param,
|
|
|
|
|
is_array($param) => sprintf('Array(%d)', count($param)),
|
|
|
|
|
is_int($param), is_float($param) => ucfirst($type),
|
|
|
|
|
is_string($param) => str_starts_with(trim(strtolower($param)), 'lambda')
|
|
|
|
|
? $param
|
|
|
|
|
: sprintf('String(%d)', strlen($param)),
|
|
|
|
|
default => ucfirst($type),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-30 09:01:51 +00:00
|
|
|
return $params;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-14 21:32:07 +02:00
|
|
|
* Anonymize at least Sql queries.
|
2023-08-30 09:01:51 +00:00
|
|
|
*/
|
|
|
|
|
public static function getAnonymizedMessage(string $message): string
|
|
|
|
|
{
|
2026-06-14 21:32:07 +02:00
|
|
|
$message = preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
|
|
|
|
|
|
|
|
|
|
if (($sqlPos = strpos($message, 'to use near')) !== false) {
|
|
|
|
|
$message = mb_strcut($message, 0, $sqlPos);
|
2023-08-30 09:01:51 +00:00
|
|
|
}
|
2026-06-14 21:32:07 +02:00
|
|
|
|
2023-08-30 09:01:51 +00:00
|
|
|
return $message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-14 21:32:07 +02:00
|
|
|
* Anonymize standard PHP stack trace.
|
2023-08-30 09:01:51 +00:00
|
|
|
*/
|
2026-10-03 10:01:56 +02:00
|
|
|
public static function getAnonymizedStackTrace(\Throwable $e): string
|
2023-08-30 09:01:51 +00:00
|
|
|
{
|
2026-06-14 21:32:07 +02:00
|
|
|
$projectPath = realpath(app_path('../')) ?: '';
|
|
|
|
|
$traceStr = '';
|
|
|
|
|
|
|
|
|
|
foreach ($e->getTrace() as $stage => $stack) {
|
|
|
|
|
$file = str_replace($projectPath, '', $stack['file'] ?? '');
|
|
|
|
|
$line = $stack['line'] ?? '';
|
|
|
|
|
|
|
|
|
|
$traceStr .= "#{$stage} {$file}({$line}): ";
|
|
|
|
|
|
2023-08-30 09:01:51 +00:00
|
|
|
if (isset($stack['class'])) {
|
2026-06-14 21:32:07 +02:00
|
|
|
$traceStr .= "{$stack['class']}->";
|
2023-08-30 09:01:51 +00:00
|
|
|
}
|
2026-06-14 21:32:07 +02:00
|
|
|
|
|
|
|
|
$traceStr .= "{$stack['function']}(";
|
|
|
|
|
$traceStr .= isset($stack['args']) ? self::handleArgs($stack['args']) : '';
|
|
|
|
|
$traceStr .= ")\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$nextStage = count($e->getTrace());
|
|
|
|
|
|
|
|
|
|
return $traceStr . "#{$nextStage} {main}";
|
2023-08-30 09:01:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-14 21:32:07 +02:00
|
|
|
* Handle Args.
|
2023-08-30 09:01:51 +00:00
|
|
|
*
|
2026-06-14 21:32:07 +02:00
|
|
|
* @param array<mixed> $args
|
2023-08-30 09:01:51 +00:00
|
|
|
*/
|
2026-06-14 21:32:07 +02:00
|
|
|
public static function handleArgs(array $args): string
|
2023-08-30 09:01:51 +00:00
|
|
|
{
|
2026-06-14 21:32:07 +02:00
|
|
|
$formattedArgs = array_map(function ($arg) {
|
|
|
|
|
$type = gettype($arg);
|
|
|
|
|
|
|
|
|
|
return match (true) {
|
|
|
|
|
is_object($arg) => sprintf('Object(%s)', $arg::class),
|
|
|
|
|
is_array($arg) => sprintf('Array(%d)', count($arg)),
|
|
|
|
|
is_string($arg) => sprintf('String(%d)', strlen((string) $arg)),
|
|
|
|
|
default => $type,
|
|
|
|
|
};
|
|
|
|
|
}, $args);
|
|
|
|
|
|
|
|
|
|
return implode(',', $formattedArgs);
|
2023-08-30 09:01:51 +00:00
|
|
|
}
|
|
|
|
|
}
|