kregel/exception-probe
Exception Probe is a Laravel package for inspecting and debugging exceptions. It surfaces useful exception details so you can understand failures faster, trace issues more clearly, and spend less time digging through logs during development. Designed to fit neatly into existing Laravel applications.
Install it and immediately look at src/ — this is a small utility package (3 stars, 0 dependents), so the surface area is likely one or two classes. Read the README and the tests before writing any code against it; the tests are the most reliable documentation for the input format the parser expects (usually Throwable::getTraceAsString() output, sometimes Throwable::getTrace() arrays).
composer require kregel/exception-probe
The first practical use case is turning a raw stacktrace into a structured, loggable payload. Instead of dumping $e->getTraceAsString() as one opaque blob:
use Throwable;
try {
$payments->charge($order);
} catch (Throwable $e) {
// Class name is illustrative — confirm from src/
$frames = (new \Kregel\ExceptionProbe\Probe())->parse($e->getTraceAsString());
Log::error('Payment charge failed', [
'message' => $e->getMessage(),
'frames' => $frames,
]);
throw $e;
}
If parsing produces empty frames, dump the raw trace first and compare it to the shape the tests expect — that will tell you almost everything about how the package behaves.
Custom report() in App\Exceptions\Handler. This is where the package earns its keep. Parse once, log structured frames, and forward to whatever sink you have:
public function report(Throwable $e): void
{
$frames = app(\Kregel\ExceptionProbe\Probe::class)->parse($e->getTraceAsString());
Log::channel('errors')->error($e->getMessage(), [
'class' => $e::class,
'frames' => $frames,
]);
parent::report($e);
}
Bind the probe as a singleton in AppServiceProvider so you aren't re-instantiating on every report.
Middleware for request-scoped context. Build a middleware that wraps the HandleExceptions behaviour so you can attach the parsed trace alongside request_id, user id, and route. This gives you greppable, structured exception logs without reaching for Sentry/Flare.
Sanitized client-facing errors. Don't leak the trace to users, but you can use parsed frames to compute a stable fingerprint (hash of class+method+file+line) and show that to the user as a support reference. Pair it with a request_id in your logs.
Structured logging via Log::withContext. Parse and merge into context once per request in bootstrap/app.php or a middleware, then any Log::error($e->getMessage()) picks up frames automatically.
Test fixtures. Because the parser is string/in array-driven, you can write pure unit tests against captured stacktrace strings (from real past exceptions) without booting Laravel. This is useful when you want a regression guard around how you present traces.
CLI/Artisan formatting. Artisan commands that catch exceptions can feed the parsed frames into a $this->table() or $this->line() renderer so operators get a clean, columnar view instead of a wall of text.
"kregel/exception-probe": "x.y.z") and be ready to vendor the source if it breaks on newer PHP. There is no active issue tracker signal here.php artisan vendor:publish will do anything.getTraceAsString() is not the same as Xdebug's format or the raw getTrace() array. Some frames legitimately have no file or no class (closures, internal calls, {main}). Any parser that produces a "clean" row per frame will have gaps — decide up front whether you want empty strings, nulls, or skipped frames.zend.exception_ignore_args. On PHP 7.4+ this is On by default, so args in frames will be empty even though the parser "supports" it. Don't design logging around argument contents unless you've verified the ini setting in every environment (local, CI, prod).composer update that shifts a use statement will invalidate your fingerprints. Prefer class+method+file and downgrade line to informational.getTraceAsString() only captures the outermost exception. If you need previous frames you'll need to walk the chain yourself and call the parser per exception.parse() returns empty or throws, log the raw string first and diff against the package's fixtures. Common culprits are leading/trailing whitespace, missing #N frames, or traces from a different SAPI.App\Support\TraceParser) that owns the formatting contract. That way you can swap kregel/exception-probe out later without touching call sites.report(). Reporting happens in the failure path, so cost is usually acceptable — but if you're logging high-cardinality traces on a busy endpoint, cache the parsed structure per exception instance or sample aggressively.Throwable over Exception in any typehints you add, since the parser doesn't care about the hierarchy and you don't want to lose Error handling.How can I help you explore Laravel packages today?