Standalone request tracing package for Laravel. Captures outgoing HTTP and SOAP requests, and optionally incoming requests. All traces are stored asynchronously via queued jobs.
- PHP 8.4+
- Laravel 12+
ext-soapandext-simplexml(only if using SOAP tracing)
composer require kolaybi/request-tracerPublish the config:
php artisan vendor:publish --tag=request-tracer-config
php artisan migrate// config/kolaybi/request-tracer.php
return [
'connection' => env('REQUEST_TRACER_DB_CONNECTION'),
'schema' => env('REQUEST_TRACER_DB_SCHEMA'),
'queue_connection' => env('REQUEST_TRACER_QUEUE_CONNECTION', 'redis'),
'queue' => env('REQUEST_TRACER_QUEUE', 'request_logging'),
'tenant_column' => 'tenant_id',
'tenant_cast' => 'integer', // 'integer', 'string', or any Eloquent cast type
'user_cast' => 'integer', // 'integer', 'string', or any Eloquent cast type
'max_body_size' => (int) env('REQUEST_TRACER_MAX_BODY_SIZE', 0),
'retention_days' => (int) env('REQUEST_TRACER_RETENTION_DAYS', 0),
'mask_sensitive' => (bool) env('REQUEST_TRACER_MASK_SENSITIVE', false),
'mask_value' => env('REQUEST_TRACER_MASK_VALUE', '[REDACTED]'),
'sensitive_keys' => env(
'REQUEST_TRACER_SENSITIVE_KEYS',
'authorization,proxy-authorization,cookie,set-cookie,x-api-key,api-key,apikey,token,access_token,refresh_token,id_token,password,passcode,secret,client_secret,private_key',
),
'context_provider' => null,
'outgoing' => [
'enabled' => env('REQUEST_TRACER_OUTGOING_ENABLED', true),
'table' => 'outgoing_request_traces',
'model' => OutgoingRequestTrace::class,
'sample_rate' => (float) env('REQUEST_TRACER_OUTGOING_SAMPLE_RATE', 1.0),
'only' => env('REQUEST_TRACER_OUTGOING_ONLY', ''), // Comma-separated host/path patterns (supports wildcards: 'api.example.com*')
'except' => env('REQUEST_TRACER_OUTGOING_EXCEPT', ''), // Comma-separated host/path patterns (supports wildcards: '*.internal.com*')
],
'incoming' => [
'enabled' => env('REQUEST_TRACER_INCOMING_ENABLED', false),
'table' => 'incoming_request_traces',
'model' => IncomingRequestTrace::class,
'sample_rate' => (float) env('REQUEST_TRACER_INCOMING_SAMPLE_RATE', 1.0),
'only' => env('REQUEST_TRACER_INCOMING_ONLY', ''), // Comma-separated paths (supports wildcards: 'api/orders*')
'except' => env('REQUEST_TRACER_INCOMING_EXCEPT', ''), // Comma-separated paths (supports wildcards: 'health*,telescope*')
'capture_response_body' => (bool) env('REQUEST_TRACER_INCOMING_CAPTURE_RESPONSE', false),
'channel_header' => env('REQUEST_TRACER_INCOMING_CHANNEL_HEADER'), // Header name to read channel from (e.g. 'Channel')
],
];If REQUEST_TRACER_MASK_SENSITIVE=true, matching keys in headers and JSON/form payloads are masked before storage.
Add RequestTracerMiddleware to your middleware stack. It generates a trace_id for every request and records incoming traces when enabled.
// bootstrap/app.php
use KolayBi\RequestTracer\Middleware\RequestTracerMiddleware;
->withMiddleware(function (Middleware $middleware) {
$middleware->prepend(RequestTracerMiddleware::class);
})The middleware accepts an optional channel parameter to tag incoming traces by route group:
// Per route group — channel set via middleware parameter
Route::middleware([RequestTracerMiddleware::class.':web'])->group(...)
Route::middleware([RequestTracerMiddleware::class.':mobile'])->group(...)
// Channel from request header — set incoming.channel_header in config
// Header value takes priority over middleware parameterImplement TraceContextProvider to supply tenant, user, and server information:
use KolayBi\RequestTracer\Contracts\TraceContextProvider;
class AppTraceContextProvider implements TraceContextProvider
{
public function tenantId(): int|string|null
{
return auth()->user()?->company_id;
}
public function userId(): int|string|null
{
return auth()->id();
}
public function clientIp(): ?string
{
return request()?->ip();
}
public function serverIdentifier(): ?string
{
return gethostname();
}
}Register it in your config:
'context_provider' => AppTraceContextProvider::class,Outgoing HTTP requests are traced automatically when outgoing.enabled is true. Use channel() or traceOf() to tag requests:
Http::channel('payment-gateway')->post('https://api.example.com/charge', $data);
Http::traceOf('bank-api')
->withTraceExtra(['order_id' => 123])
->get('https://bank.example.com/status');Extend TracingSoapClient for traced SOAP calls:
use KolayBi\RequestTracer\Soap\TracingSoapClient;
$client = TracingSoapClient::with('https://service.example.com?wsdl');
$client->channel('e-invoice')->SomeOperation($params);TracingSoapClient supports lazy initialization — set the WSDL later if needed:
$client = new TracingSoapClient();
$client->setWsdl('https://service.example.com?wsdl');
$client->setOptions(['soap_version' => SOAP_1_2]);Enable in config:
REQUEST_TRACER_INCOMING_ENABLED=trueThe middleware records every incoming request with method, path, route, status, timing, headers, and optionally the response body.
Note:
response_sizeis always recorded regardless of thecapture_response_bodysetting. It is read from the SymfonyResponsecontent when available, and falls back to theContent-Lengthheader when content is unavailable.
Display a chronological waterfall of all traces linked to a trace_id:
php artisan request-tracer:waterfall 01JEXAMPLE123The command first prints a summary header:
Trace ID …………………………………………………… 01JEXAMPLE123
Tenant ID ………………………………………………… 42
User ID ……………………………………………………… 7
Client IP ………………………………………………… 192.168.1.10
First Start …………………………………………… 2026-02-28 12:00:00.000
Last End …………………………………………………… 2026-02-28 12:00:01.250
Total Duration …………………………………… 1250ms
Followed by a waterfall table of all traces sorted by start time:
+---+-----+----------+------+-----------------------------------------------+--------+----------+-----------------+--------+-------------------------+
| # | ID | Type | Method | Endpoint | Status | Duration | Channel | Server | Start |
+---+-----+----------+------+-----------------------------------------------+--------+----------+-----------------+--------+-------------------------+
| 1 | 501 | INCOMING | POST | api.example.com/webhooks (api/webhooks) | 200 | 1250ms | — | web-01 | 2026-02-28 12:00:00.000 |
| 2 | 830 | OUTGOING | GET | https://bank.example.com/status?ref=abc | 200 | 320ms | bank-api | web-01 | 2026-02-28 12:00:00.100 |
| 3 | 831 | OUTGOING | POST | https://payment.example.com/charge | 201 | 890ms | payment-gateway | web-01 | 2026-02-28 12:00:00.350 |
+---+-----+----------+------+-----------------------------------------------+--------+----------+-----------------+--------+-------------------------+
This is useful for inspecting the full request flow — incoming request plus all outgoing calls it triggered — in chronological order.
Drill into a single trace by its ULID with progressive verbosity:
# Metadata only (type, method, endpoint, status, duration, timing, sizes, etc.)
php artisan request-tracer:inspect 01JEXAMPLE123
# + request/response headers
php artisan request-tracer:inspect 01JEXAMPLE123 -v
# + request/response body (truncated to 20 lines)
php artisan request-tracer:inspect 01JEXAMPLE123 -vv
# + body at 40 lines + exception, message, stats, extra (outgoing)
php artisan request-tracer:inspect 01JEXAMPLE123 -vvv
# Everything, no truncation
php artisan request-tracer:inspect 01JEXAMPLE123 --fullThe command searches both incoming and outgoing tables automatically — no need to specify which.
Rotate trace tables daily — the current table is atomically swapped with a fresh empty one, and the old data moves to a dated archive table (e.g. outgoing_request_traces_20260309). Archives older than retention_days are dropped automatically.
php artisan request-tracer:rotate
php artisan request-tracer:rotate --days=30Schedule it to run daily:
$schedule->command('request-tracer:rotate')->daily();Alternatively, delete old rows from the current table in chunks:
php artisan request-tracer:purge --days=30
php artisan request-tracer:purge --days=90 --chunk=10000Or set retention_days in config and schedule it:
$schedule->command('request-tracer:purge')->daily();RequestSendinglistener stores per-request trace metadata (started_at) in request attributes andRequestTimingStoreHttp::channel()/Http::traceOf()andHttp::withTraceExtra()set per-request metadata inrequest_tracerattributes- Event listeners (
ResponseReceived,ConnectionFailed) build trace attributes and dispatchStoreTraceJob - Any
X-Trace-*headers present on requests/responses are stripped before persisting
RequestTracerMiddlewaregenerates atrace_idand records start time- After the response is returned,
IncomingTraceRecorderbuilds attributes and dispatchesStoreTraceJob - The same
trace_idlinks incoming and outgoing traces within a request
TracingSoapClientstores channel/extra as instance properties__doRequest()dispatches events with timing, channel, and extra- Properties are reset after each call (no leakage between calls)
- The middleware does not run on workers, but outgoing tracing works identically
trace_idis lazily generated viaContexton the first outgoing call- Laravel resets
Contextbetween queue jobs, so each job gets a freshtrace_id
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please see License File for more information.