laramon / agent
Laramon agent: ships requests, queries, jobs, exceptions and logs from a Laravel app to a Laramon server in batches, with negligible overhead.
Requires
- php: ^8.2
- ext-json: *
- illuminate/console: ^11.0|^12.0|^13.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
None
Suggests
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-03 07:49:46 UTC
README
The Laramon agent records what your Laravel application does — requests, queries, jobs, commands, scheduled tasks, exceptions, logs, cache, mail, notifications, outgoing HTTP calls — and ships it to your self-hosted Laramon server in batches, without a daemon and with about 0.3 ms of overhead per request.
Supports PHP 8.2+ and Laravel 11, 12 and 13.
Install
Add the package to the monitored app as a Composer repository. Pick one:
Local folder (apps on the same machine, for development):
composer config repositories.laramon-agent path /Users/641/Documents/Dev/websites/laramon-agent
Git (servers / Laravel Cloud):
composer config repositories.laramon-agent vcs https://github.com/msheeraz/laramon-agent.git
Then install:
composer require laramon/agent
php artisan vendor:publish --tag=laramon-config # optional
Create a project in Laramon (Projects → New project) and copy its token:
LARAMON_URL=https://laramon.example.com LARAMON_TOKEN=lm_xxxxxxxxxxxxxxxx
Verify:
php artisan laramon:status # config + connectivity + token check php artisan laramon:test # sends a test request, log and exception php artisan laramon:flush # ship the local spool now
Nothing is recorded while LARAMON_URL / LARAMON_TOKEN are empty, when
LARAMON_ENABLED=false, or while running the test suite (set
LARAMON_RECORD_TESTS=true to override).
How data leaves your app (performance)
The agent is built so production traffic never waits on Laramon:
- While a request / job / command runs, records are kept in memory.
- After the response is sent (PHP-FPM
fastcgi_finish_request), the execution's records are appended to a local spool file — one small write, about +0.3 ms per request. - At most every
LARAMON_SPOOL_INTERVALseconds (default 5) one process per server claims the spool and ships it to Laramon as a single gzip batch. Failed shipments back off for 30 s; if Laramon is unreachable for long, the spool is capped at 50 MB and new records are dropped instead of slowing the app. - When traffic stops, the scheduler ships what is left every minute (it registers
laramon:flushautomatically; on Laravel Cloud just enable the scheduler).
No daemon or background process is needed. Other per-request costs are bounded:
runtime info (PHP / Laravel / drivers) is sent at most hourly per server, and
EXPLAIN for slow queries runs at most once per query shape per 10 minutes per
server (shared through APCu or a small file in the temp dir).
The spool lives in {sys_get_temp_dir()}/laramon (override with LARAMON_STATE_PATH).
LARAMON_TRANSPORT=direct posts every execution straight to Laramon — useful
locally for debugging, never in production.
What is captured
| Record | Source |
|---|---|
| request | global middleware + router events: phases (bootstrap, middleware, action, render, sending, terminating), status, duration, CPU time, peak memory, sizes, route, user, redacted headers |
| query | QueryExecuted (SQL with placeholders, never bindings), connection, duration, calling file:line |
| exception | exception handler reportable() + log fallback; handled (report()/rescue()) vs unhandled, 50 frames (throw site first) with ±6 lines of source for application frames; optional request payload on unhandled exceptions |
| job_queued / job_attempt | queue events; attempt status, duration, CPU, peak memory and memory growth per job (leak detection) |
| command | CommandStarting / CommandFinished, exit code, duration, CPU, memory |
| scheduled_task | schedule events: processed / skipped / failed, duration, CPU, memory |
| cache | hit / miss / write / delete / failure with durations (Laravel 11+) |
| outgoing_request | HTTP client events, status, duration, sizes |
| mail / notification | mailable / notification class, channel, recipients count, failures |
| log | MessageLogged at or above LARAMON_LOG_LEVEL |
| user | authenticated user id / name / email, once per execution; queued jobs carry the id of the user who dispatched them |
| context | Context::all() (Laravel 11+) attached to requests, job attempts and commands (64KB cap) |
Durations are microseconds. Data is buffered per execution and spooled after the
response is sent, after each job / command, when the buffer reaches
LARAMON_BUFFER_LIMIT, and on shutdown. Shipping uses raw curl with a short
timeout (LARAMON_TIMEOUT, default 2s) and never throws into your application.
Configuration
| Variable | Default | Purpose |
|---|---|---|
LARAMON_URL / LARAMON_TOKEN |
— | Laramon server and project token (recording is off without both) |
LARAMON_ENABLED |
true |
Master switch |
LARAMON_SERVER |
gethostname() |
Server / instance name sent with every payload |
LARAMON_DEPLOY |
auto | Release id. Auto: LARAVEL_CLOUD_DEPLOYMENT_ID, Forge / Vapor variables, then the git commit (.git/HEAD) |
LARAMON_REQUEST_SAMPLE_RATE |
1 |
Share of requests recorded (children follow) |
LARAMON_JOB_SAMPLE_RATE |
1 |
Share of job attempts recorded |
LARAMON_COMMAND_SAMPLE_RATE |
1 |
Share of Artisan commands recorded |
LARAMON_SCHEDULED_TASK_SAMPLE_RATE |
1 |
Share of scheduled task runs recorded (LARAMON_SCHEDULE_SAMPLE_RATE still works) |
LARAMON_IGNORE_QUERIES |
false |
Drop all queries (inverse of LARAMON_CAPTURE_QUERIES) |
LARAMON_IGNORE_CACHE |
false |
Drop all cache events (LARAMON_IGNORE_CACHE_EVENTS alias) |
LARAMON_IGNORE_MAIL |
false |
Drop all mail |
LARAMON_IGNORE_NOTIFICATIONS |
false |
Drop all notifications |
LARAMON_IGNORE_OUTGOING_REQUESTS |
false |
Drop all outgoing HTTP requests |
LARAMON_CAPTURE_HEADERS |
true |
Request headers (sensitive ones redacted) |
LARAMON_CAPTURE_SOURCE_CODE |
true |
Source snippets for application stack frames |
LARAMON_CAPTURE_REQUEST_PAYLOAD |
false |
Request input on unhandled exceptions (files: name + size only, 32KB cap) |
LARAMON_REDACT_PAYLOAD_FIELDS |
_token,password,password_confirmation,current_password,card_number,cvv |
Payload keys replaced by [redacted] at any depth |
LARAMON_CAPTURE_CONTEXT |
true |
Attach Laravel Context::all() |
LARAMON_EXPLAIN |
true |
Capture EXPLAIN for slow SELECTs |
LARAMON_EXPLAIN_THRESHOLD_MS |
100 |
Minimum query duration to explain |
LARAMON_EXPLAIN_COOLDOWN |
600 |
Seconds before the same query shape is explained again (per process) |
LARAMON_LOG_LEVEL |
debug |
Minimum log level recorded |
LARAMON_BUFFER_LIMIT |
500 |
Flush early after this many buffered records |
LARAMON_TIMEOUT |
2 |
Seconds per ingest request |
LARAMON_TRANSPORT |
spool |
spool (batched, recommended) or direct (post every execution; debugging) |
LARAMON_SPOOL_INTERVAL |
5 |
Seconds between spool shipments per server |
LARAMON_STATE_PATH |
temp dir | Where the spool and throttle stamps live |
Query plans (EXPLAIN)
For SELECT/WITH queries slower than LARAMON_EXPLAIN_THRESHOLD_MS, the agent
runs EXPLAIN (MySQL/MariaDB), EXPLAIN (FORMAT JSON) (PostgreSQL) or
EXPLAIN QUERY PLAN (SQLite) once per query shape per cooldown, on the same
connection with the same bindings, and attaches the plan to that query record.
Laramon uses it to point at full table scans, unused indexes and filesorts.
The explain itself is never recorded and errors are swallowed. Disable with
LARAMON_EXPLAIN=false (e.g. for replicas where you can't run EXPLAIN).
Filtering, redaction and pausing
use Laramon\Agent\Facades\Laramon; // Drop everything a block of code produces $report = Laramon::ignore(fn () => $exporter->run()); // Or around code that can't use a callback Laramon::pause(); // ... Laramon::resume(); // AppServiceProvider::boot() Laramon::rejectQueries(fn (string $sql, string $connection) => str_contains($sql, 'telescope_')) ->rejectCacheKeys(fn (string $key, string $kind, string $store) => str_starts_with($key, 'throttle:')) ->rejectMail(fn (array $mail) => $mail['mailable'] === \App\Mail\Heartbeat::class) ->rejectNotifications(fn ($notification, string $channel, $notifiable) => $channel === 'broadcast') ->rejectOutgoingRequests(fn (string $method, string $url) => str_contains($url, 'health')) ->rejectJobs(fn (string $name) => $name === \App\Jobs\Ping::class); Laramon::redactQueries(fn (string $sql) => preg_replace("/'[^']*@[^']*'/", "'[email]'", $sql)) ->redactExceptions(fn (string $message) => preg_replace('/\d{12,19}/', '[card]', $message)) ->redactRequests(function (array $request) { $request['url'] = preg_replace('/token=[^&]+/', 'token=[redacted]', (string) $request['url']); $request['ip'] = null; return $request; }) ->redactMailSubjects(fn (string $subject) => 'redacted') ->redactCacheKeys(fn (string $key) => preg_replace('/user:\d+/', 'user:*', $key)) ->redactCommands(fn (string $line) => preg_replace('/--password=\S+/', '--password=***', $line));
Rejectors return true to drop a record; redactors return the transformed
value (a callback that throws or returns a different type is ignored).
Exceptions are never dropped by sampling.
Sampling & filtering
LARAMON_REQUEST_SAMPLE_RATE=0.25 # keep 25% of requests (and their queries, cache...) LARAMON_JOB_SAMPLE_RATE=1 LARAMON_COMMAND_SAMPLE_RATE=1 LARAMON_SCHEDULED_TASK_SAMPLE_RATE=1 LARAMON_LOG_LEVEL=info LARAMON_CAPTURE_QUERIES=true LARAMON_CAPTURE_CACHE=true LARAMON_CAPTURE_HEADERS=true
Exceptions are always recorded. Ignored paths, commands, jobs and cache keys are
configurable in config/laramon.php (ignore.*).