replaystack / sdk
ReplayStack PHP SDK — backend event capture, exception tracing, and replay debugging for Laravel, Symfony, Slim, and plain PHP.
Requires
- php: >=8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.59
- illuminate/contracts: ^10.0 || ^11.0 || ^12.0
- illuminate/http: ^10.0 || ^11.0 || ^12.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^10.5
- psr/http-message: ^1.1 || ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^2.0 || ^3.0
- slim/slim: ^4.0
- symfony/config: ^6.0 || ^7.0
- symfony/dependency-injection: ^6.0 || ^7.0
- symfony/event-dispatcher: ^6.0 || ^7.0
- symfony/http-kernel: ^6.0 || ^7.0
Suggests
- illuminate/support: Required to use the Laravel integration (^10|^11|^12).
- psr/http-server-middleware: Required to use the PSR-15 middleware (Slim, Mezzio, etc.)
- symfony/http-kernel: Required to use the Symfony integration (^6|^7).
README
Backend event capture, exception tracing, and replay debugging for PHP — feature parity with the TypeScript and Python SDKs.
Drop-in support for Laravel, Symfony, Slim, and plain PHP. Built on PSR-7 / PSR-15 so it also slots into Mezzio, Yii 3, Tempest, Roadrunner, FrankenPHP — anything PSR-15 friendly.
Highlights
- One-line setup for Laravel, Symfony, Slim
- Per-request breadcrumb scope (no cross-talk between concurrent FPM workers / Swoole / RR coroutines)
- Sensitive-data masking with case-insensitive field matching and circular-reference safety
- Probabilistic sampling + ignore-paths with glob support (
/internal/*) - Offline queue with retry + queue-drop hook
flush()/close()lifecycle, plus auto-flush on PHP shutdown- Process guards: uncaught exceptions, fatal errors, FPM graceful shutdown
- Auth-mode auto-detection (
bearer,basic,api_key,cookie,other,none) - camelCase wire format identical to TS/Python SDKs — same backend, no surprises
Installation
composer require replaystack/sdk
Requirements:
- PHP 8.1+
- ext-curl
- ext-json
Quick start (any framework, any runtime)
use ReplayStack\Sdk\Config; use ReplayStack\Sdk\ReplayStack; use ReplayStack\Sdk\Runtime\ProcessGuards; $client = new ReplayStack(new Config( apiKey: getenv('REPLAYSTACK_API_KEY'), serviceName: 'orders-api', environment: 'production', sampleRate: 1.0, maskFields: ['phoneNumber', 'ssn'], ignoredPaths: ['/health', '/internal/*'], )); // Capture uncaught exceptions, fatal errors, and flush on shutdown. ProcessGuards::install($client); $client->addBreadcrumb('checkout started', 'http', 'info', ['orderId' => 42]); try { // ...your code... } catch (\Throwable $e) { $client->captureException($e, ['endpoint' => '/checkout', 'statusCode' => 500]); throw $e; }
Laravel
The package auto-registers a service provider and facade.
composer require replaystack/sdk php artisan vendor:publish --tag=replaystack-config
.env:
REPLAYSTACK_API_KEY=rs_live_xxxxxxxxxxxxxxxx REPLAYSTACK_ENVIRONMENT=${APP_ENV} REPLAYSTACK_SAMPLE_RATE=1.0
Use the facade anywhere:
use ReplayStack\Sdk\Integrations\Laravel\Facades\ReplayStack; ReplayStack::addBreadcrumb('querying products', 'db'); ReplayStack::captureException($e, ['endpoint' => '/products']);
See docs/LARAVEL.md.
Symfony
composer require replaystack/sdk
config/bundles.php:
return [ ReplayStack\Sdk\Integrations\Symfony\ReplayStackBundle::class => ['all' => true], ];
config/packages/replaystack.yaml:
replaystack: api_key: '%env(REPLAYSTACK_API_KEY)%' environment: '%env(APP_ENV)%' sample_rate: 1.0
The bundle wires a kernel.event_subscriber that captures every non-ignored
request, attaches breadcrumbs, and propagates x-trace-id automatically.
See docs/SYMFONY.md.
Slim / PSR-15
use ReplayStack\Sdk\Integrations\Slim\ReplayStackMiddleware; use ReplayStack\Sdk\Integrations\Psr15\MiddlewareOptions; $app->add(new ReplayStackMiddleware($client, new MiddlewareOptions( ignoredPaths: ['/health', '/internal/*'], )));
For raw PSR-15 (Mezzio, etc.), import
ReplayStack\Sdk\Integrations\Psr15\ReplayStackMiddleware directly.
See docs/SLIM.md.
Configuration
Every option can be set via env var or constructor argument; the constructor wins.
| Env var | Config field | Default |
|---|---|---|
REPLAYSTACK_API_KEY |
apiKey |
required |
REPLAYSTACK_ENDPOINT |
endpoint |
https://api.replaystack.co |
REPLAYSTACK_INGEST_URL |
ingestUrl |
{endpoint}/api/v1/ingest/events |
REPLAYSTACK_SERVICE_NAME |
serviceName |
– |
REPLAYSTACK_ENVIRONMENT |
environment |
APP_ENV or development |
REPLAYSTACK_APP_VERSION |
appVersion |
APP_VERSION |
REPLAYSTACK_COMMIT_HASH |
commitHash |
COMMIT_HASH |
REPLAYSTACK_ENABLED |
enabled |
true |
REPLAYSTACK_TIMEOUT_MS |
timeoutSeconds |
2.5s |
REPLAYSTACK_RETRIES |
maxRetries |
1 |
REPLAYSTACK_SAMPLE_RATE |
sampleRate |
1.0 |
REPLAYSTACK_CAPTURE_SUCCESS |
captureSuccess |
true |
REPLAYSTACK_MAX_PAYLOAD_SIZE_BYTES |
maxPayloadBytes |
524288 |
REPLAYSTACK_MAX_BREADCRUMBS |
maxBreadcrumbs |
50 |
REPLAYSTACK_OFFLINE_QUEUE_MAX |
offlineQueueMax |
100 |
REPLAYSTACK_FLUSH_INTERVAL_MS |
flushIntervalSeconds |
0 (off) |
Data masking
By default the following fields are scrubbed wherever they appear in headers or request/response payloads:
authorization, password, passwd, pwd, token, access_token, refresh_token,
apikey, api_key, secret, client_secret, cookie, set-cookie, cardnumber,
card_number, cvv, otp
Matching is case-insensitive and treats - / _ / space as equivalent. Add
your own with maskFields: ['phone_number', 'national_id'].
Lifecycle
$client->flush(); // synchronously drain the offline queue $client->flush(2.0); // with a deadline $client->close(); // stop accepting + drain
ProcessGuards::install($client) additionally:
- forwards uncaught exceptions to
captureException()and re-throws - captures fatal errors (
E_ERROR,E_PARSE,E_USER_ERROR, ...) viaregister_shutdown_function - calls
fastcgi_finish_request()before flushing under FPM so user-visible latency is unchanged
Testing
composer install vendor/bin/phpunit
44 tests / 200+ assertions covering utils, masking, auth detection, stacktrace parsing, client lifecycle, offline queue, and the PSR-15 middleware.
License
MIT — see LICENSE.