meyercomputerinc / log-client
Buffered, batch-shipping Monolog handler for Axiom. One HTTP request per execution; can never slow down or break the host application.
Requires
- php: ^8.3
- ext-curl: *
- ext-json: *
- laravel/framework: ^12.0|^13.0
- monolog/monolog: ^3.11
Requires (Dev)
- laravel/pint: ^1.29
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Buffered, batch-shipping Monolog handler for Axiom. Log records are collected in memory during one execution (HTTP request, artisan command, queued job, scheduled task) and POSTed to Axiom as a single NDJSON batch when that execution ends.
It is built so that logging can never slow down or break the host application:
- one HTTP call per execution, hard-capped at 1 s connect + 2 s total (
AXIOM_CONNECT_TIMEOUT_MS/AXIOM_TIMEOUT_MS; a TLS handshake to Axiom from far away can take 400–700 ms); - a circuit breaker skips shipping for 60 s after 3 consecutive failures;
- every code path catches
Throwable; failures are reported once per process viaerror_log(), never through Laravel's logger; - a bounded 500-record buffer that drops the oldest records under pressure;
- inert during unit tests unless explicitly allowed.
Install
composer require meyercomputerinc/log-client
The service provider is auto-discovered and registers an axiom log channel. Add it to your stack:
LOG_STACK=stderr,axiom # or single,axiom locally
AXIOM_ENABLED=true
AXIOM_TOKEN=xaat-...
AXIOM_DATASET=my-app
Axiom setup
- Create a dataset (one per app is a good default, e.g.
my-appandmy-app-dev). Datasets are not auto-created on ingest. - Create an API token with ingest permission on that dataset.
- Optional: run
php artisan vendor:publish --tag=log-client-configto tune the defaults.
Verify
php artisan log-client:test --dry-run # prints the NDJSON that would be sent
php artisan log-client:test # sends sample records, prints status / ingested / failed / breaker
Then in Axiom: ["my-app"] | where execution_id == "<id printed by the command>".
What gets shipped
Every event carries _time (UTC), level, message, channel, app, environment, host, and execution metadata: execution (request | job | command | schedule), execution_name (route name, job class, command, task), execution_id (one per execution), trace_id (shared with jobs dispatched from the same request; uses Nightwatch's trace id when Nightwatch is installed), and user_id when someone is authenticated.
Context keys listed in promote (default association, source, client_id) become top-level fields. Everything else in context — and Laravel Context data, which Laravel places in extra — is shipped according to context_mode:
json(default): one string field. Always safe; the Axiom free plan caps a dataset at 256 field names, and nested keys would otherwise sprawl. Query it withparse_json(context).map: a nested object. Createcontextandextraas map fields in Axiom first so each counts as one field.drop: omit it.
Exceptions in context (['exception' => $e], as Laravel's handler sends them) become exactly exception.class, exception.message, exception.file and a trace capped at exception.trace_frames lines.
When the buffer flushes
| Execution | Flush point |
|---|---|
| HTTP request | end of the kernel's terminate(), after terminable middleware |
| Artisan command | end of the console kernel's terminate() |
| Queue worker (incl. Horizon) | Looping (before the next job) and WorkerStopping — never on the job events, which fire before the worker logs a failed job's exception |
| Scheduled task | after each task |
| Any long-running process | when the oldest buffered record is flush_after_seconds old (default 10), or the buffer hits buffer_limit |
Configuration
See config/log-client.php. Everything is env-driven; the keys you are most likely to touch are AXIOM_LOG_LEVEL, context_mode, promote, and flush_after_seconds.
Testing your app
The package is forced off while app()->runningUnitTests() is true unless log-client.allow_in_tests is set, so application test suites never ship logs. To make that explicit, add <env name="AXIOM_ENABLED" value="false"/> to phpunit.xml.
License
MIT