Error Context
Add structured debugging information to exceptions using context, tags, and metadata. This makes error tracking, logging, and monitoring significantly more effective.
Overview
Section titled “Overview”The HasErrorContext trait provides three methods for attaching debugging information:
withContext()- Structured contextual data about the errorwithTags()- Categorical labels for filtering and groupingwithMetadata()- Detailed technical debugging information
Adding Context
Section titled “Adding Context”Use withContext() to attach relevant data about the circumstances of the error.
Basic Usage
Section titled “Basic Usage”use App\Exceptions\PaymentFailedException;
PaymentFailedException::insufficientFunds() ->withContext([ 'user_id' => $user->id, 'amount' => $amount, 'balance' => $account->balance, ]) ->throwIf($account->balance < $amount);Request Context
Section titled “Request Context”UnauthorizedException::invalidToken() ->withContext([ 'ip_address' => request()->ip(), 'user_agent' => request()->userAgent(), 'session_id' => session()->getId(), 'attempted_action' => 'access_admin_panel', ]) ->throwIf(!$token->isValid());Business Context
Section titled “Business Context”OrderCannotBeCancelledException::alreadyShipped() ->withContext([ 'order_id' => $order->id, 'status' => $order->status, 'shipped_at' => $order->shipped_at, 'customer_id' => $order->customer_id, ]) ->throwIf($order->status === 'shipped');Multiple Context Calls
Section titled “Multiple Context Calls”Context data merges across multiple calls:
$exception = DatabaseException::queryFailed() ->withContext(['query' => $sql]) ->withContext(['duration_ms' => $executionTime]);
// Result: ['query' => $sql, 'duration_ms' => $executionTime]Later values override earlier ones:
$exception = PaymentException::failed() ->withContext(['status' => 'pending']) ->withContext(['status' => 'failed']);
// Result: ['status' => 'failed']Adding Tags
Section titled “Adding Tags”Use withTags() to categorize exceptions for filtering in logging and monitoring systems.
Basic Tagging
Section titled “Basic Tagging”PaymentException::gatewayTimeout() ->withTags(['payment', 'stripe', 'critical']) ->throwIf($timeout);Severity Tags
Section titled “Severity Tags”// Critical errorsDatabaseException::connectionLost() ->withTags(['critical', 'database', 'infrastructure']) ->throwIf($connectionLost);
// Warning-level errorsRateLimitException::approaching() ->withTags(['warning', 'rate-limit']) ->throwIf($usage > 0.9);System Tags
Section titled “System Tags”// Tag by affected systemApiException::timeout() ->withTags(['external-api', 'payment-gateway', 'stripe']) ->throwIf($timedOut);
// Tag by error categoryValidationException::invalidInput() ->withTags(['validation', 'user-input', 'form-submission']) ->throwIf(!$valid);Multiple Tag Calls
Section titled “Multiple Tag Calls”Tags accumulate across calls:
$exception = PaymentException::failed() ->withTags(['payment']) ->withTags(['stripe', 'critical']);
// Result: ['payment', 'stripe', 'critical']Adding Metadata
Section titled “Adding Metadata”Use withMetadata() for detailed technical debugging information that’s too verbose for regular context.
API Response Metadata
Section titled “API Response Metadata”try { $response = $client->post('/charge', $data);} catch (RequestException $e) { throw PaymentException::gatewayError() ->wrap($e) ->withContext(['user_id' => $user->id, 'amount' => $amount]) ->withTags(['payment', 'stripe']) ->withMetadata([ 'request_body' => $data, 'response_status' => $e->getResponse()?->getStatusCode(), 'response_body' => $e->getResponse()?->getBody()?->getContents(), 'response_headers' => $e->getResponse()?->getHeaders(), 'duration_ms' => $duration, ]);}Database Query Metadata
Section titled “Database Query Metadata”try { DB::statement($sql, $bindings);} catch (QueryException $e) { throw DatabaseException::queryFailed() ->wrap($e) ->withContext(['table' => 'orders']) ->withMetadata([ 'query' => $sql, 'bindings' => $bindings, 'execution_time' => $time, 'connection' => config('database.default'), ]);}Multiple Metadata Calls
Section titled “Multiple Metadata Calls”Metadata merges like context:
$exception = ApiException::failed() ->withMetadata(['request' => $requestData]) ->withMetadata(['response' => $responseData]);
// Result: ['request' => $requestData, 'response' => $responseData]Retrieving Context Data
Section titled “Retrieving Context Data”Access attached data using getter methods:
try { PaymentException::failed() ->withContext(['user_id' => 123]) ->withTags(['payment']) ->withMetadata(['debug' => 'info']) ->throwIf(true);} catch (PaymentException $e) { $context = $e->getContext(); // ['user_id' => 123] $tags = $e->getTags(); // ['payment'] $metadata = $e->getMetadata(); // ['debug' => 'info']}Using Context in Exception Handlers
Section titled “Using Context in Exception Handlers”Laravel Exception Handler
Section titled “Laravel Exception Handler”namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;use Cline\Throw\Concerns\HasErrorContext;
class Handler extends ExceptionHandler{ public function report(Throwable $exception) { if ($this->hasErrorContext($exception)) { // Log context data Log::error($exception->getMessage(), [ 'context' => $exception->getContext(), 'tags' => $exception->getTags(), 'metadata' => $exception->getMetadata(), ]);
// Send to monitoring service if (in_array('critical', $exception->getTags())) { $this->reportCriticalError($exception); } }
parent::report($exception); }
private function hasErrorContext(Throwable $exception): bool { return method_exists($exception, 'getContext'); }
private function reportCriticalError(Throwable $exception): void { // Send to Sentry, Bugsnag, etc. app('sentry')->captureException($exception, [ 'extra' => [ 'context' => $exception->getContext(), 'tags' => $exception->getTags(), 'metadata' => $exception->getMetadata(), ], ]); }}Filtering by Tags
Section titled “Filtering by Tags”public function report(Throwable $exception){ if (!method_exists($exception, 'getTags')) { return parent::report($exception); }
$tags = $exception->getTags();
// Route to different handlers based on tags if (in_array('payment', $tags)) { $this->notifyFinanceTeam($exception); }
if (in_array('database', $tags)) { $this->notifyDatabaseTeam($exception); }
if (in_array('critical', $tags)) { $this->sendUrgentAlert($exception); }
parent::report($exception);}Real-World Patterns
Section titled “Real-World Patterns”Complete Error Context
Section titled “Complete Error Context”// Comprehensive error reportingPaymentFailedException::gatewayTimeout() ->withContext([ 'user_id' => auth()->id(), 'order_id' => $order->id, 'amount' => $amount, 'currency' => 'USD', ]) ->withTags(['payment', 'stripe', 'critical', 'timeout']) ->withMetadata([ 'gateway_request' => $requestData, 'gateway_response' => $responseData, 'attempt_number' => 3, 'total_duration_ms' => 15000, ]) ->throwIf($timeout);Multi-Tenant Context
Section titled “Multi-Tenant Context”TenantException::quotaExceeded() ->withContext([ 'tenant_id' => $tenant->id, 'tenant_name' => $tenant->name, 'current_usage' => $usage, 'quota_limit' => $limit, ]) ->withTags(['multi-tenant', 'quota', 'billing']) ->throwIf($usage >= $limit);Feature Flag Context
Section titled “Feature Flag Context”FeatureNotAvailableException::disabled() ->withContext([ 'feature' => 'advanced_analytics', 'user_id' => $user->id, 'user_plan' => $user->plan, ]) ->withTags(['feature-flag', 'access-control']) ->throwUnless(Feature::enabled('advanced_analytics'));Best Practices
Section titled “Best Practices”- Context for what happened - User IDs, resource IDs, relevant values
- Tags for categorization - System, severity, error type
- Metadata for debugging - Full request/response data, detailed technical info
- Sanitize sensitive data - Don’t include passwords, tokens, or PII
- Be consistent - Use similar context keys across your application
- Tag strategically - Use tags that help you filter in monitoring tools
Next Steps
Section titled “Next Steps”- Learn about Error Wrapping for exception chains
- See Base Exceptions for categorizing errors
- Explore Assertions for the
ensure()helper