protonemedia/laravel-tracer
Laravel Tracer wraps every rendered Blade view in HTML comments showing that view's path, so you can inspect the page source in your browser and instantly find the template responsible for any markup. A small, handy debugging aid for apps with deeply nested views.
Know what you're adopting first. protonemedia/laravel-tracer is an archived, single-purpose debugging package (4 stars, no dependents). The newest tag, v0.3.3, is a platform-support bump only — PHP 8.1 + Laravel 9 — with no feature or API changes, so nothing about how you use it has changed. It is not infrastructure you build on — it's a temporary instrument you install, point at a misbehaving model, and remove.
The v0.3.3 bump matters for one reason: it tells you exactly where the ceiling is. You can install cleanly on Laravel 9 / PHP 8.1+. You cannot on Laravel 10/11 — expect a composer.json constraint override, --ignore-platform-reqs, or a fork. And the PHP 8.1 floor means it won't go near an 8.0 app at all. If you're on L10+, budget for the fork before you start, and read the release note as "this is as far as it goes," not "maintenance resumed."
Minimal steps:
composer require protonemedia/laravel-tracer --dev
php artisan vendor:publish # pick the Tracer provider from the list
php artisan migrate # creates the tracer/audit table
Then look at, in this order:
config/tracer.php — this is the whole package. It controls which models/events are recorded and what context is captured. Everything you'll actually tune lives here.Tracer::latest()->first()) rather than building a UI.First use case: "Something is silently updating orders.status and nobody knows how." Enable tracing for that one model, reproduce the flow in local or staging, then read the newest rows: you get the event, the before/after attributes, and the file/line or stack that caused it. That is the entire value proposition — it answers where did this write come from, which neither laravel/telescope nor a plain observer answers cleanly.
If the constraint fight isn't worth it, the fallback is ~15 lines in a service provider (Model::updated(fn ($m) => Log::debug(class($m), ['trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 15)])) and you've reproduced 80% of the package. On L10/11 that fallback is usually the better trade — the v0.3.3 release note is a strong signal there won't be a v0.4 to save you.
Scope it to one model, one environment. Tracing everything is how you take down a staging server. Turn it on per-model, and gate it behind an env flag so it can never accidentally run in production:
// config/tracer.php (shape per README)
'enabled' => env('TRACER_ENABLED', false),
'models' => [\App\Models\Order::class],
Debug workflow that actually works:
App\ namespace frame — that frame is the culprit; everything below it is framework noise.Pair it with a failing test, not with manual clicking. The trace tells you the origin; a test (Event::fake() / assertDatabaseHas) is what stops it coming back. The package is a diagnostic, not a safety net.
Use it to find hidden writers, which are usually one of: an observer/listener, a queued job replaying an old payload, a saving hook on a parent model, or a package (cashier, auditing, search indexers) touching attributes you didn't expect. Each of those shows up as a distinct trace shape — jobs tend to be short traces ending in CallQueuedHandler, observers end in your App\Observers\* file.
Complement, don't duplicate, your audit log. If you need permanent history (who changed what, shown to users), use owen-it/laravel-auditing or spatie/activitylog. Use tracer purely for the stack-trace dimension during a debugging session. Don't leave tracer enabled as a permanent audit trail — the table grows on every write and the trace payloads are bulky.
Truncate aggressively. Treat the table as scratch space: DB::table('tracers')->truncate() between reproductions, otherwise you're scrolling through your own noise.
composer.json constraints yourself. Don't let it into a dependency graph you don't control.composer require fails before you start blaming Laravel constraints.Model::where(...)->update([...]), raw DB::update(), and bulk upserts fire nothing, so they produce no traces. If your mystery write is a mass update, tracer will look broken when it isn't. Reach for DB::listen() or MySQL's general log in that case.saveQuietly(), withoutEvents(), and Model::withoutEvents() likewise bypass it — and are a common reason a row "changes by itself."debug_backtrace() is expensive. Even scoped to one model, don't enable it on a hot write path in a load test and conclude your app got slower.remember_token-style columns verbatim. Never paste a full row into a ticket or a public Slack channel.php artisan config:cache after every config edit — a stale cached config is the #1 cause of "I enabled it and nothing happened."enabled false, model not listed, events disabled at the call site, or the write wasn't Eloquent.How can I help you explore Laravel packages today?