webimpian / laravel-log-central
Ship logs and exceptions from a Laravel app to a Log Central server.
Requires
- php: ^7.4|^8.0
- illuminate/support: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- symfony/polyfill-php80: ^1.16
Requires (Dev)
- guzzlehttp/guzzle: ^7.0
- orchestra/testbench: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0
- pestphp/pest: ^2.0|^3.0|^4.0
Suggests
- guzzlehttp/guzzle: Required only on Laravel 6, which has no HTTP client of its own (^6.3|^7.0).
README
Ship exceptions, log entries, and API request metrics from any Laravel application to a Log Central server. Install the package, set a few environment variables, and telemetry flows automatically over queued, batched background jobs — no code changes required.
Highlights
- Automatic exception tracking — every reported exception (web, queue, Artisan, scheduler, or an explicit
report($e)) is shipped with its fingerprint, stack trace, request context, and authenticated user. - Centralised log channels — nominate the channels you care about (or
*for all); their entries stream to Log Central while your local log files keep working unchanged. - Zero-touch API monitoring — every
api/*request is recorded with its method, route, status, and duration, plus the scrubbed response and payload, DB query count and time, peak memory, user-agent, and the authenticated user. Captured after the response is sent, so request latency is never affected. - Non-blocking by design — all telemetry is dispatched to queued jobs, batched, and retried with capped backoff. Delivery failures are handled silently and can never loop or disrupt the host application.
- Sensitive data scrubbed at the source — passwords, tokens, keys, and card numbers are replaced with
[scrubbed]before anything leaves your application.
Requirements
- PHP 7.4 or higher
- Laravel 6, 7, 8, 9, 10, 11, 12, or 13
guzzlehttp/guzzleon Laravel 6 only, which ships no HTTP client of its own- A running queue worker (Redis / Horizon recommended)
- A Log Central project key — create an app on the dashboard to obtain one
Installation
composer require webimpian/laravel-log-central
Add the connection details to your .env:
CENTRAL_LOG_URL=https://log.dev-aplikasiniaga.com/api CENTRAL_LOG_TOKEN=your-project-key CENTRAL_LOG_APP=your-app-slug # * ships every channel, or give a comma-separated list CENTRAL_LOG_CHANNELS=*
API monitoring is enabled by default for api/*. The following are optional — use them to narrow the monitored paths or control what is stored:
# path globs to monitor (comma-separated); empty disables CENTRAL_LOG_API_PATHS=api/* # response bodies to store: all | failed | none CENTRAL_LOG_API_RESPONSE=all # request payloads to store: all | failed | none CENTRAL_LOG_API_PAYLOAD=all # dispatch shipping jobs onto a dedicated queue CENTRAL_LOG_QUEUE=logs
The service provider registers itself automatically — no further wiring is required.
On Laravel 6, also install an HTTP client, which the framework does not provide until 7.0:
composer require guzzlehttp/guzzle
Configuration
Publish the configuration file to customise scrub keys, the shipping queue, or defaults:
php artisan vendor:publish --tag=log-central-config
| Variable | Default | Purpose |
|---|---|---|
CENTRAL_LOG_URL |
— | Base API URL of the Log Central server |
CENTRAL_LOG_TOKEN |
— | The application's project key |
CENTRAL_LOG_APP |
slug of app.name |
Must match the slug registered on Log Central |
CENTRAL_LOG_CHANNELS |
* |
Channels to ship; * = all (exceptions ship regardless) |
CENTRAL_LOG_API_PATHS |
api/* |
Paths whose API traffic is recorded; empty disables monitoring |
CENTRAL_LOG_API_RESPONSE |
all |
Response bodies to include: all, failed (4xx/5xx only), or none |
CENTRAL_LOG_API_PAYLOAD |
all |
Request payloads to include: all, failed (4xx/5xx only), or none |
CENTRAL_LOG_QUEUE |
default queue | Queue name for shipping jobs |
CENTRAL_LOG_VERIFY_SSL |
true |
Set false only for a local or self-signed Log Central server |
CENTRAL_LOG_ENABLED |
true |
Master switch (set false in testing) |
Shipping only happens when CENTRAL_LOG_ENABLED is true and both the URL and token are set. With anything missing the package stays completely dormant — no middleware, no listeners, no channel changes.
How it works
- Log channels are wrapped, not replaced. Each nominated channel is renamed to
<name>_localand a stack of[<name>_local, central]takes its place, so your existing files, Slack channels, or Papertrail sinks keep working exactly as before. Thecentralandemergencychannels are never wrapped, and neither are channels that are already stacks. Under*, discard sinks (nullandNullHandler-backed channels) are skipped too — they exist to throw entries away, so the wildcard must not resurrect them. - Entries are buffered, then batched. Log rows and API records accumulate in memory (up to 200) and flush as a single queued job when the app terminates or the process shuts down — one job per batch, not one per entry.
- API requests are recorded during
terminate(), after the response has been sent, so nothing here adds latency to the request itself. - Delivery is retried, then given up quietly. A transient failure (connection error, 5xx, 408, 429) is retried up to 3 times with a 10s then 60s backoff, and dropped in silence if it never succeeds — telemetry must never disturb the host app. Anything else (a bad URL, a rejected token) cannot be fixed by retrying, so the job is failed loudly and lands in
failed_jobswhere you will see it. - The shipper never reports itself. Exceptions thrown inside the package are filtered out before dispatch, so a broken Log Central can't loop telemetry back into itself.
- Oversized fields are capped. Any single field over 256 KB is replaced with a marker plus a 16 KB preview; API response and payload bodies are truncated to 4 KB.
What gets captured
| Situation | Shipped? |
|---|---|
| Uncaught exception (web, queue, Artisan, scheduler) | ✅ as error |
report($e) inside a try/catch |
✅ as error |
Log::...() on a shipped channel |
✅ as log entry |
Log::...() on an unlisted channel |
❌ file only |
Request to a matched api/* path |
✅ as API request |
Exception silently swallowed without report() |
❌ |
Scrubbing sensitive data
Request input, log context, API payloads, and API responses are all filtered before anything leaves the application. Keys are matched case-insensitively and as substrings, so card also covers card_number and CardHolder.
Defaults: password, password_confirmation, current_password, token, api_key, apikey, secret, authorization, card, cvv, cvc, pin.
Publish the config and edit the scrub array to add your own — replacing the list, so keep the defaults you still want:
'scrub' => [ 'password', 'token', 'ic_number', 'bank_account', ],
File uploads are never included in a payload.
Verifying the integration
# 1. a worker must be consuming the queue you configured php artisan queue:work --queue=logs # 2. send a test error php artisan tinker --execute 'report(new RuntimeException("log central smoke test"));'
The entry should appear on the Log Central dashboard within a few seconds. If it does not, work through the table below.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| Nothing arrives at all | No worker is consuming the queue. If you set CENTRAL_LOG_QUEUE=logs, the worker must run with --queue=logs, otherwise the jobs sit unprocessed. |
| Nothing arrives, queue is empty | The package is dormant: CENTRAL_LOG_ENABLED is false, or the URL or token is missing. |
Jobs land in failed_jobs |
Deliberate. The server rejected the request in a way retrying cannot fix — usually a wrong project key, an app slug not registered on Log Central, or a URL missing its /api suffix. Read the exception on the failed job. |
| Every entry appears twice | Fixed in v1.2.1. Upgrade, then run php artisan config:clear (or re-run config:cache) to rebuild config cached while the old version was wrapping channels non-idempotently. |
| Exceptions ship but log entries do not | CENTRAL_LOG_CHANNELS does not include the channel being written to. Exceptions ship regardless of this setting; log entries do not. |
| SSL certificate errors | A local or self-signed Log Central server — set CENTRAL_LOG_VERIFY_SSL=false. Never do this against production. |
Class "GuzzleHttp\Client" not found |
Laravel 6 without an HTTP client: composer require guzzlehttp/guzzle. |
| Telemetry noise in your test suite | Set CENTRAL_LOG_ENABLED=false in phpunit.xml / .env.testing. |
Compatibility
| Laravel | PHP | Notes |
|---|---|---|
| 6 | 7.4+ | Requires guzzlehttp/guzzle; the framework has no HTTP client |
| 7 – 8 | 7.4+ | |
| 9 – 13 | 8.0+ | Follows each release's own PHP floor |
The ingest contract is frozen at v1.0 — any package version talks to any Log Central server.
Testing
composer install
composer test
License
The MIT License (MIT). See LICENSE.md for details.