processhub / laravel-logs
ProcessHub centralized logging for Laravel applications — ships logs, exceptions, and deploy markers to ProcessHub's /api/ingest endpoints
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.5
- laravel/framework: ^10.0|^11.0|^12.0
- monolog/monolog: ^3.0
Requires (Dev)
- larastan/larastan: ^2.9|^3.0
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpstan/phpstan: ^1.11|^2.0
- phpunit/phpunit: ^10.0|^11.0
README
Ships logs, exceptions, and deploy markers from your Laravel application to ProcessHub's centralised observability module. Get grouped exceptions, deploy-marker annotations on your event graph, and a single requestId trail across every log line of a user request.
Status: MVP — production-ready for Laravel 10/11/12 on PHP 8.1+.
Install
composer require processhub/laravel-logs php artisan processhub:install
Then, per the install output:
-
Add credentials to
.env:PROCESSHUB_LOG_URL=https://app.processhub.io PROCESSHUB_LOG_TOKEN=ph_live_<your-token>Get the token at
ProcessHub → Приложения → <your app> → Интеграция → Выпустить токен. Copy immediately — it won't be shown again. -
Register the logging channel in
config/logging.php:'channels' => [ // … existing channels 'processhub' => [ 'driver' => 'custom', 'via' => ProcessHub\Logs\Logging\ProcessHubFactory::class, 'level' => env('LOG_LEVEL', 'warning'), ], ], 'stack' => [ 'driver' => 'stack', 'channels' => ['single', 'processhub'], 'ignore_exceptions' => false, ],
-
Verify end-to-end:
php artisan processhub:test
You should see a synthetic ERROR appear in the ProcessHub application detail page within a second.
That's it. Every Log::error/warning/info (above the channel's level) now queues an async batch to ProcessHub.
What it does
- Log::error/warning/info → ApplicationLog rows in ProcessHub, with structured context (exception class + stack trace when a
Throwableis in context). - Exception grouping — ProcessHub computes a stable fingerprint from
class + normalized message + top framesoUser 1234 not foundandUser 9876 not foundgroup together; regressions (resolved → new occurrence) flip the group back toopenand emit a pipeline trigger. - Heartbeat —
processhub:heartbeatruns every minute via the app's scheduler. Status flips toOFFLINEafter 3 missed beats. - Request-id correlation —
CorrelateRequestIdmiddleware propagatesX-Request-Idthrough Monolog's shared context; ProcessHub UI pivots on it to show every log line of one HTTP request. - Deploy markers — run
php artisan processhub:deploy "$RELEASE" --commit="$SHA"as the last step of your Forge/Envoyer/CI deploy script. The «Релизы» tab shows the timeline; open exception groups auto-resolve when their next deploy lands. Pass--failedto record an unsuccessful deploy. Without--committhe command triesgit rev-parse HEAD. Version is a positional argument, not--version(Symfony reserves the latter for printing the framework version).ProcessHub::markDeploy(...)facade also works for in-process callers. - Queue-based delivery — all batches go through a dedicated queue (
logsby default). Retry on 5xx / network, honourRetry-Afteron 429, fall back to a local file on exhausted attempts so nothing is lost. - PII redaction at the source —
password/token/authorization/api_key/cookiekeys become[REDACTED]; emails / JWTs /Bearer …/ credit-card numbers in message strings are masked before leaving the app. - Structured event listeners — failed queue jobs, skipped/failed scheduled tasks become typed log entries (
contextType=job/scheduled). Slow-query capture is available but off by default.
Configuration
See config/processhub.php after publishing. Highlights:
| Env | Default | What |
|---|---|---|
PROCESSHUB_LOG_URL |
— | Base URL of your ProcessHub tenant |
PROCESSHUB_LOG_TOKEN |
— | ph_live_<orgSlug>_<appSlug>_<rand> |
PROCESSHUB_LOG_QUEUE |
logs |
Queue name for SendLogBatchJob |
PROCESSHUB_LOG_CONNECTION |
default | Queue connection (redis, database, etc.) |
PROCESSHUB_LOG_BATCH_SIZE |
100 |
Max entries per batch (ProcessHub's hard limit) |
PROCESSHUB_LOG_TIMEOUT_MS |
5000 |
HTTP timeout |
PROCESSHUB_HEARTBEAT_ENABLED |
true |
Disable if your env doesn't run the scheduler |
PROCESSHUB_LOG_SLOW_QUERIES |
false |
Emit slow queries as logs |
PROCESSHUB_SLOW_QUERY_MS |
1000 |
Threshold when slow-query logging is on |
Custom redaction keys / patterns live in config/processhub.php under redact.keys and redact.patterns.
Commands
| Command | What |
|---|---|
processhub:install |
Publish config + print manual-step checklist |
processhub:test |
Direct POST (no queue) to verify credentials / network |
processhub:heartbeat |
Single heartbeat ping; auto-scheduled every minute |
processhub:flush-fallback |
Re-ingest batches saved to storage/logs/processhub-fallback.log during outages |
processhub:payouts:push |
Incremental push of registered payout rows (auto-scheduled on payouts.default_cron) |
You can wire processhub:flush-fallback into your own schedule if you want more aggressive retries — the package doesn't schedule it automatically.
Payouts
When your application is the data source for the ProcessHub Payouts module (tax-agent payout aggregation, see ProcessHub docs — Payouts module), the package can ship payment rows to the same ingest tenant — no extra token, no extra endpoint to configure.
1. Register the model
In app/Providers/AppServiceProvider.php::boot:
use ProcessHub\Logs\Payouts\Payouts; use App\Models\Payment; Payouts::register( model: Payment::class, map: fn (Payment $p) => [ 'gatewayPaymentId' => (string) $p->id, // stable dedup key 'paymentCreatedAt' => $p->created_at->toIso8601String(), 'rawStatus' => $p->status_text, // verbatim, ProcessHub maps it 'isCompleted' => (bool) $p->completed, 'isFatalError' => (bool) $p->fatal_error, 'grossAmount' => (string) $p->amount, // string to keep кoпейки intact // Optional core ↓ 'externalTxnId' => $p->transaction_id, 'gatewayUpdatedAt' => $p->updated_at?->toIso8601String(), 'errorReason' => $p->result_message, 'recipientPhone' => $p->phone, 'recipientName' => $p->fio, 'recipientMaskedCard' => $p->card_masked, // Anything else ProcessHub-side RAW columns may need ↓ 'rawData' => $p->only(['method', 'tochka', 'service']), ], query: fn ($q) => $q->where('type', 'PR'), // optional scope );
That's all the client code change you need. Everything else (HTTP, retries, watermark, observer hookup, scheduler) is wired in the service provider.
2. How it ships
| Mode | When | Configured by |
|---|---|---|
| Cron | processhub.payouts.default_cron (overridden by payoutSource.cadence.cronExpr from server) |
Schedule hook in the service provider |
| On status change | Eloquent created()/updated() events on the registered model |
Auto-registered observer when payoutSource.cadence.mode ∈ ['on-status-change', 'both'] |
| Manual | Payouts::push($payment) / Payouts::queue($payment) |
Anywhere in your code |
The high-watermark (max gatewayPaymentId shipped so far) lives in storage/app/processhub-payouts-watermark.json. It survives cache:clear and falls back to the server-provided payoutSource.watermark hint when missing — bootstrap is safe to restart.
3. Env reference
| Env | Default | What |
|---|---|---|
PROCESSHUB_PAYOUTS_ENABLED |
true |
Hard kill-switch (server payoutSource.enabled overrides) |
PROCESSHUB_PAYOUTS_QUEUE |
default |
Queue for PushSinglePayoutJob (observer-driven) |
PROCESSHUB_PAYOUTS_CONNECTION |
— | Queue connection override |
PROCESSHUB_PAYOUTS_DEFAULT_CRON |
0 * * * * |
Used until the first heartbeat-config refresh pulls server cadence |
4. Failure semantics
- Server returns 4xx other than 429 → command exits FAILURE, watermark stays put, scheduler email fires. Fix the cause on the ProcessHub side (token, source config, formulas) and the next cron tick resumes.
- 429 / 5xx → up to 3 retries with
Retry-After-aware backoff inside one push; if still failing, watermark stays put and the next cron tick re-tries. - Bad mapper row (mapper threw
InvalidArgumentExceptionon shape) → that row is skipped with aWARNINGlog; the rest of the batch ships. - 413 Payload Too Large → batch halved and retried; up to 3 split levels before giving up.
- No model registered → command exits SUCCESS silently with an
INFOlog; observer doesn't bind to anything.
How it fails
- Network down —
SendLogBatchJobretries 3× with exponential backoff. On exhaustion,failed()appends the batch as JSON tostorage/logs/processhub-fallback.log. Runphp artisan processhub:flush-fallbackonce the network is back. - 4xx from ProcessHub (bad token, revoked token) — jobs fail immediately (no retries); batch appended to fallback file for later re-ingest after you fix config.
- 429 rate limit — batch is released back to the queue with the
Retry-Afterdelay (no lost time on exponential backoff that doesn't fit ProcessHub's rate-limit window). Log::errorbefore config is set — handler silently drops (the install command warns you).
Testing
composer install vendor/bin/phpunit
Tests use Orchestra Testbench. There are unit tests for Redactor (pattern coverage), CorrelateRequestId middleware (validation + generation), and SendLogBatchJob (retry behaviour against a mocked Guzzle client).
Contract with ProcessHub
This package targets the ingest contract documented in ProcessHub docs — Applications module. Key limits (as of 2026-04):
- 100 entries per batch
- 2 MB max payload
- 60 requests/min per token (sliding window)
- 16 KB max message; longer is truncated server-side
- 10 max JSON depth in context
If ProcessHub changes the contract, bump the package's minor version so apps can upgrade in lock-step.
License
MIT — see LICENSE.