Table of Contents
When debugging production issues, the gap between your application logs and error monitoring can slow you down. You see an order failed, but finding the related Sentry error means searching by timestamp and guessing which error matches.
Here’s a better approach: capture the Sentry event ID and store it directly in your domain records.
The Pattern
When catching exceptions, grab the Sentry event ID and attach it to your domain object before re-throwing:
try {
$order->processPayment();
} catch (\Throwable $e) {
if (app()->bound('sentry')) {
/** @var \Sentry\State\Hub $sentry */
$sentry = app('sentry');
$eventId = $sentry->captureException($e);
if ($eventId && isset($order)) {
$relativePath = str_replace(base_path() . '/', '', $e->getFile());
$order->addNote(sprintf(
'%s: %s in %s:%s%s**[View in Sentry](https://sentry.io/issues/?query=%s)**',
$e::class,
htmlspecialchars($e->getMessage()),
$relativePath,
$e->getLine(),
str_repeat(PHP_EOL, 2),
$eventId
));
}
}
throw $e;
}
Why This Works
The key insight: capture the exception but still throw it. Sentry’s deduplication prevents duplicate events, so you get:
- A Sentry event with full stack trace and context
- A direct reference stored in your database
- Normal error handling flow (the exception still bubbles up)
Implementation Details
Relative paths: Strip base_path() from file paths to keep error messages clean and avoid exposing server directory structure.
HTML escaping: Use htmlspecialchars() on the exception message since it might be displayed in HTML contexts.
Markdown formatting: The **[View in Sentry](...)** syntax renders as a clickable link if your notes field supports markdown.
Alternative: Database Table
If you don’t have a notes/comments feature, create a dedicated error_references table:
Schema::create('error_references', function (Blueprint $table) {
$table->id();
$table->morphs('referenceable'); // order, invoice, etc.
$table->string('sentry_event_id')->index();
$table->string('exception_class');
$table->text('exception_message');
$table->string('file_path');
$table->integer('line_number');
$table->timestamp('occurred_at');
});
// Usage
ErrorReference::create([
'referenceable_id' => $order->id,
'referenceable_type' => Order::class,
'sentry_event_id' => $eventId,
'exception_class' => $e::class,
'exception_message' => $e->getMessage(),
'file_path' => str_replace(base_path() . '/', '', $e->getFile()),
'line_number' => $e->getLine(),
'occurred_at' => now(),
]);
The Payoff
When investigating a failed order, you now have a direct link to the exact Sentry event. No timestamp matching, no guessing. Click the link and you’re looking at the full stack trace with all the context Sentry captured.
This pattern works for any domain object that might fail: orders, payments, imports, scheduled jobs, API calls. Anywhere you catch exceptions and want to maintain a connection to your error monitoring.
Leave a Reply