Skip to content

Exception tracking

Exception tracking is the core of Quiet Guard: every error your app throws is captured, grouped and tracked over its lifecycle. You reach it by opening a project in the /app panel and going to its Issues tab.

How ingestion works

Your client POSTs an exception payload to /api/v1/ingest authenticated with the project key. The server validates it, applies your team's monthly quota, then hands it to the ingestor. A successful call returns 202 Accepted with the issue's public identifier. The full payload shape is documented in the Ingestion API reference.

Each accepted exception becomes an occurrence. Occurrences are grouped into Issues.

Fingerprint grouping into Issues

Rather than store thousands of identical errors as separate rows, Quiet Guard groups them. A deterministic fingerprint is computed from:

  • the project,
  • the exception class,
  • the file (path-normalised), and
  • the line.

All occurrences sharing a fingerprint roll up into a single Issue, with an occurrence counter that concurrent reports cannot lose, the date it was first seen and the date it was last seen.

Why path normalisation? Deploy tooling often serves the app from a timestamped directory (e.g. /releases/20260628/...). The fingerprinter strips that noise, keeping the path from the first /app/, /src/ or /vendor/ marker, so the same error groups together across deploys instead of fragmenting on every release.

Occurrences

Open an Issue to see its detail page: the grouped class and message, where it happens, how many times it has been seen, and the list of individual occurrences. Each occurrence carries its own stack trace, request context, environment and release, captured at the moment it happened.

The detail page adds six things on top of the trace. A Request section shows the address, HTTP method, signed-in user and PHP/Laravel runtime captured with the latest occurrence; for a team with encrypted storage enabled, it shows a locked placeholder instead, never the address or the user, until you unlock encryption in your browser on the Encryption page. Two counters, Last 24h and Last 7 days, plus the lists of Environments and Releases the issue has touched over the last 7 days, sit beside the occurrence total. A Fourteen days of occurrences chart buckets every occurrence by day, so a spike lines up with a deployment. An In your code line names the first stack frame outside your own dependencies, so you do not have to scroll past vendor frames to find where your code broke. When the project is connected to GitHub and your plan allows it, a File history action lists the five most recent commits that touched that file, newest first. Finally, if the project follows its repository's issues, two actions, Open a GitHub issue and Link a GitHub issue, tie the anomaly to a report of the repository, and the overview then shows the linked issue with its state.

The stack trace

From version 0.3 of the clients, the Latest stack trace section reads like Laravel's own exception page:

  • the frames are listed down one side, the throw site first (Thrown here): the line that raised the exception now travels as frame zero, where PHP's trace starts at the caller and never held it;
  • runs of dependency frames fold into one row named after the package ("7 frames in laravel/framework"), which opens on click;
  • beside the list, the selected frame's detail: the call, the full path and the source snippet around the failing line, with that line marked;
  • when the project is connected to GitHub, an Open this file on GitHub link points at the file at the deployed commit: the one the occurrence's release resolves to, else the repository's default branch.

The snippet is read by the client from the file that ran: the five lines on each side of the frame's line, at most ten frames per report, and never for a dependency (any path containing /vendor/). A line that names a key of the scrub list (password, secret, token, passphrase...) or one of the words key, auth, credential, salt, private, dsn, bearer, sk_, pk_, and gives it a value, is masked whole as [scrubbed], because source code is where a hardcoded secret lives: an assignment ($secret = ..., 'api_key' => ...), from version 0.3.1 an array index named in quotes ($headers['Authorization'] = ...), a call whose first argument is a literal (setApiKey('...')), or a comparison by == or === with the name on the left ($token === '...'), whose other side may be the secret itself; !== and a literal on the left ('...' === $token) do not count. A line that only uses the name stays (Hash::check($password, $hash), and from version 0.3.1 Auth::user()), and a literal that names none of them still travels; the full rule is in the core's scrubbing. To stop sending it: MONITOR_CODE_SNIPPETS=false with the Laravel SDK, code_snippets: false in the Symfony bundle, or the code_snippets option key of the WordPress plugin. A frame without a snippet says so, whether it came from an older client or from a dependency. With encrypted storage, snippets are sealed at rest with the rest of the trace.

Resolve, ignore, reopen

Every Issue has a status:

  • Open: active, needs attention (the default for a new issue).
  • Resolved: you have fixed it; it drops out of the active list.
  • Ignored: noise you do not want to act on; kept but muted.

You move between states with the actions on the issue list and the issue detail page. Filters let you focus on, say, only open issues.

Regression reopen

If an Issue is Resolved and the same error happens again, Quiet Guard treats it as a regression: the issue is automatically flipped back to Open on the next occurrence, and an issue.reopened alert is fired to any subscribed notification channels. You do not have to babysit resolved issues, if they come back, you will hear about it.

Release → commit correlation

If an occurrence carries a release (a commit SHA, sent in the payload context.release), and the project is connected to GitHub, Quiet Guard maps that release to the exact synced commit. From the issue you can jump to the commit that shipped the broken code, closing the loop from error seen in production to the change that caused it.

Note: release correlation only resolves when the release value is a hexadecimal commit SHA and the matching commit has been synced from GitHub.

Triage workflow

The project's Issues tab opens as a triage view: the status filter defaults to Open, which the active-filters line above the rows says; the other filters sit behind the funnel button and apply as soon as you pick them. The 24h column counts each issue's occurrences over the last day, spot the burst happening right now, not last week's noisy issue. Select several issues to resolve or ignore them in one action.

Triage across every project

An agency answers for a portfolio, not for one application. The Issues entry in the sidebar gathers every issue of every project the team owns, with a Project column and a Project filter, and it opens sorted by the last 24 hours rather than by recency: the question it answers is what is burning now, not what arrived last. Filters, assignment and the bulk resolve or ignore all work there exactly as they do inside a project, across projects.

The per-project tab is unchanged and stays the right place once you know which project you want. The sidebar entry carries a badge with the team's open count.

Assigning issues

Assign an issue to a team member from the list or from the issue page. The Assigned to me filter turns the list into a personal worklist, perfect for agencies splitting projects across developers.

Logs around an event

When your plan includes application logs, the issue page shows the logs recorded on the same project and environment within ±5 minutes of the latest occurrence, the "why" often hides there. Each line links to the full log entry.

Press ⌘K / Ctrl+K anywhere in the panel to find issues by exception class, message or file, and projects by name. With encrypted storage, sealed messages are not searchable (by design): search relies on the clear metadata.

What's next

You are reading the Quiet Guard v1.0 documentation.