spatie/backtrace
Spatie's PHP package offers a cleaner alternative to debug_backtrace for working with backtraces. Create a Backtrace instance, get an array of Frame objects, and easily read each frame's file, line number, class, method, arguments, and object.
This is a low-level utility, not a Laravel package in the traditional sense — there is no service provider, no facade, and no config file to publish. You composer require spatie/backtrace and use the static factory directly. Expect to reach for it when you are building debugging/observability tooling rather than in ordinary controller code.
Your first use case is almost certainly replacing a raw debug_backtrace() call. The baseline:
use Spatie\Backtrace\Backtrace;
$frames = Backtrace::create()
->applicationPath(base_path())
->trimFilePaths()
->frames(); // Spatie\Backtrace\Frame[]
Look at the Spatie\Backtrace\Frame class first — file, lineNumber, class, method, arguments, object, applicationFrame, trimmedFilePath are the entire surface you'll work with. The Backtrace builder is where the behavior lives: withArguments(), withObject(), reduceArguments(), applicationPath(), trimFilePaths(), offset(), limit(), startingFromFrame().
The single most valuable property in a Laravel app is applicationFrame. It lets you instantly split "frames in my app/, routes/, config/" from "frames in vendor/", which is the exact filtering every crash reporter needs and the reason you'd choose this over debug_backtrace().
Custom exception handler / log enrichment. In bootstrap/app.php or an exception handler, use Backtrace::createForThrowable($e) to get frames from the already-recorded trace (cheaper than re-walking the stack), then filter to application frames and format them for your log channel or Slack alert. Combine with getSnippetAsString() on the top application frame to include a few lines of source context.
"Where did this come from?" debugging. When a service is called from many places and you need to know the caller, create()->applicationPath(base_path())->startingFromFrame(fn (Frame $f) => $f->class === MyService::class)->frames() gives you everything after your entry point — no manual index counting.
Compact trace strings for structured logs. Build a helper that maps frames to "Class::method (app/Services/Foo.php:42)" and attach it as a log context key. Use applicationPath() + trimFilePaths() so paths are repo-relative and readable in your log aggregator. Pair with offset() to drop framework bootstrap noise.
Splitting app vs vendor in a crash report. Use applicationFrame to render two sections — "Your code" and "Vendor" — instead of a wall of 60 frames. This is precisely how Ignition-style error pages are built, and it's the pattern users of your tooling will expect.
Argument reduction for safe logging. withArguments()->reduceArguments() gives you ReducedArgument / UnReducedArgument contract objects rather than raw values. If you log request payloads or domain objects, write a custom ArgumentReducer for your money/date/ID value objects returning only the fields you're willing to put in logs — ArgumentReducers::default([new YourReducer()]) runs yours first, then the built-ins.
Reusable trait. Since there's no container binding, the idiomatic Laravel move is a small trait or helper (e.g. CapturesBacktrace::backtrace()) that pre-applies applicationPath(base_path()) so callers can't forget it.
withArguments() and especially withObject() are opt-in for a reason. withObject() retains references to live objects and can blow up memory in long-running queue workers or Octane. Never enable it unconditionally in production.createForThrowable() cannot include object — PHP simply doesn't record them. Don't promise it in your UI.zend.exception_ignore_args. Arguments from a throwable only appear if this INI setting is 0 at the time the exception is thrown. Setting it at runtime after the fact changes nothing. This is a common source of "my args are null" confusion.trimFilePaths() silently does nothing without applicationPath(). They're a pair; set both or neither.artisan and please are special-cased as vendor frames since 1.8.0, even though they live in your project root. If you filter by applicationFrame, your CLI entry point vanishes — usually desirable, but surprising if you expect it.file can be a directory in unusual contexts and this crashed the package until 1.8.2. Pin ^1.8.2 or later.open_basedir (fixed in 1.7.4). Guard snippet rendering behind a try/catch if you're in constrained hosting environments.config/backtrace.php.ArgumentReducer, not events or middleware. That's the only designed seam; everything else is a builder method.How can I help you explore Laravel packages today?