laravel maintained by logic-replay
Laravel Logic Replay
Record one HTTP request as a sanitized trace: route middleware, service spans, queries, jobs, and checkpoints where the expected business result did not match. Dry-replay that same request locally inside a database transaction that is rolled back.
Recording is limited to local and testing unless you change record_environments. The viewer is limited the same way. Replay never commits database writes, and it fakes HTTP, mail, notifications, and queues.
Install
composer require logic-replay/laravel
From a path checkout:
"repositories": [
{ "type": "path", "url": "../laravel-logic-reply" }
]
Publish the config when you want to change the defaults:
php artisan vendor:publish --tag=logic-replay-config
The file driver writes JSON under storage/logic-replay. For the database driver, set LOGIC_REPLAY_DRIVER=database and publish the migration:
php artisan vendor:publish --tag=logic-replay-migrations
php artisan migrate
The package prepends its trace middleware automatically.
Checkpoints
use LogicReplay\Facades\LogicReplay;
$total = LogicReplay::span('pricing.calculate', fn () => $pricing->calculate($cart));
LogicReplay::expect('cart.grand_total', expected: 1500, actual: $total->amount);
expect() returns false when the values differ. The HTTP response can still be 200. The trace status becomes failed.
Wrap a service call that is not already inside span():
LogicReplay::call($pricing, 'calculate', [$cart]);
Inspect
GET /logic-replaylists traces.php artisan logic-replay:listphp artisan logic-replay:show {id}php artisan logic-replay:export {id} trace.jsonphp artisan logic-replay:import trace.json
Passwords, tokens, cookies, and similar keys are stored as [redacted]. Other fields, including email addresses, are kept so a local replay still has the lookup values. Add those keys to redact_keys when they should be removed. Do not commit trace files.
Dry replay
php artisan logic-replay:run {id} --dry
The command freezes time to the original request, restores a user only when LogicReplay::resolveUserUsing() is set, fakes external calls from recorded HTTP responses, and rolls the database transaction back.
LogicReplay::resolveUserUsing(fn (int|string $id) => \App\Models\User::find($id));
LogicReplay::resolveTenantUsing(fn () => auth()->user()?->tenant_id);
During replay the request has header X-Logic-Replay-Tenant and attribute logic_replay.tenant_id.
Verdicts are same_failure, fixed, new_failure, unchanged, or incomplete. Queued jobs from the original trace are not executed.
Replay runs only in replay_environments (local, testing by default).