diff --git a/README.md b/README.md
index a24bbf9..a618cac 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
> **High-fidelity process tracking, deterministic replay, and AI-powered debugging for Laravel β production & enterprise ready.**
-[](https://packagist.org/packages/iazaran/tracereplay)
+[](https://packagist.org/packages/iazaran/trace-replay)
[](https://php.net)
[](https://laravel.com)
[](LICENSE)
@@ -10,7 +10,7 @@
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.
-
+
---
@@ -40,14 +40,14 @@ TraceReplay is not a standard error logger. It is a full-fledged **execution tra
## π Installation
```bash
-composer require iazaran/tracereplay
+composer require iazaran/trace-replay
```
Publish the config and migrations:
```bash
-php artisan vendor:publish --tag=tracereplay-config
-php artisan vendor:publish --tag=tracereplay-migrations
+php artisan vendor:publish --tag=trace-replay-config
+php artisan vendor:publish --tag=trace-replay-migrations
```
Run migrations:
@@ -63,79 +63,79 @@ php artisan migrate
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:
```bash
-php artisan vendor:publish --tag=tracereplay-views
+php artisan vendor:publish --tag=trace-replay-views
```
-This copies the Blade templates to `resources/views/vendor/tracereplay/` where you can edit them freely. The package will automatically use your published versions instead of its built-in 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.
---
## βοΈ Configuration
-Open `config/tracereplay.php`. Every option is documented inline; the key ones are:
+Open `config/trace-replay.php`. Every option is documented inline; the key ones are:
```php
return [
// Globally enable or disable tracing (useful for CI)
- 'enabled' => env('TRACEREPLAY_ENABLED', true),
+ 'enabled' => env('TRACE_REPLAY_ENABLED', true),
// Probabilistic sampling β 1.0 = always trace, 0.1 = trace 10% of requests
- 'sample_rate' => env('TRACEREPLAY_SAMPLE_RATE', 1.0),
+ 'sample_rate' => env('TRACE_REPLAY_SAMPLE_RATE', 1.0),
// Multi-tenant project ID (optional)
- 'project_id' => env('TRACEREPLAY_PROJECT_ID', null),
+ 'project_id' => env('TRACE_REPLAY_PROJECT_ID', null),
// Automatically mask these keys in request/response payloads
'mask_fields' => ['password', 'password_confirmation', 'token', 'api_key',
'authorization', 'secret', 'credit_card', 'cvv', 'ssn', 'private_key'],
// Track DB queries inside each step
- 'track_db_queries' => env('TRACEREPLAY_TRACK_DB', true),
+ 'track_db_queries' => env('TRACE_REPLAY_TRACK_DB', true),
// Dashboard route middleware (add 'auth' or custom gate middleware for production)
'middleware' => ['web'],
'api_middleware' => ['api'],
// IP allowlist for the dashboard (exact match; empty = allow all)
- 'allowed_ips' => array_filter(explode(',', env('TRACEREPLAY_ALLOWED_IPS', ''))),
+ 'allowed_ips' => array_filter(explode(',', env('TRACE_REPLAY_ALLOWED_IPS', ''))),
// Async step persistence via a queue
'queue' => [
- 'enabled' => env('TRACEREPLAY_QUEUE_ENABLED', false),
- 'connection' => env('TRACEREPLAY_QUEUE_CONNECTION', env('QUEUE_CONNECTION', 'sync')),
- 'queue' => env('TRACEREPLAY_QUEUE_NAME', 'default'),
+ '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 engine
'replay' => [
- 'default_base_url' => env('TRACEREPLAY_REPLAY_URL', env('APP_URL', 'http://localhost')),
- 'timeout' => env('TRACEREPLAY_REPLAY_TIMEOUT', 30),
+ 'default_base_url' => env('TRACE_REPLAY_REPLAY_URL', env('APP_URL', 'http://localhost')),
+ 'timeout' => env('TRACE_REPLAY_REPLAY_TIMEOUT', 30),
],
// Auto-pruning retention period (days)
- 'retention_days' => env('TRACEREPLAY_RETENTION_DAYS', 30),
+ 'retention_days' => env('TRACE_REPLAY_RETENTION_DAYS', 30),
// Failure notifications (email / Slack webhook)
'notifications' => [
- 'on_failure' => env('TRACEREPLAY_NOTIFY_ON_FAILURE', false),
+ 'on_failure' => env('TRACE_REPLAY_NOTIFY_ON_FAILURE', false),
'channels' => ['mail'],
- 'mail' => ['to' => env('TRACEREPLAY_NOTIFY_EMAIL')],
- 'slack' => ['webhook_url' => env('TRACEREPLAY_SLACK_WEBHOOK')],
+ 'mail' => ['to' => env('TRACE_REPLAY_NOTIFY_EMAIL')],
+ 'slack' => ['webhook_url' => env('TRACE_REPLAY_SLACK_WEBHOOK')],
],
// OpenAI integration for in-dashboard AI responses
'ai' => [
- 'openai_api_key' => env('TRACEREPLAY_OPENAI_KEY'),
- 'model' => env('TRACEREPLAY_OPENAI_MODEL', 'gpt-4o'),
+ 'openai_api_key' => env('TRACE_REPLAY_OPENAI_KEY'),
+ 'model' => env('TRACE_REPLAY_OPENAI_MODEL', 'gpt-4o'),
],
// Auto-tracing for jobs and artisan commands (registered automatically)
'auto_trace' => [
- 'jobs' => env('TRACEREPLAY_AUTO_TRACE_JOBS', true),
- 'commands' => env('TRACEREPLAY_AUTO_TRACE_COMMANDS', false),
+ 'jobs' => env('TRACE_REPLAY_AUTO_TRACE_JOBS', true),
+ 'commands' => env('TRACE_REPLAY_AUTO_TRACE_COMMANDS', false),
'exclude_commands' => [
'queue:work', 'queue:listen', 'horizon', 'schedule:run',
- 'schedule:work', 'tracereplay:prune', 'tracereplay:export',
+ 'schedule:work', 'trace-replay:prune', 'trace-replay:export',
],
],
];
@@ -222,7 +222,7 @@ For Laravel 11+ (using `bootstrap/app.php`):
Queue jobs are automatically traced when `auto_trace.jobs` is enabled (default: `true`). No manual listener registration is needed β the service provider wires everything up.
-To disable, set `TRACEREPLAY_AUTO_TRACE_JOBS=false` in your `.env`.
+To disable, set `TRACE_REPLAY_AUTO_TRACE_JOBS=false` in your `.env`.
---
@@ -231,21 +231,21 @@ To disable, set `TRACEREPLAY_AUTO_TRACE_JOBS=false` in your `.env`.
Artisan commands can be auto-traced by enabling `auto_trace.commands`:
```env
-TRACEREPLAY_AUTO_TRACE_COMMANDS=true
+TRACE_REPLAY_AUTO_TRACE_COMMANDS=true
```
-Internal commands like `queue:work`, `horizon`, and `tracereplay:prune` are excluded by default (see `auto_trace.exclude_commands` in the config).
+Internal commands like `queue:work`, `horizon`, and `trace-replay:prune` are excluded by default (see `auto_trace.exclude_commands` in the config).
---
### Debug Bar Component
-Drop the `
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.
composer require iazaran/tracereplay
+ composer require iazaran/trace-replay
Publish config and migrations:
-php artisan vendor:publish --tag=tracereplay-config
-php artisan vendor:publish --tag=tracereplay-migrations
+ php artisan vendor:publish --tag=trace-replay-config
+php artisan vendor:publish --tag=trace-replay-migrations
php artisan migrate
Migrations use json columns for full MySQL, MariaDB, PostgreSQL, and SQLite compatibility.
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=tracereplay-views
- This copies the Blade templates to resources/views/vendor/tracereplay/ where you can edit them freely. The package will automatically use your published versions instead of its built-in views.
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.
Key options in config/tracereplay.php:
Key options in config/trace-replay.php:
return [
- 'enabled' => env('TRACEREPLAY_ENABLED', true),
- 'sample_rate' => env('TRACEREPLAY_SAMPLE_RATE', 1.0), // 0.1 = 10% of requests
- 'project_id' => env('TRACEREPLAY_PROJECT_ID', null),
+ '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('TRACEREPLAY_TRACK_DB', true),
+ 'track_db_queries' => env('TRACE_REPLAY_TRACK_DB', true),
'middleware' => ['web'], // add 'auth', 'can:...' for production
'api_middleware' => ['api'],
- 'allowed_ips' => array_filter(explode(',', env('TRACEREPLAY_ALLOWED_IPS', ''))),
+ 'allowed_ips' => array_filter(explode(',', env('TRACE_REPLAY_ALLOWED_IPS', ''))),
'queue' => [
- 'enabled' => env('TRACEREPLAY_QUEUE_ENABLED', false),
- 'connection' => env('TRACEREPLAY_QUEUE_CONNECTION', env('QUEUE_CONNECTION', 'sync')),
- 'queue' => env('TRACEREPLAY_QUEUE_NAME', 'default'),
+ '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('TRACEREPLAY_REPLAY_URL', env('APP_URL')),
- 'timeout' => env('TRACEREPLAY_REPLAY_TIMEOUT', 30),
+ 'default_base_url' => env('TRACE_REPLAY_REPLAY_URL', env('APP_URL')),
+ 'timeout' => env('TRACE_REPLAY_REPLAY_TIMEOUT', 30),
],
- 'retention_days' => env('TRACEREPLAY_RETENTION_DAYS', 30),
+ 'retention_days' => env('TRACE_REPLAY_RETENTION_DAYS', 30),
'notifications' => [
- 'on_failure' => env('TRACEREPLAY_NOTIFY_ON_FAILURE', false),
+ 'on_failure' => env('TRACE_REPLAY_NOTIFY_ON_FAILURE', false),
'channels' => ['mail'],
- 'mail' => ['to' => env('TRACEREPLAY_NOTIFY_EMAIL')],
- 'slack' => ['webhook_url' => env('TRACEREPLAY_SLACK_WEBHOOK')],
+ 'mail' => ['to' => env('TRACE_REPLAY_NOTIFY_EMAIL')],
+ 'slack' => ['webhook_url' => env('TRACE_REPLAY_SLACK_WEBHOOK')],
],
'ai' => [
- 'openai_api_key' => env('TRACEREPLAY_OPENAI_KEY'),
- 'model' => env('TRACEREPLAY_OPENAI_MODEL', 'gpt-4o'),
+ 'openai_api_key' => env('TRACE_REPLAY_OPENAI_KEY'),
+ 'model' => env('TRACE_REPLAY_OPENAI_MODEL', 'gpt-4o'),
],
'auto_trace' => [
- 'jobs' => env('TRACEREPLAY_AUTO_TRACE_JOBS', true),
- 'commands' => env('TRACEREPLAY_AUTO_TRACE_COMMANDS', false),
+ 'jobs' => env('TRACE_REPLAY_AUTO_TRACE_JOBS', true),
+ 'commands' => env('TRACE_REPLAY_AUTO_TRACE_COMMANDS', false),
'exclude_commands' => ['queue:work', 'queue:listen', 'horizon', 'schedule:run',
- 'schedule:work', 'tracereplay:prune', 'tracereplay:export'],
+ 'schedule:work', 'trace-replay:prune', 'trace-replay:export'],
],
];
@@ -270,11 +270,11 @@ 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:
TRACEREPLAY_AUTO_TRACE_COMMANDS=true
- Internal commands like queue:work, horizon, and tracereplay:prune are excluded by default (see auto_trace.exclude_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).
Navigate to https://your-app.com/tracereplay.
Navigate to https://your-app.com/trace-replay.
Add authentication or authorization middleware in config/tracereplay.php:
// config/tracereplay.php
-'middleware' => ['web', 'auth', 'can:view-tracereplay'],
+ 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-tracereplay', fn ($user) =>
+Gate::define('view-trace-replay', fn ($user) =>
in_array($user->email, ['admin@example.com'])
);
Or restrict by IP (exact match, comma-separated via env):
- TRACEREPLAY_ALLOWED_IPS=203.0.113.5,10.0.0.1
+ TRACE_REPLAY_ALLOWED_IPS=203.0.113.5,10.0.0.1
Data Masking
TraceReplay automatically redacts configured keys from all request/response payloads before they touch the database β including deeply nested values:
- // config/tracereplay.php
+ // config/trace-replay.php
'mask_fields' => ['password', 'token', 'secret', 'api_key', 'credit_card'],
// Input: ['username' => 'alice', 'password' => 'hunter2']
@@ -318,7 +318,7 @@ AI Debugging
Configure OPENAI_API_KEY and click "Ask AI" to get an answer directly in the dashboard.
MCP / AI-Agent JSON-RPC API
- Autonomous agents can query TraceReplay over JSON-RPC 2.0 at POST /api/tracereplay/mcp.
+ Autonomous agents can query TraceReplay over JSON-RPC 2.0 at POST /api/trace-replay/mcp.
Method Params Returns
@@ -338,14 +338,14 @@ MCP / AI-Agent JSON-RPC API
Data Retention
Prune old traces automatically via the scheduler:
// app/Console/Kernel.php
-$schedule->command('tracereplay:prune --days=30')->daily();
- php artisan tracereplay:prune --days=30 --dry-run # preview
-php artisan tracereplay:prune --days=7 --status=error # prune only error traces
+$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
Export a trace for archiving:
- php artisan tracereplay:export {id} --format=json
-php artisan tracereplay:export {id} --format=csv
-php artisan tracereplay:export {id} --format=json --output=/tmp/trace.json
-php artisan tracereplay:export --status=error --format=json # all error traces
+ 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
Testing
./vendor/bin/pest
diff --git a/docs/robots.txt b/docs/robots.txt
index 77e19f7..aaf55be 100644
--- a/docs/robots.txt
+++ b/docs/robots.txt
@@ -1,3 +1,3 @@
User-agent: *
Allow: /
-Sitemap: https://iazaran.github.io/tracereplay/sitemap.xml
+Sitemap: https://iazaran.github.io/trace-replay/sitemap.xml
diff --git a/docs/sitemap.xml b/docs/sitemap.xml
index 651a20f..4216178 100644
--- a/docs/sitemap.xml
+++ b/docs/sitemap.xml
@@ -1,7 +1,7 @@
- https://iazaran.github.io/tracereplay/
+ https://iazaran.github.io/trace-replay/
2024-05-01T00:00:00+00:00
weekly
1.0
diff --git a/phpunit.xml b/phpunit.xml
index ff1499b..b244bc1 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -22,7 +22,7 @@
-
+
diff --git a/resources/views/components/trace-bar.blade.php b/resources/views/components/trace-bar.blade.php
index 1af6589..d4e525a 100644
--- a/resources/views/components/trace-bar.blade.php
+++ b/resources/views/components/trace-bar.blade.php
@@ -1,5 +1,5 @@
-@if(config('tracereplay.enabled') && $trace)
-
@@ -28,13 +28,13 @@
{{ substr($trace->id, 0, 8) }}
-
View in Dashboard β
-
@endif
diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php
index b7e5c4c..e562c27 100644
--- a/resources/views/index.blade.php
+++ b/resources/views/index.blade.php
@@ -1,4 +1,4 @@
-@extends('tracereplay::layout')
+@extends('trace-replay::layout')
@section('title', 'Traces β TraceReplay')
@section('content')
@@ -21,7 +21,7 @@
-
diff --git a/resources/views/show.blade.php b/resources/views/show.blade.php
index 0582551..704043e 100644
--- a/resources/views/show.blade.php
+++ b/resources/views/show.blade.php
@@ -1,4 +1,4 @@
-@extends('tracereplay::layout')
+@extends('trace-replay::layout')
@section('title', ($trace->name ?? 'Trace') . ' β TraceReplay')
@section('content')
@@ -277,7 +277,7 @@ class="font-mono font-bold">
this.aiPromptContent = 'Generating expert debugging promptβ¦';
try {
- const response = await fetch(`{{ route('tracereplay.ai.prompt', $trace->id) }}`, {
+ const response = await fetch(`{{ route('trace-replay.ai.prompt', $trace->id) }}`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
@@ -297,7 +297,7 @@ class="font-mono font-bold">
this.replayData = { original: 'Running replay...', replay: 'Waiting...' };
try {
- const response = await fetch(`{{ route('tracereplay.replay', $trace->id) }}`, {
+ const response = await fetch(`{{ route('trace-replay.replay', $trace->id) }}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
diff --git a/routes/api.php b/routes/api.php
index 045cd4e..14e4cc9 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -4,9 +4,9 @@
use TraceReplay\Http\Controllers\Api\McpController;
Route::group([
- 'prefix' => 'api/tracereplay/mcp',
- 'as' => 'tracereplay.api.mcp.',
- 'middleware' => config('tracereplay.api_middleware', ['api']),
+ 'prefix' => 'api/trace-replay/mcp',
+ 'as' => 'trace-replay.api.mcp.',
+ 'middleware' => config('trace-replay.api_middleware', ['api']),
], function () {
Route::post('/', [McpController::class, 'handleRpc'])->name('rpc');
diff --git a/routes/web.php b/routes/web.php
index 0883156..2a00851 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -5,13 +5,13 @@
use TraceReplay\Http\Middleware\TraceReplayAuthMiddleware;
$middleware = array_merge(
- config('tracereplay.middleware', ['web']),
+ config('trace-replay.middleware', ['web']),
[TraceReplayAuthMiddleware::class]
);
Route::group([
- 'prefix' => 'tracereplay',
- 'as' => 'tracereplay.',
+ 'prefix' => 'trace-replay',
+ 'as' => 'trace-replay.',
'middleware' => $middleware,
], function () {
Route::get('/', [DashboardController::class, 'index'])->name('index');
diff --git a/src/Console/Commands/ExportTraceCommand.php b/src/Console/Commands/ExportTraceCommand.php
index 6461895..f65d715 100644
--- a/src/Console/Commands/ExportTraceCommand.php
+++ b/src/Console/Commands/ExportTraceCommand.php
@@ -7,7 +7,7 @@
class ExportTraceCommand extends Command
{
- protected $signature = 'tracereplay:export
+ protected $signature = 'trace-replay:export
{id? : UUID of the trace to export (omit to export all)}
{--format=json : Export format: json or csv}
{--output= : File path to write the output (defaults to stdout)}
diff --git a/src/Console/Commands/PruneTracesCommand.php b/src/Console/Commands/PruneTracesCommand.php
index 0729761..f875645 100644
--- a/src/Console/Commands/PruneTracesCommand.php
+++ b/src/Console/Commands/PruneTracesCommand.php
@@ -7,7 +7,7 @@
class PruneTracesCommand extends Command
{
- protected $signature = 'tracereplay:prune
+ protected $signature = 'trace-replay:prune
{--days= : Override the retention_days config value}
{--status= : Only prune traces with this status (success|error|processing)}
{--dry-run : Show what would be deleted without deleting}';
@@ -16,7 +16,7 @@ class PruneTracesCommand extends Command
public function handle(): int
{
- $days = (int) ($this->option('days') ?? config('tracereplay.retention_days', 30));
+ $days = (int) ($this->option('days') ?? config('trace-replay.retention_days', 30));
$status = $this->option('status');
$dryRun = $this->option('dry-run');
diff --git a/src/Facades/TraceReplay.php b/src/Facades/TraceReplay.php
index d5a431d..1dafde1 100644
--- a/src/Facades/TraceReplay.php
+++ b/src/Facades/TraceReplay.php
@@ -22,6 +22,6 @@ class TraceReplay extends Facade
{
protected static function getFacadeAccessor(): string
{
- return 'tracereplay';
+ return 'trace-replay';
}
}
diff --git a/src/Http/Controllers/DashboardController.php b/src/Http/Controllers/DashboardController.php
index 26ce35e..415091a 100644
--- a/src/Http/Controllers/DashboardController.php
+++ b/src/Http/Controllers/DashboardController.php
@@ -26,14 +26,14 @@ public function index(Request $request)
$traces = $query->paginate(25)->withQueryString();
- return view('tracereplay::index', compact('traces'));
+ return view('trace-replay::index', compact('traces'));
}
public function show(string $id)
{
$trace = Trace::with('steps')->findOrFail($id);
- return view('tracereplay::show', compact('trace'));
+ return view('trace-replay::show', compact('trace'));
}
public function replay(Request $request, string $id, ReplayService $replayService): JsonResponse
diff --git a/src/Http/Middleware/TraceMiddleware.php b/src/Http/Middleware/TraceMiddleware.php
index 59ebfd6..bc489f9 100644
--- a/src/Http/Middleware/TraceMiddleware.php
+++ b/src/Http/Middleware/TraceMiddleware.php
@@ -12,18 +12,18 @@ class TraceMiddleware
{
public function handle(Request $request, Closure $next): SymfonyResponse
{
- if (! config('tracereplay.enabled')) {
+ if (! config('trace-replay.enabled')) {
return $next($request);
}
// Respect sampling rate
- $sampleRate = (float) config('tracereplay.sample_rate', 1.0);
+ $sampleRate = (float) config('trace-replay.sample_rate', 1.0);
if ($sampleRate < 1.0 && mt_rand() / mt_getrandmax() > $sampleRate) {
return $next($request);
}
- // Skip tracereplay dashboard routes to avoid recursive tracing
- if (str_starts_with($request->path(), 'tracereplay') || str_starts_with($request->path(), 'api/tracereplay')) {
+ // Skip trace-replay dashboard routes to avoid recursive tracing
+ if (str_starts_with($request->path(), 'trace-replay') || str_starts_with($request->path(), 'api/trace-replay')) {
return $next($request);
}
@@ -58,7 +58,7 @@ public function handle(Request $request, Closure $next): SymfonyResponse
public function terminate(Request $request, SymfonyResponse $response): void
{
- if (! config('tracereplay.enabled')) {
+ if (! config('trace-replay.enabled')) {
return;
}
diff --git a/src/Http/Middleware/TraceReplayAuthMiddleware.php b/src/Http/Middleware/TraceReplayAuthMiddleware.php
index 7979dee..6c88435 100644
--- a/src/Http/Middleware/TraceReplayAuthMiddleware.php
+++ b/src/Http/Middleware/TraceReplayAuthMiddleware.php
@@ -9,14 +9,14 @@
/**
* Guards the TraceReplay dashboard routes.
*
- * - IP allowlist: if `tracereplay.allowed_ips` is non-empty, only those IPs can access the dashboard.
- * - Can be combined with any Laravel auth middleware via the `tracereplay.middleware` config key.
+ * - IP allowlist: if `trace-replay.allowed_ips` is non-empty, only those IPs can access the dashboard.
+ * - Can be combined with any Laravel auth middleware via the `trace-replay.middleware` config key.
*/
class TraceReplayAuthMiddleware
{
public function handle(Request $request, Closure $next): Response
{
- $allowedIps = config('tracereplay.allowed_ips', []);
+ $allowedIps = config('trace-replay.allowed_ips', []);
if (! empty($allowedIps) && ! \in_array($request->ip(), $allowedIps, true)) {
abort(403, 'Access to TraceReplay dashboard is restricted.');
diff --git a/src/Listeners/CommandTraceListener.php b/src/Listeners/CommandTraceListener.php
index a0df112..9a85cfb 100644
--- a/src/Listeners/CommandTraceListener.php
+++ b/src/Listeners/CommandTraceListener.php
@@ -10,7 +10,7 @@ class CommandTraceListener
{
public function onCommandStarting(CommandStarting $event): void
{
- if (! config('tracereplay.auto_trace.commands', false)) {
+ if (! config('trace-replay.auto_trace.commands', false)) {
return;
}
@@ -19,7 +19,7 @@ public function onCommandStarting(CommandStarting $event): void
return;
}
- $excluded = config('tracereplay.auto_trace.exclude_commands', []);
+ $excluded = config('trace-replay.auto_trace.exclude_commands', []);
if (\in_array($event->command, $excluded, true)) {
return;
}
@@ -34,7 +34,7 @@ public function onCommandStarting(CommandStarting $event): void
public function onCommandFinished(CommandFinished $event): void
{
- if (! config('tracereplay.auto_trace.commands', false)) {
+ if (! config('trace-replay.auto_trace.commands', false)) {
return;
}
@@ -42,7 +42,7 @@ public function onCommandFinished(CommandFinished $event): void
return;
}
- $excluded = config('tracereplay.auto_trace.exclude_commands', []);
+ $excluded = config('trace-replay.auto_trace.exclude_commands', []);
if (\in_array($event->command, $excluded, true)) {
return;
}
diff --git a/src/Listeners/JobTraceListener.php b/src/Listeners/JobTraceListener.php
index 34b94a9..5a70c65 100644
--- a/src/Listeners/JobTraceListener.php
+++ b/src/Listeners/JobTraceListener.php
@@ -11,7 +11,7 @@ class JobTraceListener
{
public function onJobProcessing(JobProcessing $event): void
{
- if (! config('tracereplay.auto_trace.jobs', true)) {
+ if (! config('trace-replay.auto_trace.jobs', true)) {
return;
}
@@ -30,7 +30,7 @@ public function onJobProcessing(JobProcessing $event): void
public function onJobProcessed(JobProcessed $_event): void
{
- if (! config('tracereplay.auto_trace.jobs', true)) {
+ if (! config('trace-replay.auto_trace.jobs', true)) {
return;
}
@@ -40,7 +40,7 @@ public function onJobProcessed(JobProcessed $_event): void
public function onJobFailed(JobFailed $event): void
{
- if (! config('tracereplay.auto_trace.jobs', true)) {
+ if (! config('trace-replay.auto_trace.jobs', true)) {
return;
}
diff --git a/src/Services/AiPromptService.php b/src/Services/AiPromptService.php
index 40ab598..75292c9 100644
--- a/src/Services/AiPromptService.php
+++ b/src/Services/AiPromptService.php
@@ -71,7 +71,7 @@ public function generateFixPrompt(Trace $trace): string
$prompt .= "2. **Hypothesis:** State your best hypothesis about the underlying issue.\n";
$prompt .= "3. **Fix:** Provide a concrete, minimal code fix (PHP/Laravel) or configuration change.\n";
$prompt .= "4. **Prevention:** Suggest how to prevent this class of error in the future (tests, validation, guards).\n\n";
- $prompt .= "_Context generated by TraceReplay β https://github.com/iazaran/tracereplay_\n";
+ $prompt .= "_Context generated by TraceReplay β https://github.com/iazaran/trace-replay_\n";
return $prompt;
}
@@ -82,8 +82,8 @@ public function generateFixPrompt(Trace $trace): string
*/
public function callOpenAI(string $prompt): ?string
{
- $apiKey = config('tracereplay.ai.openai_api_key');
- $model = config('tracereplay.ai.model', 'gpt-4o');
+ $apiKey = config('trace-replay.ai.openai_api_key');
+ $model = config('trace-replay.ai.model', 'gpt-4o');
if (! $apiKey) {
return null;
diff --git a/src/Services/NotificationService.php b/src/Services/NotificationService.php
index e28ec9a..ff337a7 100644
--- a/src/Services/NotificationService.php
+++ b/src/Services/NotificationService.php
@@ -10,7 +10,7 @@ class NotificationService
{
public function notifyFailure(Trace $trace): void
{
- $channels = config('tracereplay.notifications.channels', []);
+ $channels = config('trace-replay.notifications.channels', []);
foreach ($channels as $channel) {
match ($channel) {
@@ -23,13 +23,13 @@ public function notifyFailure(Trace $trace): void
protected function sendMail(Trace $trace): void
{
- $to = config('tracereplay.notifications.mail.to');
+ $to = config('trace-replay.notifications.mail.to');
if (! $to) {
return;
}
$errorStep = $trace->error_step;
- $dashboardUrl = rtrim(config('app.url', ''), '/').'/tracereplay/traces/'.$trace->id;
+ $dashboardUrl = rtrim(config('app.url', ''), '/').'/trace-replay/traces/'.$trace->id;
$subject = "[TraceReplay] Trace Failed: {$trace->name}";
$body = $this->buildEmailBody($trace, $errorStep, $dashboardUrl);
@@ -41,13 +41,13 @@ protected function sendMail(Trace $trace): void
protected function sendSlack(Trace $trace): void
{
- $webhookUrl = config('tracereplay.notifications.slack.webhook_url');
+ $webhookUrl = config('trace-replay.notifications.slack.webhook_url');
if (! $webhookUrl) {
return;
}
$errorStep = $trace->error_step;
- $dashboardUrl = rtrim(config('app.url', ''), '/').'/tracereplay/traces/'.$trace->id;
+ $dashboardUrl = rtrim(config('app.url', ''), '/').'/trace-replay/traces/'.$trace->id;
$payload = [
'text' => ':red_circle: *TraceReplay β Trace Failed*',
diff --git a/src/Services/PayloadMasker.php b/src/Services/PayloadMasker.php
index d2ccedb..70d2ead 100644
--- a/src/Services/PayloadMasker.php
+++ b/src/Services/PayloadMasker.php
@@ -11,7 +11,7 @@ public function __construct()
{
$this->fields = array_map(
'strtolower',
- config('tracereplay.mask_fields', [
+ config('trace-replay.mask_fields', [
'password', 'password_confirmation', 'token',
'api_key', 'authorization', 'secret', 'credit_card',
])
diff --git a/src/Services/ReplayService.php b/src/Services/ReplayService.php
index 9f6d9ac..dfbeb03 100644
--- a/src/Services/ReplayService.php
+++ b/src/Services/ReplayService.php
@@ -32,7 +32,7 @@ public function replay(Trace $trace, ?string $overrideUrl = null): array
// Remove host headers so they don't interfere with the target
unset($headers['host'], $headers['Host']);
- $baseUrl = $overrideUrl ?? config('tracereplay.replay.default_base_url');
+ $baseUrl = $overrideUrl ?? config('trace-replay.replay.default_base_url');
$targetUrl = rtrim($baseUrl, '/').'/'.ltrim($uri, '/');
// Symfony normalises all header names to lowercase, so 'Content-Type' never exists
@@ -40,7 +40,7 @@ public function replay(Trace $trace, ?string $overrideUrl = null): array
$isJson = str_contains($headers['content-type'][0] ?? '', 'json');
$response = Http::withHeaders($headers)
- ->timeout((int) config('tracereplay.replay.timeout', 30))
+ ->timeout((int) config('trace-replay.replay.timeout', 30))
->withQueryParameters($query)
->send($method, $targetUrl, $isJson ? ['json' => $body] : ['form_params' => $body]);
diff --git a/src/TraceReplayManager.php b/src/TraceReplayManager.php
index be94c90..30278ce 100644
--- a/src/TraceReplayManager.php
+++ b/src/TraceReplayManager.php
@@ -36,7 +36,7 @@ public function __construct($app)
public function start(string $name, array $tags = []): ?Trace
{
- if (! config('tracereplay.enabled', true)) {
+ if (! config('trace-replay.enabled', true)) {
return null;
}
@@ -81,7 +81,7 @@ public function step(string $label, callable $callback, array $extra = []): mixe
$start = microtime(true);
$status = 'success';
$errorReason = null;
- $trackDb = config('tracereplay.track_db_queries', true);
+ $trackDb = config('trace-replay.track_db_queries', true);
// Use Laravel's built-in query log rather than DB::listen() to avoid
// listener accumulation: each additional step() call would register
@@ -232,7 +232,7 @@ public function end(string $status = 'success'): void
]);
// Fire notification if configured and trace failed
- if ($status === 'error' && config('tracereplay.notifications.on_failure', false)) {
+ if ($status === 'error' && config('trace-replay.notifications.on_failure', false)) {
try {
app(NotificationService::class)->notifyFailure($this->currentTrace->fresh(['steps']));
} catch (Throwable) {
@@ -259,10 +259,10 @@ public function getCurrentTrace(): ?Trace
protected function persistStep(TraceStep $step): void
{
try {
- if (config('tracereplay.queue.enabled') && class_exists(PersistTraceStepJob::class)) {
+ if (config('trace-replay.queue.enabled') && class_exists(PersistTraceStepJob::class)) {
dispatch(new PersistTraceStepJob($step->toArray()))
- ->onConnection(config('tracereplay.queue.connection'))
- ->onQueue(config('tracereplay.queue.queue'));
+ ->onConnection(config('trace-replay.queue.connection'))
+ ->onQueue(config('trace-replay.queue.queue'));
} else {
$step->save();
}
@@ -273,7 +273,7 @@ protected function persistStep(TraceStep $step): void
protected function determineProjectId(): ?string
{
- return config('tracereplay.project_id');
+ return config('trace-replay.project_id');
}
/**
diff --git a/src/TraceReplayServiceProvider.php b/src/TraceReplayServiceProvider.php
index ae01c85..f4e771d 100644
--- a/src/TraceReplayServiceProvider.php
+++ b/src/TraceReplayServiceProvider.php
@@ -23,9 +23,9 @@ class TraceReplayServiceProvider extends ServiceProvider
{
public function register(): void
{
- $this->mergeConfigFrom(__DIR__.'/../config/tracereplay.php', 'tracereplay');
+ $this->mergeConfigFrom(__DIR__.'/../config/trace-replay.php', 'trace-replay');
- $this->app->singleton('tracereplay', fn ($app) => new TraceReplayManager($app));
+ $this->app->singleton('trace-replay', fn ($app) => new TraceReplayManager($app));
$this->app->singleton(PayloadMasker::class);
$this->app->singleton(AiPromptService::class);
@@ -39,16 +39,16 @@ public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->publishes([
- __DIR__.'/../config/tracereplay.php' => config_path('tracereplay.php'),
- ], 'tracereplay-config');
+ __DIR__.'/../config/trace-replay.php' => config_path('trace-replay.php'),
+ ], 'trace-replay-config');
$this->publishes([
__DIR__.'/../database/migrations/' => database_path('migrations'),
- ], 'tracereplay-migrations');
+ ], 'trace-replay-migrations');
$this->publishes([
- __DIR__.'/../resources/views' => resource_path('views/vendor/tracereplay'),
- ], 'tracereplay-views');
+ __DIR__.'/../resources/views' => resource_path('views/vendor/trace-replay'),
+ ], 'trace-replay-views');
$this->commands([
PruneTracesCommand::class,
@@ -57,8 +57,8 @@ public function boot(): void
}
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
- $this->loadViewsFrom(__DIR__.'/../resources/views', 'tracereplay');
- $this->loadViewComponentsAs('tracereplay', [
+ $this->loadViewsFrom(__DIR__.'/../resources/views', 'trace-replay');
+ $this->loadViewComponentsAs('trace-replay', [
TraceBar::class,
]);
@@ -66,14 +66,14 @@ public function boot(): void
$this->loadRoutesFrom(__DIR__.'/../routes/api.php');
// Auto-trace queue jobs
- if (config('tracereplay.auto_trace.jobs', true)) {
+ if (config('trace-replay.auto_trace.jobs', true)) {
Event::listen(JobProcessing::class, fn (JobProcessing $e) => $this->app->make(JobTraceListener::class)->onJobProcessing($e));
Event::listen(JobProcessed::class, fn (JobProcessed $e) => $this->app->make(JobTraceListener::class)->onJobProcessed($e));
Event::listen(JobFailed::class, fn (JobFailed $e) => $this->app->make(JobTraceListener::class)->onJobFailed($e));
}
// Auto-trace artisan commands
- if (config('tracereplay.auto_trace.commands', false)) {
+ if (config('trace-replay.auto_trace.commands', false)) {
Event::listen(CommandStarting::class, fn (CommandStarting $e) => $this->app->make(CommandTraceListener::class)->onCommandStarting($e));
Event::listen(CommandFinished::class, fn (CommandFinished $e) => $this->app->make(CommandTraceListener::class)->onCommandFinished($e));
}
diff --git a/src/View/Components/TraceBar.php b/src/View/Components/TraceBar.php
index 0e53c73..702d141 100644
--- a/src/View/Components/TraceBar.php
+++ b/src/View/Components/TraceBar.php
@@ -13,13 +13,13 @@ public function __construct(
public function render()
{
- if (! $this->show || ! config('tracereplay.enabled', true)) {
+ if (! $this->show || ! config('trace-replay.enabled', true)) {
return '';
}
$trace = TraceReplay::getCurrentTrace();
- return view('tracereplay::components.trace-bar', [
+ return view('trace-replay::components.trace-bar', [
'trace' => $trace,
]);
}
diff --git a/tests/Feature/TraceReplayTest.php b/tests/Feature/TraceReplayTest.php
index b71cfc5..7c95deb 100644
--- a/tests/Feature/TraceReplayTest.php
+++ b/tests/Feature/TraceReplayTest.php
@@ -32,7 +32,7 @@
});
it('returns null from start() when tracing is disabled', function () {
- config(['tracereplay.enabled' => false]);
+ config(['trace-replay.enabled' => false]);
$trace = TraceReplay::start('Disabled Trace');
@@ -188,7 +188,7 @@
// ββ PayloadMasker βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
it('PayloadMasker masks configured sensitive fields', function () {
- config(['tracereplay.mask_fields' => ['password', 'token']]);
+ config(['trace-replay.mask_fields' => ['password', 'token']]);
$masker = new PayloadMasker;
$result = $masker->mask([
@@ -236,83 +236,83 @@
// ββ Artisan Commands ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-it('tracereplay:prune deletes old traces', function () {
+it('trace-replay:prune deletes old traces', function () {
Trace::factory()->create(['started_at' => now()->subDays(60)]);
Trace::factory()->create(['started_at' => now()->subDays(60)]);
Trace::factory()->create(['started_at' => now()->subDays(1)]);
- $this->artisan('tracereplay:prune', ['--days' => 30])
+ $this->artisan('trace-replay:prune', ['--days' => 30])
->expectsOutput('Deleted 2 trace(s) older than 30 day(s).')
->assertExitCode(0);
expect(Trace::count())->toBe(1);
});
-it('tracereplay:prune dry-run does not delete traces', function () {
+it('trace-replay:prune dry-run does not delete traces', function () {
Trace::factory()->create(['started_at' => now()->subDays(60)]);
- $this->artisan('tracereplay:prune', ['--days' => 30, '--dry-run' => true])
+ $this->artisan('trace-replay:prune', ['--days' => 30, '--dry-run' => true])
->assertExitCode(0);
expect(Trace::count())->toBe(1);
});
-it('tracereplay:export outputs JSON for a trace', function () {
+it('trace-replay:export outputs JSON for a trace', function () {
$trace = Trace::factory()->create(['name' => 'Exportable Trace']);
- $this->artisan('tracereplay:export', ['id' => $trace->id, '--format' => 'json'])
+ $this->artisan('trace-replay:export', ['id' => $trace->id, '--format' => 'json'])
->assertExitCode(0);
});
// ββ Export CSV ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-it('tracereplay:export outputs CSV for a trace', function () {
+it('trace-replay:export outputs CSV for a trace', function () {
$trace = Trace::factory()->create(['name' => 'CSV, with "commas"']);
- $this->artisan('tracereplay:export', ['id' => $trace->id, '--format' => 'csv'])
+ $this->artisan('trace-replay:export', ['id' => $trace->id, '--format' => 'csv'])
->assertExitCode(0);
});
-it('tracereplay:export rejects unsupported format', function () {
+it('trace-replay:export rejects unsupported format', function () {
$trace = Trace::factory()->create();
- $this->artisan('tracereplay:export', ['id' => $trace->id, '--format' => 'xml'])
+ $this->artisan('trace-replay:export', ['id' => $trace->id, '--format' => 'xml'])
->assertExitCode(1);
});
-it('tracereplay:export returns failure for nonexistent trace', function () {
- $this->artisan('tracereplay:export', ['id' => 'nonexistent-id'])
+it('trace-replay:export returns failure for nonexistent trace', function () {
+ $this->artisan('trace-replay:export', ['id' => 'nonexistent-id'])
->assertExitCode(1);
});
-it('tracereplay:export filters by status', function () {
+it('trace-replay:export filters by status', function () {
Trace::factory()->create(['status' => 'success']);
Trace::factory()->create(['status' => 'error']);
- $this->artisan('tracereplay:export', ['--status' => 'error', '--format' => 'json'])
+ $this->artisan('trace-replay:export', ['--status' => 'error', '--format' => 'json'])
->assertExitCode(0);
});
// ββ Prune Command Extra ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-it('tracereplay:prune rejects zero days', function () {
- $this->artisan('tracereplay:prune', ['--days' => 0])
+it('trace-replay:prune rejects zero days', function () {
+ $this->artisan('trace-replay:prune', ['--days' => 0])
->assertExitCode(1);
});
-it('tracereplay:prune filters by status', function () {
+it('trace-replay:prune filters by status', function () {
Trace::factory()->create(['started_at' => now()->subDays(60), 'status' => 'success']);
Trace::factory()->create(['started_at' => now()->subDays(60), 'status' => 'error']);
- $this->artisan('tracereplay:prune', ['--days' => 30, '--status' => 'success'])
+ $this->artisan('trace-replay:prune', ['--days' => 30, '--status' => 'success'])
->assertExitCode(0);
expect(Trace::count())->toBe(1)
->and(Trace::first()->status)->toBe('error');
});
-it('tracereplay:prune shows no-op when no traces match', function () {
- $this->artisan('tracereplay:prune', ['--days' => 30])
+it('trace-replay:prune shows no-op when no traces match', function () {
+ $this->artisan('trace-replay:prune', ['--days' => 30])
->expectsOutput('No traces found matching the criteria.')
->assertExitCode(0);
});
@@ -322,7 +322,7 @@
it('dashboard index page loads', function () {
Trace::factory()->count(3)->create();
- $response = $this->get('/tracereplay');
+ $response = $this->get('/trace-replay');
$response->assertOk();
$response->assertSee('Traces');
@@ -332,7 +332,7 @@
Trace::factory()->create(['status' => 'error', 'name' => 'Error Trace']);
Trace::factory()->create(['status' => 'success', 'name' => 'Good Trace']);
- $response = $this->get('/tracereplay?status=error');
+ $response = $this->get('/trace-replay?status=error');
$response->assertOk();
$response->assertSee('Error Trace');
@@ -342,7 +342,7 @@
Trace::factory()->create(['name' => 'Login Flow']);
Trace::factory()->create(['name' => 'Checkout Process']);
- $response = $this->get('/tracereplay?search=Login');
+ $response = $this->get('/trace-replay?search=Login');
$response->assertOk();
$response->assertSee('Login Flow');
@@ -351,14 +351,14 @@
it('dashboard show page loads for a valid trace', function () {
$trace = Trace::factory()->create(['name' => 'Detail Test']);
- $response = $this->get("/tracereplay/traces/{$trace->id}");
+ $response = $this->get("/trace-replay/traces/{$trace->id}");
$response->assertOk();
$response->assertSee('Detail Test');
});
it('dashboard show returns 404 for nonexistent trace', function () {
- $response = $this->get('/tracereplay/traces/nonexistent-uuid');
+ $response = $this->get('/trace-replay/traces/nonexistent-uuid');
$response->assertNotFound();
});
@@ -367,7 +367,7 @@
Trace::factory()->create(['status' => 'success', 'duration_ms' => 100]);
Trace::factory()->create(['status' => 'error', 'duration_ms' => 200]);
- $response = $this->getJson('/tracereplay/stats');
+ $response = $this->getJson('/trace-replay/stats');
$response->assertOk();
$response->assertJsonStructure(['total', 'success', 'failed', 'today', 'failure_rate', 'avg_duration', 'slowest']);
@@ -377,7 +377,7 @@
it('dashboard export downloads JSON file', function () {
$trace = Trace::factory()->create(['name' => 'Export Me']);
- $response = $this->get("/tracereplay/traces/{$trace->id}/export");
+ $response = $this->get("/trace-replay/traces/{$trace->id}/export");
$response->assertOk();
$response->assertHeader('Content-Type', 'application/json');
@@ -389,7 +389,7 @@
it('MCP list traces returns paginated results', function () {
Trace::factory()->count(3)->create();
- $response = $this->getJson('/api/tracereplay/mcp/traces');
+ $response = $this->getJson('/api/trace-replay/mcp/traces');
$response->assertOk();
$response->assertJsonPath('status', 'success');
@@ -399,7 +399,7 @@
Trace::factory()->create(['status' => 'success']);
Trace::factory()->create(['status' => 'error']);
- $response = $this->getJson('/api/tracereplay/mcp/traces?filter_by_error=1');
+ $response = $this->getJson('/api/trace-replay/mcp/traces?filter_by_error=1');
$response->assertOk();
$response->assertJsonPath('data.total', 1);
@@ -408,7 +408,7 @@
it('MCP get context returns trace details', function () {
$trace = Trace::factory()->create(['status' => 'success']);
- $response = $this->getJson("/api/tracereplay/mcp/traces/{$trace->id}/context");
+ $response = $this->getJson("/api/trace-replay/mcp/traces/{$trace->id}/context");
$response->assertOk();
$response->assertJsonPath('status', 'success');
@@ -425,7 +425,7 @@
$trace = Trace::latest()->first();
- $response = $this->getJson("/api/tracereplay/mcp/traces/{$trace->id}/fix-prompt");
+ $response = $this->getJson("/api/trace-replay/mcp/traces/{$trace->id}/fix-prompt");
$response->assertOk();
$response->assertJsonPath('status', 'success');
@@ -434,7 +434,7 @@
it('MCP RPC list_traces method works', function () {
Trace::factory()->count(2)->create();
- $response = $this->postJson('/api/tracereplay/mcp', [
+ $response = $this->postJson('/api/trace-replay/mcp', [
'method' => 'list_traces',
'params' => [],
'id' => 1,
@@ -446,7 +446,7 @@
});
it('MCP RPC returns error for unknown method', function () {
- $response = $this->postJson('/api/tracereplay/mcp', [
+ $response = $this->postJson('/api/trace-replay/mcp', [
'method' => 'unknown_method',
'params' => [],
'id' => 42,
@@ -461,7 +461,7 @@
it('MCP RPC get_trace_context method works', function () {
$trace = Trace::factory()->create(['status' => 'error']);
- $response = $this->postJson('/api/tracereplay/mcp', [
+ $response = $this->postJson('/api/trace-replay/mcp', [
'method' => 'get_trace_context',
'params' => ['trace_id' => $trace->id],
'id' => 2,
@@ -481,7 +481,7 @@
$trace = Trace::latest()->first();
- $response = $this->postJson('/api/tracereplay/mcp', [
+ $response = $this->postJson('/api/trace-replay/mcp', [
'method' => 'generate_fix_prompt',
'params' => ['trace_id' => $trace->id],
'id' => 3,
@@ -493,20 +493,20 @@
// ββ TraceMiddleware ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-it('TraceMiddleware skips tracereplay dashboard routes', function () {
+it('TraceMiddleware skips trace-replay dashboard routes', function () {
// Dashboard route should not create a trace
- $this->get('/tracereplay');
+ $this->get('/trace-replay');
- // The middleware skips routes starting with 'tracereplay'
+ // The middleware skips routes starting with 'trace-replay'
// Traces created should only be for the dashboard itself, not from middleware
// Since dashboard creates no traces itself, count should be 0 from middleware
- expect(Trace::where('name', 'like', 'HTTP GET /tracereplay%')->count())->toBe(0);
+ expect(Trace::where('name', 'like', 'HTTP GET /trace-replay%')->count())->toBe(0);
});
it('TraceMiddleware respects disabled config', function () {
- config(['tracereplay.enabled' => false]);
+ config(['trace-replay.enabled' => false]);
- $this->get('/tracereplay');
+ $this->get('/trace-replay');
expect(Trace::count())->toBe(0);
});
@@ -514,17 +514,17 @@
// ββ Auth Middleware ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
it('TraceReplayAuthMiddleware allows access when no IPs configured', function () {
- config(['tracereplay.allowed_ips' => []]);
+ config(['trace-replay.allowed_ips' => []]);
- $response = $this->get('/tracereplay');
+ $response = $this->get('/trace-replay');
$response->assertOk();
});
it('TraceReplayAuthMiddleware blocks access from unauthorized IPs', function () {
- config(['tracereplay.allowed_ips' => ['192.168.1.100']]);
+ config(['trace-replay.allowed_ips' => ['192.168.1.100']]);
- $response = $this->get('/tracereplay');
+ $response = $this->get('/trace-replay');
$response->assertForbidden();
});
@@ -699,7 +699,7 @@
});
it('PayloadMasker is case-insensitive', function () {
- config(['tracereplay.mask_fields' => ['Authorization']]);
+ config(['trace-replay.mask_fields' => ['Authorization']]);
$masker = new PayloadMasker;
$result = $masker->mask(['AUTHORIZATION' => 'Bearer xyz', 'name' => 'test']);
@@ -721,9 +721,9 @@
});
config([
- 'tracereplay.notifications.on_failure' => true,
- 'tracereplay.notifications.channels' => ['mail'],
- 'tracereplay.notifications.mail.to' => 'test@example.com',
+ 'trace-replay.notifications.on_failure' => true,
+ 'trace-replay.notifications.channels' => ['mail'],
+ 'trace-replay.notifications.mail.to' => 'test@example.com',
]);
$trace = Trace::factory()->create(['status' => 'error', 'name' => 'Notified Trace']);
@@ -736,8 +736,8 @@
Mail::fake();
config([
- 'tracereplay.notifications.channels' => ['mail'],
- 'tracereplay.notifications.mail.to' => null,
+ 'trace-replay.notifications.channels' => ['mail'],
+ 'trace-replay.notifications.mail.to' => null,
]);
$trace = Trace::factory()->create(['status' => 'error']);
@@ -750,8 +750,8 @@
Http::fake(['*' => Http::response([], 200)]);
config([
- 'tracereplay.notifications.channels' => ['slack'],
- 'tracereplay.notifications.slack.webhook_url' => 'https://hooks.slack.test/webhook',
+ 'trace-replay.notifications.channels' => ['slack'],
+ 'trace-replay.notifications.slack.webhook_url' => 'https://hooks.slack.test/webhook',
]);
$trace = Trace::factory()->create(['status' => 'error', 'name' => 'Slack Trace']);
@@ -764,8 +764,8 @@
Http::fake();
config([
- 'tracereplay.notifications.channels' => ['slack'],
- 'tracereplay.notifications.slack.webhook_url' => null,
+ 'trace-replay.notifications.channels' => ['slack'],
+ 'trace-replay.notifications.slack.webhook_url' => null,
]);
$trace = Trace::factory()->create(['status' => 'error']);
@@ -777,7 +777,7 @@
// ββ Step DB query tracking βββββββββββββββββββββββββββββββββββββββββββββββββββ
it('step records db query count when tracking is enabled', function () {
- config(['tracereplay.track_db_queries' => true]);
+ config(['trace-replay.track_db_queries' => true]);
TraceReplay::start('DB Tracking');
TraceReplay::step('Query Step', function () {
@@ -791,7 +791,7 @@
});
it('step does not track queries when disabled', function () {
- config(['tracereplay.track_db_queries' => false]);
+ config(['trace-replay.track_db_queries' => false]);
TraceReplay::start('No DB Tracking');
TraceReplay::step('Silent Step', function () {
@@ -831,7 +831,7 @@
it('context() returns the manager for chaining', function () {
TraceReplay::start('Chain Test');
- $result = app('tracereplay')->context(['x' => 1]);
+ $result = app('trace-replay')->context(['x' => 1]);
expect($result)->toBeInstanceOf(TraceReplayManager::class);
});
@@ -853,10 +853,10 @@
// ββ Export Command β invalid directory ββββββββββββββββββββββββββββββββββββββ
-it('tracereplay:export fails on invalid output directory', function () {
+it('trace-replay:export fails on invalid output directory', function () {
$trace = Trace::factory()->create();
- $this->artisan('tracereplay:export', [
+ $this->artisan('trace-replay:export', [
'id' => $trace->id,
'--format' => 'json',
'--output' => '/nonexistent/dir/trace.json',
@@ -865,8 +865,8 @@
// ββ Prune Command β invalid status βββββββββββββββββββββββββββββββββββββββββ
-it('tracereplay:prune rejects invalid status', function () {
- $this->artisan('tracereplay:prune', ['--days' => 30, '--status' => 'invalid'])
+it('trace-replay:prune rejects invalid status', function () {
+ $this->artisan('trace-replay:prune', ['--days' => 30, '--status' => 'invalid'])
->assertExitCode(1);
});
@@ -875,7 +875,7 @@
it('dashboard index ignores invalid status filter', function () {
Trace::factory()->create(['status' => 'success', 'name' => 'Valid Trace']);
- $response = $this->get('/tracereplay?status=nonexistent');
+ $response = $this->get('/trace-replay?status=nonexistent');
$response->assertOk();
// Invalid status is ignored β all traces should be shown
@@ -887,7 +887,7 @@
it('dashboard replay endpoint returns error for trace without request payload', function () {
$trace = Trace::factory()->create();
- $response = $this->postJson("/tracereplay/traces/{$trace->id}/replay");
+ $response = $this->postJson("/trace-replay/traces/{$trace->id}/replay");
$response->assertStatus(400);
$response->assertJsonPath('status', 'error');
@@ -906,7 +906,7 @@
$trace = Trace::latest()->first();
// No OpenAI key configured, so ai_response will be null
- $response = $this->postJson("/tracereplay/traces/{$trace->id}/ai-prompt");
+ $response = $this->postJson("/trace-replay/traces/{$trace->id}/ai-prompt");
$response->assertOk();
$response->assertJsonPath('status', 'success');
@@ -916,7 +916,7 @@
// ββ AiPromptService::callOpenAI ββββββββββββββββββββββββββββββββββββββββββββ
it('AiPromptService callOpenAI returns null when no key configured', function () {
- config(['tracereplay.ai.openai_api_key' => null]);
+ config(['trace-replay.ai.openai_api_key' => null]);
$result = app(AiPromptService::class)->callOpenAI('test prompt');
@@ -924,7 +924,7 @@
});
it('AiPromptService callOpenAI returns response when key configured', function () {
- config(['tracereplay.ai.openai_api_key' => 'test-key']);
+ config(['trace-replay.ai.openai_api_key' => 'test-key']);
Http::fake([
'api.openai.com/*' => Http::response([
@@ -938,7 +938,7 @@
});
it('AiPromptService callOpenAI returns null on API failure', function () {
- config(['tracereplay.ai.openai_api_key' => 'test-key']);
+ config(['trace-replay.ai.openai_api_key' => 'test-key']);
Http::fake([
'api.openai.com/*' => Http::response([], 500),
@@ -952,7 +952,7 @@
// ββ TraceBar Component ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
it('TraceBar renders empty when disabled', function () {
- config(['tracereplay.enabled' => false]);
+ config(['trace-replay.enabled' => false]);
$component = new TraceBar;
$result = $component->render();
@@ -969,11 +969,11 @@
// ββ Export Command β output to file βββββββββββββββββββββββββββββββββββββββββ
-it('tracereplay:export writes to output file', function () {
+it('trace-replay:export writes to output file', function () {
$trace = Trace::factory()->create(['name' => 'File Export']);
$tmpFile = tempnam(sys_get_temp_dir(), 'tr_export_');
- $this->artisan('tracereplay:export', [
+ $this->artisan('trace-replay:export', [
'id' => $trace->id,
'--format' => 'json',
'--output' => $tmpFile,
@@ -987,8 +987,8 @@
// ββ ExportTraceCommand β status validation βββββββββββββββββββββββββββββββββ
-it('tracereplay:export rejects invalid status', function () {
- $this->artisan('tracereplay:export', ['--status' => 'invalid', '--format' => 'json'])
+it('trace-replay:export rejects invalid status', function () {
+ $this->artisan('trace-replay:export', ['--status' => 'invalid', '--format' => 'json'])
->assertExitCode(1);
});
@@ -1028,8 +1028,8 @@
});
config([
- 'tracereplay.notifications.channels' => ['mail'],
- 'tracereplay.notifications.mail.to' => 'test@example.com',
+ 'trace-replay.notifications.channels' => ['mail'],
+ 'trace-replay.notifications.mail.to' => 'test@example.com',
]);
$trace = Trace::factory()->create([
@@ -1045,8 +1045,8 @@
Http::fake(['*' => Http::response([], 200)]);
config([
- 'tracereplay.notifications.channels' => ['slack'],
- 'tracereplay.notifications.slack.webhook_url' => 'https://hooks.slack.test/webhook',
+ 'trace-replay.notifications.channels' => ['slack'],
+ 'trace-replay.notifications.slack.webhook_url' => 'https://hooks.slack.test/webhook',
]);
$trace = Trace::factory()->create([
@@ -1065,7 +1065,7 @@
it('MCP RPC trigger_replay returns error for trace without payload', function () {
$trace = Trace::factory()->create();
- $response = $this->postJson('/api/tracereplay/mcp', [
+ $response = $this->postJson('/api/trace-replay/mcp', [
'method' => 'trigger_replay',
'params' => ['trace_id' => $trace->id],
'id' => 10,
diff --git a/tests/TestCase.php b/tests/TestCase.php
index bbef4a5..363abc7 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -31,7 +31,7 @@ protected function getEnvironmentSetUp($app)
'prefix' => '',
]);
- $app['config']->set('tracereplay.enabled', true);
+ $app['config']->set('trace-replay.enabled', true);
$app['config']->set('app.key', 'base64:'.base64_encode(str_repeat('a', 32)));
}
}