TraceReplay Documentation
Production-conscious step-level tracing, deterministic HTTP replay, and AI-ready debugging for Laravel.
TraceReplay is not a standard error logger. It is a full-fledged execution tracer that captures every step of your complex workflows, reconstructs them with a waterfall timeline, and offers one-click AI debugging when things go wrong. It supports Laravel 10, 11, 12, and 13.
Installation
composer require iazaran/trace-replay
Run the installer:
php artisan trace-replay:install
Or publish the config manually:
php artisan vendor:publish --tag=trace-replay-config
Run migrations:
php artisan migrate
Migrations run automatically without publishing. They use json columns for full MySQL, MariaDB, PostgreSQL, and SQLite compatibility.
⚠️ Important When Updating: Package migrations do not execute automatically upon composer update. Whenever you update TraceReplay to a newer version, you must run php artisan migrate to apply any newly introduced schema changes.
Publishing Views
TraceReplay ships with a polished, dark-themed dashboard featuring a waterfall timeline, syntax-highlighted JSON inspector, and live stats — all styled and ready to use out of the box. Publishing the views lets you customise the layout, colours, or add your own branding:
php artisan vendor:publish --tag=trace-replay-views
This copies the Blade templates to resources/views/vendor/trace-replay/ where you can edit them freely. The package will automatically use your published versions instead of its built-in views.
Recommended: Enable HTTP Middleware
To automatically trace all HTTP requests, add the TraceMiddleware to your application. This provides instant visibility into every request without manual instrumentation.
For Laravel 10 (app/Http/Kernel.php):
protected $middlewareGroups = [
'web' => [
// ...
\TraceReplay\Http\Middleware\TraceMiddleware::class,
],
];
For Laravel 11+ (bootstrap/app.php):
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\TraceReplay\Http\Middleware\TraceMiddleware::class);
})
Configuration
Key options in config/trace-replay.php:
return [
'enabled' => env('TRACE_REPLAY_ENABLED', true),
'sample_rate' => env('TRACE_REPLAY_SAMPLE_RATE', 1.0), // 0.1 = 10% of requests
'project_id' => env('TRACE_REPLAY_PROJECT_ID', null),
'mask_fields' => ['password', 'password_confirmation', 'token', 'api_key',
'authorization', 'secret', 'credit_card', 'cvv', 'ssn', 'private_key'],
'track_db_queries' => env('TRACE_REPLAY_TRACK_DB', true),
'middleware' => ['web', 'auth'], // add 'can:view-trace-replay' for production
'route_prefix' => env('TRACE_REPLAY_ROUTE_PREFIX', 'trace-replay'),
'api' => [
'token' => env('TRACE_REPLAY_API_TOKEN'), // API disabled until set
'middleware' => ['api'],
'route_prefix' => env('TRACE_REPLAY_API_ROUTE_PREFIX', 'api/trace-replay'),
'max_steps' => env('TRACE_REPLAY_API_MAX_STEPS', 500),
],
'allowed_ips' => array_filter(explode(',', env('TRACE_REPLAY_ALLOWED_IPS', ''))),
'queue' => [
'enabled' => env('TRACE_REPLAY_QUEUE_ENABLED', false),
'connection' => env('TRACE_REPLAY_QUEUE_CONNECTION', env('QUEUE_CONNECTION', 'sync')),
'queue' => env('TRACE_REPLAY_QUEUE_NAME', 'default'),
],
'replay' => [
'default_base_url' => env('TRACE_REPLAY_REPLAY_URL', env('APP_URL')),
'timeout' => env('TRACE_REPLAY_REPLAY_TIMEOUT', 30),
'allow_mutating_methods' => env('TRACE_REPLAY_REPLAY_MUTATING', false),
'allowed_hosts' => array_filter(explode(',', env('TRACE_REPLAY_REPLAY_ALLOWED_HOSTS', ''))),
],
'retention_days' => env('TRACE_REPLAY_RETENTION_DAYS', 30),
'notifications' => [
'on_failure' => env('TRACE_REPLAY_NOTIFY_ON_FAILURE', false),
'channels' => ['mail'],
'mail' => ['to' => env('TRACE_REPLAY_NOTIFY_EMAIL')],
'slack' => ['webhook_url' => env('TRACE_REPLAY_SLACK_WEBHOOK')],
],
'ai' => [
'driver' => env('TRACE_REPLAY_AI_DRIVER', 'openai'), // openai | anthropic | ollama
'api_key' => env('TRACE_REPLAY_AI_KEY'),
'model' => env('TRACE_REPLAY_AI_MODEL', 'gpt-4o'),
'base_url' => env('TRACE_REPLAY_AI_BASE_URL'),
],
'auto_trace' => [
'jobs' => env('TRACE_REPLAY_AUTO_TRACE_JOBS', true),
'commands' => env('TRACE_REPLAY_AUTO_TRACE_COMMANDS', false),
'livewire' => env('TRACE_REPLAY_AUTO_TRACE_LIVEWIRE', true),
'capture_job_payload' => env('TRACE_REPLAY_CAPTURE_JOB_PAYLOAD', false),
'exclude_commands' => ['queue:work', 'queue:listen', 'horizon', 'schedule:run',
'schedule:work', 'trace-replay:prune', 'trace-replay:export'],
],
];
Laravel 11+ applications do not always define an api middleware group. If your app does not, set trace-replay.api.middleware to middleware that exists in your project, such as ['throttle:api'].
Manual Tracing
Wrap any complex logic in TraceReplay::step(). Each callback's return value is passed through transparently.
use TraceReplay\Facades\TraceReplay;
class BookingService
{
public function handleBooking(array $payload): void
{
TraceReplay::start('Flight Booking', ['channel' => 'web']);
try {
$inventory = TraceReplay::step('Validate Inventory', function () use ($payload) {
return Inventory::check($payload['flight_id']);
});
TraceReplay::checkpoint('Inventory OK', ['seats' => $inventory->seats]);
TraceReplay::context(['user_tier' => auth()->user()->tier]);
TraceReplay::step('Charge Credit Card', function () use ($payload) {
return PaymentGateway::charge($payload['amount']);
});
TraceReplay::end('success');
} catch (\Exception $e) {
TraceReplay::end('error');
throw $e;
}
}
}
API Reference
| Method | Description |
|---|---|
TraceReplay::start(name, tags[]) | Start a new trace; returns Trace or null |
TraceReplay::step(label, callable, extra[]) | Wrap callable — records timing, memory, DB queries, cache, HTTP, mail, logs & errors |
TraceReplay::measure(label, callable) | Alias for step() — semantic clarity for benchmarks |
TraceReplay::checkpoint(label, state[]) | Zero-overhead breadcrumb (no callable) |
TraceReplay::context(array) | Merge data into the next step's state_snapshot |
TraceReplay::end(status) | Finalise trace; status: 'success' or 'error' |
TraceReplay::getCurrentTrace() | Returns the active Trace model or null |
TraceReplay::setWorkspaceId(id) | Scope subsequent traces to a workspace |
TraceReplay::setProjectId(id) | Scope subsequent traces to a project |
Testing Helper
Use TraceReplay::fake() in tests to assert on your instrumentation without hitting the database. The fake captures all start(), step(), checkpoint(), and end() calls in memory.
use TraceReplay\Facades\TraceReplay;
public function test_booking_records_steps()
{
$fake = TraceReplay::fake();
$this->post('/book', ['flight_id' => 123]);
$fake->assertTraceStarted('Flight Booking');
$fake->assertStepRecorded('Validate Inventory');
$fake->assertCheckpointRecorded('Inventory validated');
$fake->assertTraceEnded('success');
$fake->assertTraceCount(1);
}
| Assertion | Description |
|---|---|
assertTraceStarted(name) | Assert a trace with the given name was started |
assertNoTraceStarted() | Assert no trace was started at all |
assertTraceCount(n) | Assert exactly n traces were started |
assertStepRecorded(label) | Assert a step with the given label was recorded |
assertCheckpointRecorded(label) | Assert a checkpoint with the given label was recorded |
assertStepCount(n, traceName?) | Assert exactly n steps in total (or in a named trace) |
assertTraceEnded(status) | Assert a trace with the given final status exists |
Auto Tracing — Jobs, Commands & Livewire
Queue jobs are automatically traced when auto_trace.jobs is enabled (default: true). No manual listener registration needed — the service provider wires everything up.
Artisan commands can be auto-traced by enabling auto_trace.commands:
TRACE_REPLAY_AUTO_TRACE_COMMANDS=true
Internal commands like queue:work, horizon, and trace-replay:prune are excluded by default (see auto_trace.exclude_commands).
Livewire Component Tracing
By enabling auto_trace.livewire, TraceReplay hooks into Livewire v2/v3's global events to automatically record distinct checkpoints on the timeline whenever your Livewire components go through hydration or dehydration cycles. This brings immense visibility into how heavily your components are performing over the wire.
The Dashboard
Navigate to https://your-app.com/trace-replay.
- Waterfall timeline — visual duration bars relative to total trace time
- Live stats — auto-refreshing counters (total, failed, avg duration)
- Search & filter — by name, IP, user ID; toggle failed-only
- Step inspector — syntax-highlighted JSON payloads and state snapshots
- Replay engine — re-run any HTTP step and view a structural JSON diff
- AI Fix Prompt — one-click prompt for Cursor, ChatGPT, or Claude
Security
Add authentication or authorization middleware in config/trace-replay.php:
// config/trace-replay.php
'middleware' => ['web', 'auth', 'can:view-trace-replay'],
// AuthServiceProvider
Gate::define('view-trace-replay', fn ($user) =>
in_array($user->email, ['admin@example.com'])
);
Or restrict by IP (exact match, comma-separated via env):
TRACE_REPLAY_ALLOWED_IPS=203.0.113.5,10.0.0.1
Replay Safety
HTTP replay is conservative by default: non-GET methods are blocked, override_url can only target the originally recorded host, and sensitive headers such as Authorization, cookies, CSRF tokens, and forwarded headers are stripped before replay.
TRACE_REPLAY_REPLAY_ALLOWED_HOSTS=staging.example.com,*.internal.test
Failure Notifications
When trace-replay.notifications.on_failure is enabled, mail and Slack notifications are dispatched through a queued job after the response so slow webhooks do not add latency to failed requests.
Data Masking
TraceReplay automatically redacts configured keys from all request/response payloads before they touch the database — including deeply nested values:
// config/trace-replay.php
'mask_fields' => ['password', 'token', 'secret', 'api_key', 'credit_card'],
// Input: ['username' => 'alice', 'password' => 'hunter2']
// Stored: ['username' => 'alice', 'password' => '********']
Replay Engine
When an HTTP request fails, TraceReplay lets you deterministically replay it from the dashboard. The replay engine proxies the original request, captures the new response, and generates an exact structural JSON diff.
AI Debugging
For any failed trace the dashboard shows an AI Fix Prompt button. It generates a structured markdown prompt containing:
- Full execution timeline with per-step timing and DB query stats
- Exact error message, file, line number, and first 20 stack frames
- Request/response payloads (sensitive fields masked)
- Step-by-step state snapshots
No API Key Required
The AI prompt feature works without any API key. Simply copy the generated prompt and paste it into ChatGPT, Claude, or any other AI assistant of your choice.
Optional: Direct AI Integration
For a seamless experience, configure an AI driver to get answers directly in the dashboard. Three drivers are supported:
# OpenAI (default)
TRACE_REPLAY_AI_DRIVER=openai
TRACE_REPLAY_AI_KEY=sk-your-openai-key
TRACE_REPLAY_AI_MODEL=gpt-4o
# Or Anthropic Claude
TRACE_REPLAY_AI_DRIVER=anthropic
TRACE_REPLAY_AI_KEY=sk-ant-your-key
TRACE_REPLAY_AI_MODEL=claude-3-5-sonnet-latest
# Or Ollama (local, no API key needed)
TRACE_REPLAY_AI_DRIVER=ollama
TRACE_REPLAY_AI_MODEL=llama3
TRACE_REPLAY_AI_BASE_URL=http://localhost:11434/api/generate
With a key configured, clicking "Ask AI" sends the prompt to your chosen provider and displays the response directly in the dashboard.
MCP / AI-Agent JSON-RPC API
Autonomous agents can query TraceReplay over JSON-RPC 2.0 at POST /api/trace-replay/mcp.
The API is disabled until TRACE_REPLAY_API_TOKEN is configured. Send requests with an Authorization: Bearer your-token header.
| Method | Params | Returns |
|---|---|---|
list_traces | limit, status, filter_by_error | Paginated trace summaries |
get_trace_context | trace_id, step_limit | Trace context with capped steps |
generate_fix_prompt | trace_id | Markdown debugging prompt |
trigger_replay | trace_id | Replay result + JSON diff |
{
"jsonrpc": "2.0",
"method": "generate_fix_prompt",
"params": { "trace_id": "9b12f7e4-..." },
"id": 1
}
step_limit defaults to trace-replay.api.max_steps (500) and is capped at that value to avoid oversized responses.
Data Retention
Prune old traces automatically via the scheduler:
// app/Console/Kernel.php
$schedule->command('trace-replay:prune --days=30')->daily();
php artisan trace-replay:prune --days=30 --dry-run # preview
php artisan trace-replay:prune --days=7 --status=error # prune only error traces
Set TRACE_REPLAY_RETENTION_DAYS to choose the default window. If retention_days is null, pruning is a no-op unless --days is passed.
Export a trace for archiving:
php artisan trace-replay:export {id} --format=json
php artisan trace-replay:export {id} --format=csv
php artisan trace-replay:export {id} --format=json --output=/tmp/trace.json
php artisan trace-replay:export --status=error --format=json # all error traces
Diagnostics
php artisan trace-replay:doctor
The doctor command reports dashboard protection, API token status, sample rate, retention, replay safety, query binding capture, and whether the trace tables exist.
Testing
composer test
composer format:test
The test suite covers the full engine lifecycle, model scopes & accessors, payload masking (PII, case-insensitive), AI prompt & notification services, dashboard UI with date range filters, MCP JSON-RPC API, middleware (trace & auth), replay engine, log call tracking, TraceReplayFake assertions, Artisan commands (doctor, prune & export with validation), queue persistence, and Blade components — all using an in-memory SQLite database.
TraceReplay © 2026 — MIT License