TraceReplay

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

MethodDescription
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);
}
AssertionDescription
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.

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:

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.

MethodParamsReturns
list_traceslimit, status, filter_by_errorPaginated trace summaries
get_trace_contexttrace_id, step_limitTrace context with capped steps
generate_fix_prompttrace_idMarkdown debugging prompt
trigger_replaytrace_idReplay 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