ahmedhsieb / laravel-request-tracer
Trace a Laravel request end to end: per-step timing, input/output in dd() format, line-by-line Deep trace, table writes and problem detection. A production-safe replacement for dd().
Package info
github.com/Ahmedhsieb/laravel-request-tracer
pkg:composer/ahmedhsieb/laravel-request-tracer
Requires
- php: ^8.1
- ext-json: *
- illuminate/cache: ^10.0|^11.0|^12.0|^13.0
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/contracts: ^10.0|^11.0|^12.0|^13.0
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/events: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/queue: ^10.0|^11.0|^12.0|^13.0
- illuminate/routing: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- nikic/php-parser: ^4.18|^5.0
- symfony/var-dumper: ^6.2|^7.0|^8.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.0
- larastan/larastan: ^2.9|^3.0
- laravel/pint: ^1.13
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- pestphp/pest: ^2.0|^3.0|^4.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v0.9.0
- v0.8.6
- v0.8.5
- v0.8.4
- v0.8.3
- v0.8.2
- v0.8.1
- v0.8.0
- v0.7.0
- v0.6.0
- v0.5.5
- v0.5.4
- v0.5.3
- v0.5.2
- v0.5.1
- v0.5.0
- v0.4.0
- v0.3.1
- v0.3.0
- v0.2.1
- v0.2.0
- v0.1.0
- dev-phase/24-readme
- dev-phase/23-graph-filters
- dev-phase/22-graph
- dev-phase/21-copy
- dev-phase/20-folders
- dev-phase/19-ui
- dev-phase/18-namespace
- dev-phase/17-journal-load-more
- dev-phase/16-did-not-run
- dev-phase/15-journal-groups
- dev-phase/14-follow-edits
- dev-phase/13-untraced-code
- dev-phase/12-workbench
- dev-phase/11-replay-modes
- dev-phase/10-journal
- dev-phase/9-config-merge
- dev-phase/8-simple
- dev-phase/7-docs
- dev-phase/6-hardening
- dev-phase/5-dashboard
- dev-phase/4-api
- dev-phase/3b-deep-trace
- dev-phase/3-proxies
- dev-phase/2-collectors
- dev-phase/1-core
- dev-phase/0-scaffold
This package is auto-updated.
Last update: 2026-10-14 06:03:55 UTC
README
Replace dd() with a trace of the whole request.
dd() shows one value in one place and kills the request. Request Tracer records the whole request, step by step, and lets you open any step: what went in, what came out, which lines ran, which rows were written.
- Install · Start tracing · Features · Configure · Commands · Examples · Production
Install
composer require ahmedhsieb/laravel-request-tracer
That is all. No config file, no migration, nothing to publish: the tables create themselves and the dashboard is served from the package.
| Needs | |
|---|---|
| PHP | 8.1 – 8.4 |
| Laravel | 10, 11, 12, 13 |
| Database | MySQL / MariaDB, PostgreSQL or SQLite (for the traces) |
Want to look around first? npm ci && npm run dev in the package folder opens the dashboard with demo data, no backend needed.
Start tracing
php artisan request-tracer:listen
Use your application as usual, then open /request-tracer.
It works like queue:work: while it runs, everything the project does is traced (requests, queued jobs, artisan commands, scheduled tasks) and each trace is printed in the terminal. Press Ctrl+C and it stops. When it is not running nothing is recorded, and the package costs 25 to 65 µs per request.
- Local: the dashboard is open to everyone.
- Any other environment: closed (
403) until you define who may open it, see Production.
Features
The flow: everything the request did
Middleware, controller, validation, services, queries, HTTP calls, events, jobs, cache, logs and exceptions, each on a timeline with its real start and duration. Above it: time, memory, queries, problems, tables written, and where the time went by kind of step. The strip under the tabs is a time map: drag over it to zoom the timeline into that part of the request.
Hover a step to preview it, click to pin it. The right side shows its Input and Output, printed like dd(), with secrets masked.
Folders: deep requests stay readable
A step with steps inside is a folder. The list shows one folder at a time, so names are never squeezed by indentation, however deep the request goes. Open a folder with the button on its row, go up with the arrow, or jump to any level from the path above the list. The time axis follows: an open folder shows its own stretch of time.
Search and filters, inside the folder
Search by name, SQL, file or summary. Filter by kind (query, HTTP, cache...), speed (slow / fast) and result (failed / OK). They look at everything below the open folder, however deep, and list the matches with the path each one is in. A switch narrows them to the folder's own steps.
Graph view
The same steps as columns that grow to the right. Click a step and what is inside it opens in the next column; the path you came by stays on the left, and clicking an earlier step goes back. Lines are as thick as the share of the time a step took.
Inside the code, line by line
Every condition, block and variable of a step: which if was true, which block never ran, what the variables held. Hover a line to see what happened there, click it to keep the pop-up open. Replay walks through the lines in the order they ran. Functions the step called appear under the line that calls them.
| Dot | Meaning |
|---|---|
| green | the line ran |
| purple | a condition was false |
| red | the line threw |
| orange | an exception was caught here |
| blue | a loop (with its iterations) |
Database: what the request wrote
Every table the request changed: INSERT, UPDATE, DELETE, the rows, and for updates before → after with the changed cells highlighted. Each card names the step that wrote it and whether the transaction committed or rolled back.
Problems: found for you
Uncaught and caught exceptions, N+1 queries, repeated and slow queries, slow steps and failed HTTP calls, each with an explanation, the file and line, and a link to the step in the flow.
Replay: run a request again, traced
Nothing was recording when it happened? Run it again. In the dashboard press Replay a request, paste a cURL command (browser: Network tab, Copy as cURL) or describe the request. You choose what it may change:
| Simulation (default) | Run for real | |
|---|---|---|
| Database | rolled back, nothing changes | changes are kept |
| E-mails, notifications, jobs, HTTP-client calls | do not go out | go out |
| Afterwards | Undo puts every Eloquent write back |
Replay is off in the production environment unless you turn it on; running for real there needs a second switch.
Journal: when you do not know which request it was
Turn it on (REQUEST_TRACER_JOURNAL=true) and every request leaves one light row (URL, masked body, user, status, error), traced or not, for an hour. Pick the one that failed and it replays itself with a full trace. Group by route puts the requests of one route prefix together in the list and in the journal.
Also in the dashboard
- Ctrl+K: search requests, the steps of the open request, and actions, from the keyboard.
- Record from outside: a signed header for one request, or watch a user for a while, for servers where you cannot run the listen command.
- Jobs and commands: a job started by a request links back to it, and the other way round.
- Dark and light themes; panes you can resize; works on a narrow screen.
Configure
There is nothing to configure to start. To change something publish the file and keep only the keys you change (every key has a default):
php artisan vendor:publish --tag=request-tracer-config
The keys you will reach for first:
| Key | Default | What it does |
|---|---|---|
code.paths |
['app'] |
Folders or files whose code is traced, line by line. Everything inside is traced, you never list classes. |
code.except |
[] |
Folders, files or classes to leave out ('app/Legacy', 'App\Reports\*'). #[NoTrace] on a class does the same. |
dashboard.path |
request-tracer |
URL of the dashboard. |
dashboard.gate |
viewRequestTracer |
The gate that decides who may open it. |
journal.enabled |
false |
Note every request so it can be replayed (REQUEST_TRACER_JOURNAL). |
replay.enabled, replay.real |
null |
Allow replay (and running for real). null = everywhere except production. |
masking.keys |
password, token, secret, card... |
Field names whose values are masked before anything is stored. Yours are added. |
exclude.paths |
login, webhooks, health checks... | URLs that are never recorded. Yours are added. |
retention.hours, retention.max_rows |
24, 5000 |
How long traces are kept (cleaned up automatically). |
limits.max_spans |
5000 |
Steps stored per trace. |
unlimited |
false |
Store everything, whatever its size, so you never tune the numbers above. Only the process running out of memory stops it. For debugging (REQUEST_TRACER_UNLIMITED=true). |
storage.connection |
app default | Database connection that holds the tables (REQUEST_TRACER_DB_CONNECTION). |
Environment variables: REQUEST_TRACER_ENABLED, REQUEST_TRACER_MODE, REQUEST_TRACER_JOURNAL, REQUEST_TRACER_DB_CONNECTION. Every key is in docs/04-configuration.md.
// config/request-tracer.php return [ 'code' => [ 'paths' => ['app', 'modules/Billing/src', 'lib/Money.php'], 'except' => ['app/Legacy', 'App\Reports\*'], ], 'masking' => ['keys' => ['ssn', 'national_id']], 'journal' => ['enabled' => true], ];
Commands
| Command | What it does |
|---|---|
php artisan request-tracer:listen |
Trace everything while it runs. --seconds=60 stops after a minute, --no-feed prints nothing. |
php artisan request-tracer:replay "POST /orders" |
Run a request again, traced. Options: --data=, --header=, --as=<user id>, --curl=, --journal=failed, --real. |
php artisan request-tracer:undo <trace> |
Take back what a real replay changed. |
php artisan request-tracer:token |
Make a signed header for one request (--ttl=, --user=, --route=). |
php artisan request-tracer:prune |
Delete old traces (scheduled automatically). |
php artisan request-tracer:install |
Optional: publish the config and assets. |
Examples
Run a failed request again, with a full trace
php artisan request-tracer:replay --journal=failed
Replay a request as a user, sending a body
php artisan request-tracer:replay "POST /orders" --data='{"items":[1,2]}' --as=42
Run it for real, then undo it
php artisan request-tracer:replay "POST /orders" --data='{"items":[1,2]}' --as=42 --real php artisan request-tracer:undo 01J8DEMO0RDER0FL0W00000001
Trace one request on a server, without the listen command
php artisan request-tracer:token --ttl=300 --route='api/orders*' # then send it: curl -H "X-Request-Tracer: <token>" https://example.com/api/orders
Trace something outside app/, or leave a class out
use Ahmedhsieb\RequestTracer\Attributes\Trace; use Ahmedhsieb\RequestTracer\Attributes\NoTrace; #[Trace(deep: true)] // this class: input, output and line by line class Money { /* ... */ } #[NoTrace] // never traced class HeavyReport { /* ... */ }
Show the tenant or module next to each request
// App\Providers\AppServiceProvider::boot() use Ahmedhsieb\RequestTracer\Facades\Tracer; Tracer::resolveTenantUsing(fn ($request) => $request?->header('X-Tenant')); Tracer::resolveModuleUsing(fn ($request) => $request?->segment(1));
In production
Same two steps, on the server. The dashboard stays closed until you say who may open it:
Gate::define('viewRequestTracer', fn ($user) => in_array($user->email, ['ops@example.com']));
- Secrets are masked before they are stored, by field name and by shape (
Bearer ...). The raw value never reaches the database. The one exception is the encrypted, short-lived undo log of a real replay. - A failure inside the tracer never breaks a request; it is logged and swallowed.
- Nothing records on its own: only while
request-tracer:listenruns, or for a signed header, a watched user, a sample, or a replay. - To trace without the command: signed header, watched user, sampling.
What it costs
Nothing measurable when nobody listens. While listening, a request of 47 steps and 15 queries takes about 5 ms longer with input and output for every call, about 7.5 ms with every line as well, and 7 to 10 ms more after the response was sent. Method and caveats in docs/benchmarks.md.
Honest limits
Proxies cannot see new, final classes, private or static methods. Line-by-line tracing compiles a copy of each file of code.paths the first time it is loaded while somebody listens; a long-running worker (Octane, queue:work) keeps what it loaded before you started listening until it restarts. Masking is name based. The package is 0.x. More in How it compares.
Documentation
Introduction · Installation · Quick start · Configuration · How capture works · Deep trace · Production · Large applications · Using the dashboard · Jobs and commands · Extending · Comparison · Troubleshooting and upgrading · JSON API · Decisions · Security · Changelog
License
MIT.
See LICENSE. العربية: README.ar.md












