dennissmink/lara-bug
LaraBug adds lightweight, self-hosted error tracking to Laravel: it captures exceptions with stack traces and request/user context, stores them for review in a dashboard, and can alert you by email or Slack when something breaks.
LaraBug is a SaaS-backed exception reporter: your app POSTs throwables to the LaraBug service, which groups them into deduplicated "bugs," tracks occurrence counts, and notifies you (mail/Slack/etc.). It is not a local log replacement — think of it as Sentry/Bugsnag-lite wired into Laravel's exception pipeline.
Minimum setup
composer require dennissmink/lara-bug
php artisan vendor:publish output):
php artisan vendor:publish --provider="LaraBug\LaraBugServiceProvider"
.env (names mirror config/larabug.php, typically LOGIN_KEY and PROJECT_KEY).app/Exceptions/Handler.php:
public function report(Throwable $exception)
{
if ($this->shouldReport($exception) && app()->bound('larabug')) {
app('larabug')->handleException($exception);
}
parent::report($exception);
}
Laravel 11+ in bootstrap/app.php:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (Throwable $e) {
app(\LaraBug\LaraBug::class)->handleException($e);
});
})
Where to look first
config/larabug.php — the single source of truth for keys, enabled environments, and any ignore lists. Read it before touching code.LaraBug\LaraBug class (the handleException entry point) and the service provider — this tells you exactly what is bound in the container and what the transport does.First use case: production-only reporting. Leave environments at ['production'], get one real (or forced) exception flowing end-to-end, then expand to staging once you trust the noise level.
Central handler integration (default). Report once from the framework's report() path, guarded by shouldReport() and app()->bound('larabug'). This catches everything Laravel already considers reportable and respects $dontReport. Avoid scattering manual handleException() calls at catch sites unless you have a specific reason — duplicate reporting is easy to introduce.
Manual reporting for handled exceptions. When you catch and recover from an error but still want visibility:
try {
$this->chargeCustomer($order);
} catch (PaymentFailed $e) {
report($e); // goes through the handler, LaraBug included
// recover...
}
Prefer report() over calling LaraBug directly; it keeps filtering logic in one place.
Enrich with deploy/user context. Incidents are far more actionable with the release SHA, environment, and affected user attached. If the package exposes user/context setters, use them; otherwise wrap reporting in your own reporter that appends context to the exception message or a log line before handing off:
app(\LaraBug\LaraBug::class)->handleException(
new RuntimeException($e->getMessage().' [release='.config('app.release').']', 0, $e)
);
Filter aggressively. Point LaraBug at the exceptions you will actually act on. Trim $dontReport for expected domain exceptions (validation, auth, 404s), and keep a package-level ignore list if the config supports one. Noisy reporters get muted by the team within a week.
Environment and sampling discipline. production and maybe staging only. If the config supports sampling or throttling, set it — a tight loop throwing thousands of exceptions per minute shouldn't hammer the API (or your response times).
Decouple from the request path. If reporting happens synchronously via Guzzle, wrap the remote call so a LaraBug outage cannot turn a handled exception into a 500. Options: queue the report in a job, or wrap handleException() in try/catch with short timeouts. Verify against your installed version whether the package already queues.
Multi-app / multi-tenant. One LaraBug project per application is the sane default. For multi-tenant SaaS, either keep a single project and tag exceptions with tenant id, or swap project_key per tenant if the package makes that configurable at runtime — don't share a project across unrelated apps.
Keep a local fallback. Remote reporting is an addition, not a replacement. Retain parent::report() / Laravel logging so storage/logs/laravel.log still has the trace when the network or the SaaS is down.
Tests and CI. Bind a fake or disable reporting in the test environment (APP_ENV=testing outside the enabled list is usually enough). Never let the PHPUnit suite make live HTTP calls to the dashboard. In deploy pipelines, inject LOGIN_KEY/PROJECT_KEY as secrets per environment and re-run config:cache.
environments. The most common "it's not reporting" cause is APP_ENV not being in the enabled list (or APP_ENV being local on a box you thought was production). Check this before debugging anything else.config/larabug.php reads env at cache-build time. After changing keys or environments, run php artisan config:clear (and config:cache in deploy). Stale cached config with old keys looks like an auth failure.handleException() call, or calling both the facade and the handler, produces duplicates. Pick one integration point.report(Exception) vs report(Throwable), and Laravel 11's move to bootstrap/app.php, break copy-pasted README snippets. Match the snippet to your framework version.storage/logs/laravel.log for Guzzle exceptions, verify outbound HTTPS/DNS from the host, and confirm the project key matches the dashboard project. A 401/403 means keys; a timeout means network; silence usually means environments.App\Support\ErrorReporter around it (or bind a fake in tests) so you can swap it later without touching call sites.How can I help you explore Laravel packages today?