normalizer->normalize($rawPayload); $fingerprint = $this->fingerprinter->compute($rawPayload); $now = Carbon::now(); $isNew = false; $isRegression = false; $issue = $this->findOrCreateIssue($project, $data, $fingerprint, $now, $isNew, $isRegression); $event = Event::create([ 'issue_id' => $issue->id, 'project_id' => $project->id, 'event_id' => $data['event_id'], 'level' => $data['level'], 'message' => $data['message'], 'platform' => $data['platform'], 'timestamp' => $data['timestamp'], 'server_name' => $data['server_name'], 'release' => $data['release'], 'environment' => $data['environment'], 'exception' => $data['exception'], 'breadcrumbs' => $data['breadcrumbs'], 'contexts' => $data['contexts'], 'tags' => $data['tags'], 'extra' => $data['extra'], 'user_context' => $data['user_context'], 'request_context' => $data['request_context'], 'sdk' => $data['sdk'], 'raw_payload' => $rawPayload, ]); $this->dispatchNotifications($project, $issue, $isNew, $isRegression); return $event; } private function findOrCreateIssue( Project $project, array $data, string $fingerprint, Carbon $now, bool &$isNew, bool &$isRegression, ): Issue { $issue = Issue::where('project_id', $project->id) ->where('fingerprint', $fingerprint) ->first(); if ($issue) { $isRegression = $this->updateExistingIssue($issue, $data, $now); $isNew = false; return $issue; } $isNew = true; $isRegression = false; return $this->createNewIssue($project, $data, $fingerprint, $now); } private function createNewIssue( Project $project, array $data, string $fingerprint, Carbon $now, ): Issue { $exception = $this->fingerprinter->extractPrimaryException( ['exception' => $data['exception']], ); $title = $this->buildTitle($exception, $data['message']); $culprit = $this->buildCulprit($exception); return Issue::create([ 'project_id' => $project->id, 'fingerprint' => $fingerprint, 'title' => $title, 'culprit' => $culprit, 'level' => $data['level'], 'status' => 'unresolved', 'first_seen_at' => $now, 'last_seen_at' => $now, 'times_seen' => 1, 'is_regression' => false, 'first_release' => $data['release'], 'metadata' => array_filter([ 'platform' => $data['platform'], 'sdk' => $data['sdk'], ]), ]); } private function updateExistingIssue(Issue $issue, array $data, Carbon $now): bool { $wasResolved = $issue->status === 'resolved'; $currentRelease = $data['release']; $resolvedInRelease = $issue->resolved_in_release; $issue->increment('times_seen'); $issue->update(['last_seen_at' => $now]); // Check for regression: issue was resolved, and now appears again if ($wasResolved) { // If we have release tracking and the issue was resolved in a release, // only mark as regression if the current release is different (assumed newer) if ($resolvedInRelease && $currentRelease && $currentRelease === $resolvedInRelease) { // Same release as resolved - don't mark as regression return false; } $issue->markRegression(); return true; } return false; } private function buildTitle(?array $exception, ?string $message): string { if ($exception) { $type = $exception['type'] ?? 'Error'; $value = $exception['value'] ?? ''; return str($value) ->whenEmpty(fn () => str($type)) ->prepend("{$type}: ") ->limit(255) ->toString(); } return str($message ?? 'Unknown Error')->limit(255)->toString(); } private function buildCulprit(?array $exception): ?string { if (! $exception) { return null; } $frames = $exception['stacktrace']['frames'] ?? []; $last = end($frames); if (! $last) { return null; } $file = $last['filename'] ?? $last['abs_path'] ?? '?'; $function = $last['function'] ?? null; return $function ? "{$file} in {$function}" : $file; } private function dispatchNotifications( Project $project, Issue $issue, bool $isNew, bool $isRegression, ): void { if ($isNew) { $this->notificationDispatcher->onNewIssue($project, $issue); } if ($isRegression) { $this->notificationDispatcher->onRegression($project, $issue); } $this->notificationDispatcher->checkSpike($project, $issue); } }