cbagdawala / innlogger
Laravel SDK for InnLogger: signed, fail-silent application log and exception shipping.
Requires
- php: ^8.1
- ext-json: *
- guzzlehttp/guzzle: ^7.5
- illuminate/console: ^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/contracts: ^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/log: ^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- monolog/monolog: ^2.0|^3.0
- psr/log: ^1.1|^2.0|^3.0
Requires (Dev)
- orchestra/testbench: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^9.5|^10.5|^11.0|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Ships application logs and exceptions from a Laravel app to an InnLogger portal over signed HTTPS requests. It is built to never break the host application: short timeouts, every failure swallowed, no recursion.
- Laravel 8, 9, 10, 11, 12 and 13, PHP 8.1+
InnLoggerfacade:critical,error,warning,notice,info,debug,trace,exceptionLog::channel('innlogger')log channel (and stack support)- Optional automatic exception reporting, request context capture and heartbeat
innlogger:test,innlogger:status,innlogger:heartbeatandinnlogger:importArtisan commands- Recursive, case-insensitive redaction plus value masking
Installation
composer require cbagdawala/innlogger
php artisan vendor:publish --tag=innlogger-config # optional: config/innlogger.php
The service provider and the InnLogger alias are auto-discovered.
Configuration
Create a project and credential in the InnLogger portal, then add to .env:
INNLOGGER_URL=https://logger.example.com INNLOGGER_API_KEY=ilv_xxxxxxxxx INNLOGGER_API_SECRET=ils_xxxxxxxxx INNLOGGER_LOG_LEVEL=2 INNLOGGER_TIMEOUT=2 INNLOGGER_ENABLED=true INNLOGGER_ENVIRONMENT=production INNLOGGER_AUTO_EXCEPTION=true
| Env | Config key | Default | Meaning |
|---|---|---|---|
INNLOGGER_ENABLED |
enabled |
true |
Master switch. false = no network calls at all. |
INNLOGGER_URL |
url |
– | Portal base URL (the SDK appends /api/v1/...). |
INNLOGGER_API_KEY |
api_key |
– | Project API key (ilv_...). |
INNLOGGER_API_SECRET |
api_secret |
– | Signing secret (ils_...). Never sent over the wire. |
INNLOGGER_LOG_LEVEL |
log_level |
2 |
Transmission threshold, 0–7 (see below). |
INNLOGGER_TIMEOUT |
timeout |
2 |
Total request timeout, seconds. |
INNLOGGER_CONNECT_TIMEOUT |
connect_timeout |
1 |
Connect timeout, seconds. |
INNLOGGER_ENVIRONMENT |
environment |
APP_ENV |
Environment name sent with each event. |
INNLOGGER_APPLICATION |
application |
APP_NAME |
Application name. |
INNLOGGER_APP_VERSION |
application_version |
– | Sent in heartbeats and event metadata. |
INNLOGGER_HOSTNAME |
hostname |
gethostname() |
Hostname override. |
INNLOGGER_CATEGORY |
category |
application |
Default category (exceptions use exception). |
INNLOGGER_AUTO_EXCEPTION |
auto_exception |
false |
Report exceptions from Laravel's exception handler. |
INNLOGGER_EXCEPTION_LEVEL |
exception_level |
2 |
Severity for exceptions. |
INNLOGGER_FAIL_SILENT |
fail_silent |
true |
See Failure behaviour. |
INNLOGGER_ALLOW_INSECURE |
allow_insecure |
false |
Permit http:// URLs (local development only). |
INNLOGGER_RETRIES |
retries |
1 |
Extra attempts for transient failures (max 5). |
INNLOGGER_RETRY_DELAY_MS |
retry_delay_ms |
100 |
Delay before retry n is n × delay. |
INNLOGGER_TRANSPORT |
transport |
laravel |
laravel (HTTP client, works with Http::fake()) or guzzle. |
INNLOGGER_DIAGNOSTICS_CHANNEL |
diagnostics_channel |
– | Local log channel for delivery failures (e.g. single). |
INNLOGGER_CAPTURE_REQUEST |
capture.request |
true |
Capture route/method/URL/request ID/status. |
INNLOGGER_CAPTURE_USER |
capture.user |
true |
Capture the authenticated user ID. |
INNLOGGER_HEARTBEAT |
heartbeat.schedule |
false |
Schedule innlogger:heartbeat every five minutes. |
| – | redact_fields |
see file | Extra keys to redact. |
| – | mask_patterns |
[] |
Extra regex => replacement masking rules. |
| – | limits.* |
16 KB / 64 KB | Message, trace, context and metadata size limits. |
Usage
use Cbagdawala\InnLogger\Laravel\Facades\InnLogger; InnLogger::critical('System failure', $context); InnLogger::error('Payment failed', ['category' => 'payment', 'order_id' => $order->id]); InnLogger::warning('Slow API response', ['ms' => 3200]); InnLogger::notice('Important event'); InnLogger::info('Customer created', ['customer_id' => 12]); InnLogger::debug('Calculated values', $values); InnLogger::trace('Detailed diagnostic'); InnLogger::exception($exception, ['order_id' => 5]); // ERROR by default InnLogger::exception($exception, [], [], 1); // as CRITICAL
$contextis free-form data (redacted, max 64 KB). A stringcategorykey becomes the event category; aThrowableunderexceptionbecomes the normalized exception.- An optional third argument sets top-level event fields:
category,request_id,user_id,url,http_method,http_status,file,line,metadata,occurred_at,event_id. - Every call returns a
SendResult(successful(),status,reason,httpStatus,eventId,logId,duplicate()), which you can ignore.
Log channel
The package registers an innlogger log driver and, unless you define one yourself, an
innlogger channel:
Log::channel('innlogger')->error('Payment failed', ['category' => 'payment']);
Add it to your stack to mirror normal logging (the threshold still applies):
// config/logging.php 'stack' => ['driver' => 'stack', 'channels' => ['daily', 'innlogger']], 'innlogger' => ['driver' => 'innlogger', 'level' => 'debug'], // or: ['driver' => 'custom', 'via' => \Cbagdawala\InnLogger\Laravel\Logging\CreateInnLoggerLogger::class]
Monolog levels map to: emergency/alert/critical → 1, error → 2, warning → 3,
notice → 4, info → 5, debug → 6. The channel name and Monolog level go into metadata.
Automatic exception reporting
With INNLOGGER_AUTO_EXCEPTION=true, the SDK adds a reportable() callback to Laravel's
exception handler. It runs alongside Laravel's normal reporting and never replaces it,
respects dontReport, and never throws. If the innlogger channel is also in your log
stack, the same exception object is sent only once.
Request context
For HTTP requests the SDK adds the route (name or URI), HTTP method, URL (path and query, with sensitive parameters redacted), request ID, authenticated user ID (only when a guard has already resolved the user, so no extra queries), hostname and application version. It never captures request bodies, Authorization headers, cookies or passwords.
To also record the response status code and a stable request ID (taken from
X-Request-Id or generated), add the middleware:
// bootstrap/app.php (Laravel 11+) ->withMiddleware(fn (Middleware $m) => $m->append(\Cbagdawala\InnLogger\Laravel\Http\CaptureRequestContext::class))
Heartbeat
php artisan innlogger:heartbeat posts {environment, hostname, application_version} to
/api/v1/heartbeat. Schedule it yourself or set INNLOGGER_HEARTBEAT=true.
Heartbeats honour enabled but not the log threshold.
Artisan commands
php artisan innlogger:test # sends a test event (bypasses the threshold) and reports config # validity, reachability, authentication, status and event ID php artisan innlogger:status # shows the configuration (secret never printed) and sends a heartbeat php artisan innlogger:status --offline
Importing existing log files
innlogger:import backfills what is already in storage/logs (for example, the history from
before you installed the SDK):
php artisan innlogger:import --dry-run # count what would be sent, send nothing php artisan innlogger:import # storage/logs/laravel*.log php artisan innlogger:import storage/logs/laravel-2026-09-*.log --since=2026-09-01 --level=warning
- Entries keep their original time. Times without an offset are read in
app.timezone. - Imported events never trigger email alerts, and they don't count as recent activity
in the portal. Pass
--alertsif you do want your notification rules to run on them. - Re-running is safe: each entry gets an event ID derived from its content, and the portal skips IDs it already stored. If an import stops (network, rate limit), run the same command again.
--leveldefaults toINNLOGGER_LOG_LEVEL(2 = ERROR and CRITICAL);--untilstops before a time;--environmentoverrides the environment written in each entry.--ratecaps events per second (default 50; the portal allows 10,000 per minute per project, shared with live traffic). A rate-limited import waits and carries on.- Stack traces, the exception class, file and line are rebuilt from the log text. Context is redacted like live events. Anything older than the project's retention period is removed by the portal's next nightly cleanup.
Requires an InnLogger portal that supports imported events (2026-09-27 or later). An older
portal stores the events but sends alerts for them, so do a --dry-run and check with the
portal's administrator first.
Threshold semantics
Severity: 0 OFF, 1 CRITICAL, 2 ERROR, 3 WARNING, 4 NOTICE, 5 INFO, 6 DEBUG, 7 TRACE.
An event is sent when level <= INNLOGGER_LOG_LEVEL; 0 sends nothing.
INNLOGGER_LOG_LEVEL |
Sent |
|---|---|
| 0 | nothing |
| 2 | CRITICAL, ERROR |
| 5 | CRITICAL … INFO |
| 7 | everything |
The threshold only controls what is transmitted. Alerting is configured with notification rules in the portal.
Wire protocol
POST {url}/api/v1/logs and POST {url}/api/v1/heartbeat, JSON body, headers
X-InnLogger-Key, X-InnLogger-Timestamp (unix seconds), X-InnLogger-Nonce (random,
per attempt), X-InnLogger-Signature, X-InnLogger-Request-Id, where
signature = hex(HMAC-SHA256(timestamp + "\n" + nonce + "\n" + raw_body, api_secret))
Each event gets a UUID v4 event_id before sending. Retries resend the same body, and so the
same event_id (the portal de-duplicates on it), with a fresh timestamp, nonce and signature.
Failure behaviour
- A short timeout (2 s, connect 1 s) and at most
retriesextra attempts, only for connection errors/timeouts and HTTP 502/503/504. 401/403/413/422 are not retried. The worst case per event is roughly(retries + 1) × timeout. - HTTP 429 pauses sending for
retry_afterseconds (in this PHP process); events in that window are dropped rather than queued. - HTTP 200 (duplicate
event_id) counts as success. - Every error (transport, serialization, context capture) is caught. With
fail_silent=true(the default) calls return a failedSendResultand nothing is thrown. Withfail_silent=false, directInnLogger::*calls throwInnLoggerException(useful while developing); the log channel and the automatic exception hook still never throw. - An InnLogger failure is never logged back into InnLogger: a re-entrancy guard drops any
event produced while a send is in progress. Local diagnostics go only to
diagnostics_channel(off by default) and never include payloads, headers or secrets. - Missing configuration or a plain
http://URL disables sending; no request is made. - Sending is synchronous (no queue); keep the timeout short.
Security notes
- Keep
INNLOGGER_API_SECRETin.env/your secret store only. It signs requests and is never transmitted; if it appears inside a logged string, it is masked. - HTTPS is required.
INNLOGGER_ALLOW_INSECURE=trueis for local development only. Redirects are not followed. - Redaction is recursive and case-insensitive (
-and_are treated alike). Always redacted:password,password_confirmation,current_password,new_password,token,access_token,refresh_token,id_token,authorization,proxy_authorization,cookie,set_cookie,card_number,cvv,cvc,secret,api_secret,client_secret,api_key,private_key,csrf_token,xsrf_token,x_xsrf_token,_token, plus yourredact_fields. The same keys are redacted in URL query strings. - Masking rules replace
Bearer/Basic/Digestcredentials andils_...secrets in any string (message, exception message, trace, context, metadata); add your own withmask_patterns. - Treat logs as sensitive data: avoid logging personal or payment details in the first place. Redaction is a safety net, not a licence.
- Limits: message 16 KB, trace 64 KB (truncated), context and metadata 64 KB each
(replaced by a
_truncatedmarker if larger), so the body stays below the portal's 256 KB limit.
Framework-agnostic core
Client, Config, Signer, PayloadBuilder, Redactor, Severity and
Transport\GuzzleTransport have no Laravel dependency:
$client = new \Cbagdawala\InnLogger\Client( new \Cbagdawala\InnLogger\Config(['url' => ..., 'api_key' => ..., 'api_secret' => ..., 'log_level' => 2]), new \Cbagdawala\InnLogger\Transport\GuzzleTransport(), ); $client->error('Payment failed', ['order_id' => 1]);
Testing the package
This repository is a read-only mirror, published automatically from the private InnLogger repository. Pull requests here would be overwritten; please open an issue instead.
composer install ./vendor/bin/phpunit