lukawar / lah-inspector
Bridges a Laravel app to lah-cli / laravel-api-helper — captures SQL queries (exact timings, bindings, source file:line) per request for the VS Code panel. Local/dev only.
Requires
- php: ^8.1
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- lukawar/lah-cli: ^1.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Bridges a Laravel application to lah-cli / the laravel-api-helper VS Code
extension. It captures the SQL queries executed during a request — with exact
timings, separated bindings and the source file:line — and streams them to the
locally running lah-cli sidecar, which forwards them to the editor panel.
This is the Full mode counterpart to lah-cli's standalone general_log capture
(Lite mode). It is local/development only and disables itself in production.
Capture modes at a glance
| Mode | Requires | What you get |
|---|---|---|
| Core | nothing | Route listing, sending requests, viewing responses. Always works. |
| Lite | lah-cli + MySQL general_log |
Queries captured from the DB log. No bindings, no file:line, time-window correlation, misses prepared statements. |
| Full | lah-cli + this package | Exact QueryExecuted timings, masked bindings, source file:line, request-id correlation. |
The VS Code panel auto-detects the active mode and shows a banner + install hints when a higher tier is available but not installed. You never need this package for the core plugin to work — it only unlocks the richer Queries/Models detail.
How it works
laravel-api-helper ──HTTP (X-Lah-Request-Id)──▶ Laravel + lah-inspector
│ announce() → POST /ingest/hello (once)
│ DB::listen → collector
│ terminate() → POST /ingest/queries
▼
lah-cli ──▶ VS Code panel
- The plugin sets an
X-Lah-Request-Idheader on every request it sends. CaptureMiddlewareannounces the package to lah-cli once per process, then activates the request-scopedInspectorfor that id.DB::listenrecords everyQueryExecuted(sql, exact time, bindings, callerfile:line).- On
terminate()the batch is POSTed to{ingest_url}/ingest/queries.
Correlation is by request id (not a time window), so it stays exact even under concurrent traffic.
Installation
One command installs both. This package depends on
lukawar/lah-cli, so requiring it also pulls the sidecar binary intovendor/bin/lah-cli— you do not install lah-cli separately.
A. From Packagist (when published)
composer require --dev lukawar/lah-inspector
This resolves lukawar/lah-inspector and lukawar/lah-cli in one shot. The CLI
ships prebuilt binaries for macOS/Linux (amd64/arm64) with a shim that auto-selects
your platform, exposed as vendor/bin/lah-cli.
B. Local path repository (development / unpublished)
If you have the package source on disk (e.g. while developing it), point Composer at
it with a path repository. In the Laravel app's composer.json:
{
"repositories": [
{
"type": "path",
"url": "src/lukawar/lah-inspector",
"options": { "symlink": true }
}
],
"require-dev": {
"lukawar/lah-inspector": "@dev"
}
}
Then:
composer update lukawar/lah-inspector
With symlink: true, Composer links vendor/lukawar/lah-inspector to your source
dir, so edits take effect immediately. (Under php-fpm with OPcache enabled you may
need to restart the fpm process to pick up changes.)
Configure the environment
Add to the app's .env:
LAH_ENABLED=true # optional — defaults shown: # LAH_INGEST_URL=http://127.0.0.1:7331
Capture runs only when LAH_ENABLED=true and the app is not in the
production environment.
Running Laravel in Docker (important)
ingest_url defaults to http://127.0.0.1:7331. Inside a container, 127.0.0.1
is the container itself, not your host — so if lah-cli runs on the host (the
usual setup), the hello/queries POSTs go nowhere and the panel silently stays in
Lite mode, prompting you to "add lukawar/lah-inspector" even though it is already
installed.
Point the package at the host gateway instead. In the app's .env:
LAH_ENABLED=true LAH_INGEST_URL=http://host.docker.internal:7331
On Docker Desktop (macOS/Windows) host.docker.internal already resolves to the
host. On Linux, add it explicitly to the php service in docker-compose.yml:
services: php: extra_hosts: - "host.docker.internal:host-gateway"
Verify from inside the container:
php artisan lah:status # lah-cli: reachable
If it still says not reachable, the container cannot reach the host on port
7331 — check the extra_hosts entry and that lah-cli is running in the project
root. See Troubleshooting.
Verify the wiring
php artisan lah:status
Example output:
lah-inspector v0.1.0
enabled: yes
env: local
ingest_url: http://127.0.0.1:7331
lah-cli: reachable
enabled: no→ setLAH_ENABLED=true.lah-cli: not reachable→ start lah-cli in the project root (it must be listening on the ingest port).
Then send a request through the laravel-api-helper panel: the Queries tab should
switch to the Full banner and show per-query timing, bindings and a clickable
📄 file:line.
Configuration
Publish the config to tweak defaults:
php artisan vendor:publish --tag=lah-inspector-config
| Key | Default | Purpose |
|---|---|---|
enabled |
false |
Master switch (LAH_ENABLED). |
ingest_url |
http://127.0.0.1:7331 |
lah-cli base URL. |
header |
X-Lah-Request-Id |
Correlation header. |
capture_bindings |
true |
Send bindings (masked) alongside SQL. |
capture_caller |
true |
Resolve source file:line via backtrace. |
max_queries |
500 |
Cap per request. |
mask |
password, token, secret, … | Columns whose col = ? bindings are masked. |
timeout_ms |
300 |
Upper bound on the ingest POST. |
Each key also reads a matching LAH_* env var (LAH_INGEST_URL, LAH_HEADER,
LAH_CAPTURE_BINDINGS, LAH_CAPTURE_CALLER, LAH_MAX_QUERIES, LAH_TIMEOUT_MS).
Security
- Hard-disabled in
production, regardless ofLAH_ENABLED. - Data is sent only to the local
ingest_url(loopback / host gateway). - Sensitive bindings (
password = ?,token = ?, …) are masked to***before leaving the app. - Ingest failures are swallowed — diagnostics never break the app request.
- Installed as a
--devdependency; it is not shipped to production deploys.
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
| Queries tab stays in Lite (despite the package being installed) | Package not loaded, LAH_ENABLED off, or — most commonly in Docker — ingest_url points at 127.0.0.1 from inside the container. Run php artisan lah:status; set LAH_INGEST_URL=http://host.docker.internal:7331; clear config cache (php artisan config:clear). |
lah-cli: not reachable |
lah-cli not running, wrong ingest_url, or (in Docker) missing host.docker.internal — see Running Laravel in Docker. |
| Edits to package don't apply | OPcache under php-fpm — restart the fpm process / container. |
file:line points at vendor code |
The first non-internal frame is used; Caller skips Laravel/Illuminate/Symfony and this package's own dir. |
| No queries at all | Confirm the plugin is sending the X-Lah-Request-Id header and that the route actually hits the DB. |
Limitations (v1)
- Binding masking covers
column <op> ?clauses; INSERT value lists are not column-mapped yet. - Designed for the classic FPM request lifecycle; Octane is out of scope for now.