Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Raygun4Php Laravel Package

mindscape/raygun4php

Raygun4PHP is a PHP client for Raygun error tracking. It captures exceptions and errors, sends stack traces, tags, custom data, and user context, and works with Laravel or plain PHP apps to provide real-time crash reporting and diagnostics.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require mindscape/raygun4php

Requirements are ext-curl, ext-json, and a reasonably modern PHP (7.4/8.x depending on version). There is no Laravel service provider, facade, config file, or publishable assets — you wire it yourself.

Where to look first

  1. src/RaygunClient.php — the entire public API surface (SendException, SendError, SetUser, SetTags, SetVersion, SetCustomData, SetCustomDataProvider, SetFilterParams, SetAsyncSending/DisableAsyncSending, SetProxy, ignoreErrors).
  2. src/RaygunRequestMessage.php — this is the file that decides what gets scraped out of $_SERVER, $_POST, $_COOKIE, $_SESSION, and request headers. Read it before you ship to production; it is effectively your privacy/data-leak audit.
  3. src/RaygunMessage.php — the JSON envelope sent to https://api.raygun.io/entries.

First use case: report unhandled exceptions

Bind a singleton so the client is configured once per process:

// app/Providers/AppServiceProvider.php
use Raygun4Php\RaygunClient;

$this->app->scoped(RaygunClient::class, function () {
    $client = new RaygunClient(config('services.raygun.key'));
    $client->SetVersion(config('app.version', 'dev'));
    $client->SetTags([config('app.env'), gethostname()]);
    $client->SetFilterParams(config('services.raygun.filter', []));

    if (app()->runningInConsole() || app()->runningUnitTests()) {
        $client->DisableAsyncSending();
    }

    return $client;
});

Laravel 11+ (bootstrap/app.php):

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->report(function (Throwable $e) {
        try {
            app(\Raygun4Php\RaygunClient::class)->SendException($e);
        } catch (\Throwable $ignored) {
            // never let the reporter break the request
        }
    });
})

Laravel 10 and below: call the same block from App\Exceptions\Handler::report() after parent::report($e).

That alone gets you stack traces, request data, and environment info in the Raygun dashboard within one deploy. Everything else is enrichment.

Implementation Patterns

Attach the authenticated user in middleware, not the provider. The client is stateful and, under Octane, a container singleton outlives the request.

public function handle($request, Closure $next)
{
    $client = app(RaygunClient::class);

    if ($user = $request->user()) {
        $client->SetUser([
            'Identifier'   => (string) $user->getKey(),
            'IsAnonymous'  => false,
            'Email'        => $user->email,
            'FullName'     => $user->name,
            'FirstName'    => Str::before($user->name, ' '),
        ]);
    }

    return $next($request);
}

Use scoped() bindings (or forgetInstance in a terminating middleware) so user/tags/custom data don't bleed between requests under Octane or in tests.

Lazy context via SetCustomDataProvider. Pass a closure; it is invoked at send time, so the client always reports the current state instead of whatever existed when it was bound:

$client->SetCustomDataProvider(function () {
    return [
        'tenant_id' => tenant()?->id,
        'route'     => request()->route()?->getName(),
        'queue'     => app()->runningInConsole() ? 'cli' : null,
    ];
});

Rich tags drive grouping. Tags are plain strings and are how you'll slice issues (e.g. ['api', 'billing', 'v2']). Set static tags once at bind time, then push per-feature tags right before a manual SendError.

Manual reporting for non-exception events. SendError($message, $tags, $customData) takes a string — use it for handled-but-serious conditions (payment gateway declines, webhook signature failures, third-party API non-200s) that you don't want to throw:

app(RaygunClient::class)->SendError(
    'Stripe webhook signature mismatch',
    ['billing', 'webhook'],
    ['event_id' => $payload['id'] ?? null]
);

Version tracking. Feed SetVersion(config('app.version')) (or a git SHA injected at build). Raygun's "Deployment" view is only useful if this changes per release — it's the fastest way to correlate a spike to a deploy.

Console/queue jobs. Async curl handles won't reliably flush at the end of a long-lived worker. Disable async in console mode (as above) and add job context manually — SendException will otherwise capture CLI-shaped "request" data that tells you nothing:

$client->SetCustomData(['job' => $job::class, 'attempt' => $job->attempts(), 'queue' => $job->queue]);

Wrap it if you plan to test. The package has no HTTP abstraction, so you can't Http::fake() it. Put a small ErrorReporter interface over it and bind a no-op fake in testing, or subclass RaygunClient and override the protected Send() method. This is also your escape hatch if you later migrate to Sentry/Bugsnag.

Filtering. Build SetFilterParams([...]) from a config array that includes Laravel's usual suspects (password, password_confirmation, token, secret, api_key, authorization, card_number, cvv) plus your domain-specific keys. Do this in the same place you bind the client so it can't be forgotten.

Gotchas and Tips

  • Async sending is deferred to __destruct. The client opens curl_multi handles and completes them on destruction. That's fine in PHP-FPM, fragile under exit()/dd(), and unreliable in queue workers and Octane. Call DisableAsyncSending() in CLI contexts, and know that fatal errors (E_ERROR, memory exhaustion) frequently never reach Raygun because the process dies before __destruct runs. If fatal-error coverage matters, add a register_shutdown_function that inspects error_get_last() and reports synchronously.
  • It scrapes superglobals by default. RaygunRequestMessage walks $_SERVER, $_POST, $_COOKIE, $_SESSION, and headers. That includes Authorization: Bearer …, cookies, and any secrets Laravel exposes via env into $_SERVER. SetFilterParams() masks by key name recursively — it does not scrub free-text values, so a token inside a JSON string body sails straight through.
  • SetUser keys are exact and case-sensitive: Identifier, IsAnonymous, Email, FullName, FirstName. identifier or user_id are silently dropped — a very common reason "user shows as anonymous."
  • Tags must be a flat array of strings. Nested arrays or assoc arrays don't do what you expect; you'll get unhelpful grouping.
  • Custom data must be json_encode-able. Resources, closures, circular Eloquent relations, or Binary data will cause the encode to fail and the field to vanish without an error. Always reduce to scalars: $model->only(['id', 'status']).
  • Payload size. Raygun rejects oversized entries (~128 KB). Full stack traces on deep Laravel call chains plus a fat custom-data blob will get dropped. Keep custom data under a few KB.
  • Don't double-report. If you both add a Raygun call in Handler::report() and register a Log listener or a middleware try/catch, you'll get duplicate issues and inflated counts. Pick one chokepoint. Also add $exceptions->dontReport([ValidationException::class, AuthenticationException::class, ModelNotFoundException::class]) — 404s and validation failures are noise and will burn your quota.
  • ignoreErrors() is for PHP error codes only (E_NOTICE, E_DEPRECATED…), not exception classes. Exception filtering is your job.
  • Protect the reporter itself. Network failure, DNS, a proxy, or a json_encode failure should never cascade. Wrap every send in try/catch (\Throwable).
  • Namespace change. Modern versions use Raygun4Php\RaygunClient; most blog posts and Stack Overflow answers use the global RaygunClient. If you're following a tutorial and get "class not found," that's why.
  • Outbound network. Staging/CI boxes behind firewalls will hang on curl timeouts for seconds per error. Guard it: if (app()->environment('local', 'testing')) return; at the binding level, or set a short CURLOPT_TIMEOUT via a subclass. SetProxy() exists if you route egress through a gateway.
  • Timezone skew. OccurredOn is built from PHP's default timezone. If your server is UTC and your team reads in local time, timestamps look hours off — set date_default_timezone_set() explicitly or accept UTC everywhere.
  • Throttle at your layer. There's no sampling, rate limiting, or buffering in the client. A loop that fails 10,000 times opens 10,000 curl handles. Wrap SendException in a simple per-request cap or a cache-based limiter.
  • Maintenance risk. 36 stars, 0 dependents, low activity. Pin an exact version in composer.json, read the changelog before bumping, and keep the client behind your own thin interface so swapping providers is an afternoon, not a refactor. Also confirm it stays compatible with your PHP version — this package does not track Laravel releases.
  • Local verification. There's no debug/dry-run mode. To confirm wiring without polluting the dashboard, point the client at a scratch project key or temporarily var_dump the payload from a subclassed Send().
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core