sirix / sentry-psr
PSR-compatible integration for Sentry — provides middleware for HTTP and error listener for console commands.
Requires
- php: ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0
- psr/container: ^1.1 || ^2.0
- psr/http-message: ^1.0 || ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^1.0 || ^2.0 || ^3.0
- sentry/sentry: ^4.0
- sirix/container-resolver: ^0.1.2
- sirix/redaction: ^2.1
Requires (Dev)
- bamarni/composer-bin-plugin: ^1.8
- laminas/laminas-cli: ^1.13
- monolog/monolog: ^3.10
- phpunit/phpunit: ^11.5
- psr/event-dispatcher: ^1.0
- symfony/console: ^7.4
- symfony/event-dispatcher: ^7.4
Suggests
- laminas/laminas-cli: Optional, allows integration with Laminas CLI commands for error reporting.
- mezzio/mezzio: For seamless integration with the Mezzio framework via ConfigProvider.
- monolog/monolog: Optional PSR-3 logger implementation for logging exceptions alongside Sentry.
- psr/event-dispatcher: Optional; needed if you want to type-hint PSR event dispatcher aliases.
- symfony/console: Optional; needed if you want to capture errors from Symfony Console commands.
- symfony/event-dispatcher: Optional; needed to wire Symfony EventDispatcher into the container.
README
PSR-15 & PSR-11 integration for Sentry with long-running-safe HTTP and console lifecycle handling.
This library provides:
SentryErrorMiddlewarefor PSR-15 HTTP pipelines.SentryHubFactoryfor explicit Sentry hub creation from PSR-11 config.SentryLifecyclefor isolated scope and flush lifecycle operations.SentryReporteras the recommended injectable API for capturing events and enriching scope.SentryCommandListenerandConsoleEventDispatcherFactoryfor Symfony Console/Laminas CLI integration.- A Mezzio/Laminas-friendly
ConfigProvider.
Requirements
- PHP
~8.2 || ~8.3 || ~8.4 || ~8.5 sentry/sentry^4.0- Core PSR packages for container, HTTP messages and HTTP middleware/handler.
Logger integration uses the required PSR-3 psr/log interface. Concrete logger implementations such as monolog/monolog remain optional.
Optional integrations are intentionally not required for HTTP-only projects:
monolog/monologas a logger implementation/service.symfony/consoleandsymfony/event-dispatcherfor console command capture.psr/event-dispatcherif your container/framework consumes the PSR event dispatcher alias.
Installation
composer require sirix/sentry-psr
Configuration
Use sentry for native Sentry SDK options and sentry_psr for this package's integration/lifecycle behavior.
sentry_psr is required at runtime. Copy the provided baseline config from src/config/sentry.global.php into your application config, then adjust it for your project. The ConfigProvider wires services only; it does not inject runtime defaults.
return [ 'sentry' => [ 'dsn' => 'https://<key>@sentry.io/<project>', 'environment' => 'production', 'release' => '1.2.3', ], 'sentry_psr' => [ 'isolate_http_scope' => true, 'isolate_console_scope' => true, 'set_current_hub' => true, 'default_integrations' => false, 'flush_on_http_error' => false, 'flush_on_console_terminate' => true, 'flush_timeout' => 2, 'capture_http_request_context' => true, 'capture_console_input' => true, 'log_console_command_start' => true, 'redaction' => [ 'replacement' => '[Filtered]', 'sensitive_key_pattern' => '/password|passwd|secret|token|api[_-]?key|authorization|cookie/i', 'max_depth' => 8, 'max_items_per_container' => 100, 'max_total_nodes' => 5000, 'use_default_rules' => false, 'rules' => [], 'regex_rules' => [], ], 'http_context' => [ 'enabled' => true, 'capture_headers' => false, 'capture_query_string' => false, 'allowed_headers' => [ 'User-Agent', 'X-Request-Id', ], 'request_id_headers' => [ 'X-Request-Id', 'X-Correlation-Id', ], 'request_id_attributes' => [ 'request_id', 'requestId', 'correlation_id', 'correlationId', ], 'allowed_attributes' => [ 'route', 'route_name', 'request_id', 'correlation_id', ], ], ], ];
sentry vs sentry_psr
sentryis passed toSentry\ClientBuilderand supports native Sentry PHP SDK options.sentry_psrcontrols this package: scope isolation, global hub behavior, flush timing and HTTP/console context enrichment.
Invalid container services, missing sentry_psr, or invalid config value types are reported through sirix/container-resolver exceptions, so misconfiguration fails early and with context.
Console input redaction
Console command arguments and options are redacted with a Sentry-specific sirix/redaction redactor configured from sentry_psr.redaction before they are attached to Sentry command context. The listener intentionally does not reuse an application-wide Sirix\Redaction\RedactorInterface service, so Sentry console sanitization remains isolated and predictable.
By default, keys matching password|passwd|secret|token|api[_-]?key|authorization|cookie are replaced with [Filtered], recursively and with traversal limits suitable for long-running workers. You can opt in to additional exact-key and regex-key redaction rules:
'redaction' => [ 'replacement' => '*', 'sensitive_key_pattern' => '/password|passwd|secret|token|api[_-]?key|authorization|cookie/i', 'max_depth' => 8, 'max_items_per_container' => 100, 'max_total_nodes' => 5000, 'use_default_rules' => false, 'rules' => [ 'email' => ['type' => 'email'], 'phone' => ['type' => 'phone'], 'card_number' => ['type' => 'start_end', 'start' => 6, 'end' => 4], ], 'regex_rules' => [ [ 'pattern' => '/customer[_-]?name/i', 'rule' => ['type' => 'name'], ], ], ],
Supported rule types are fixed_value, full_mask, start_end, unicode_start_end, email, phone, name, null and offset. use_default_rules=false is the baseline because the default sirix/redaction rules are intentionally broad and may hide more Sentry context than expected.
HTTP filtering is intentionally separate: request headers still use the http_context allowlist/blocklist model and are not controlled by console redaction settings. Raw query strings and request targets are not captured unless sentry_psr.http_context.capture_query_string=true.
Default integrations
default_integrations defaults to false through sentry_psr.default_integrations unless you explicitly set sentry.default_integrations.
This package intentionally avoids registering global SDK error/exception handlers by default so embedding applications and long-running workers remain in control of capture lifecycle. If you want the standard Sentry SDK integrations, opt in explicitly:
'sentry_psr' => [ 'default_integrations' => true, ],
DI Container wiring
ConfigProvider registers services only. Runtime options must be provided by your application config, usually by copying src/config/sentry.global.php.
ConfigProvider registers:
Sentry\State\HubInterface→Sirix\SentryPsr\Hub\SentryHubFactorySirix\SentryPsr\Lifecycle\SentryLifecycle→Sirix\SentryPsr\Lifecycle\SentryLifecycleFactorySirix\SentryPsr\Reporter\SentryReporter→Sirix\SentryPsr\Reporter\SentryReporterFactorySirix\SentryPsr\Middleware\SentryErrorMiddleware→Sirix\SentryPsr\Middleware\SentryErrorMiddlewareFactory
Console-related services are registered only when Symfony Console and EventDispatcher classes are available:
Sirix\SentryPsr\Listener\SentryCommandListener→Sirix\SentryPsr\Listener\SentryCommandListenerFactorySymfony\Component\EventDispatcher\EventDispatcher→Sirix\SentryPsr\ConsoleEventDispatcher\ConsoleEventDispatcherFactory
Aliases registered with console integration:
Psr\EventDispatcher\EventDispatcherInterface⇒Symfony\Component\EventDispatcher\EventDispatcherLaminas\Cli\SymfonyEventDispatcher⇒Symfony\Component\EventDispatcher\EventDispatcher
Long-running applications
When using this package in long-running PHP processes, make sure every request, job or command is executed inside an isolated Sentry scope. Otherwise user, tag, context and breadcrumb data may leak between independent units of work.
Version 2 behavior is long-running safe by default:
- each HTTP request runs inside
HubInterface::withScope(); - each console command pushes a scope on
ConsoleEvents::COMMANDand pops it onConsoleEvents::TERMINATE; SentryLifecycle::flush()provides an explicit flush hook for buffered/custom transports;SentryReportermutates the current request/command-local scope when called inside that lifecycle.
HTTP pipeline placement
Place SentryErrorMiddleware as early as possible so it wraps application middleware that might add Sentry context:
use Psr\Container\ContainerInterface; use Sirix\SentryPsr\Middleware\SentryErrorMiddleware; return function (App $app, ContainerInterface $container): void { $app->pipe(SentryErrorMiddleware::class); // routes and application middleware after Sentry };
If the middleware is not outermost, only downstream middleware/handlers are covered by the isolated scope.
Mezzio under RoadRunner
After copying the baseline config, these are the typical long-running HTTP values to keep or review:
return [ 'sentry_psr' => [ 'isolate_http_scope' => true, 'flush_on_http_error' => false, 'flush_timeout' => 2, ], ];
For async/buffered transports, call SentryLifecycle::flush() from your worker shutdown hook, or enable targeted flush points where needed.
Queue consumer / manual job lifecycle
use Sirix\SentryPsr\Lifecycle\SentryLifecycle; use Sirix\SentryPsr\Reporter\SentryReporter; final readonly class JobRunner { public function __construct( private SentryLifecycle $lifecycle, private SentryReporter $reporter, ) {} public function run(Job $job): void { $this->lifecycle->withIsolatedScope(function () use ($job): void { $this->reporter->setTag('job', $job->name()); $this->reporter->setContext('job', ['id' => $job->id()]); $job->handle(); }); $this->lifecycle->flush(); } }
HTTP context and sensitive data
HTTP context enrichment is enabled by default and stores method, path, host, scheme, selected scalar attributes and request/correlation id. Raw query strings and request targets are excluded by default and require sentry_psr.http_context.capture_query_string=true.
Sensitive data is excluded by default:
AuthorizationCookieSet-Cookie- raw request body
- arbitrary form fields
Headers are not captured unless sentry_psr.http_context.capture_headers=true, and even then only allowed_headers are included. Sensitive header names remain blocked.
Console integration
Install the optional console dependencies before enabling this integration:
composer require symfony/console symfony/event-dispatcher
SentryCommandListener subscribes to:
ConsoleEvents::COMMAND— push command-local scope, set command context and add breadcrumb;ConsoleEvents::ERROR— capture command exception in the active command scope;ConsoleEvents::TERMINATE— flush if configured and pop command scope.
Set sentry_psr.log_console_command_start=false to disable the PSR-3 Console command started info log while keeping command breadcrumbs and error reporting enabled.
Manual wiring:
use Sentry\State\HubInterface; use Sirix\SentryPsr\ConsoleEventDispatcher\ConsoleEventDispatcherFactory; use Sirix\SentryPsr\Lifecycle\SentryLifecycle; use Sirix\SentryPsr\Listener\SentryCommandListener; use Sirix\SentryPsr\Listener\SentryCommandListenerFactory; use Symfony\Component\EventDispatcher\EventDispatcher; return [ 'dependencies' => [ 'factories' => [ HubInterface::class => Sirix\SentryPsr\Hub\SentryHubFactory::class, SentryLifecycle::class => Sirix\SentryPsr\Lifecycle\SentryLifecycleFactory::class, SentryCommandListener::class => SentryCommandListenerFactory::class, EventDispatcher::class => ConsoleEventDispatcherFactory::class, ], ], ];
Console arguments/options are captured by default, but keys containing password, secret, token, api key, authorization or cookie are filtered.
Reporter API
Prefer SentryReporter from your container:
use Sirix\SentryPsr\Reporter\SentryReporter; final readonly class CheckoutService { public function __construct(private SentryReporter $sentry) {} public function checkout(): void { $this->sentry->setTag('feature', 'checkout'); $this->sentry->setUser(['id' => '123']); try { // ... } catch (\Throwable $exception) { $this->sentry->captureException($exception, ['step' => 'payment']); throw $exception; } } }
Static helper removal
Sirix\SentryPsr\Helper\SentryHelper was removed in 2.0. Use SentryReporter through DI instead, so the hub and scope are explicit and safe for long-running applications.
Global current hub
SentryHubFactory creates a hub explicitly through ClientBuilder and Hub.
By default sentry_psr.set_current_hub=true, so Sentry SDK global functions continue to use the configured hub. Set it to false if your application wants fully explicit DI-only hub usage:
'sentry_psr' => [ 'set_current_hub' => false, ],
If disabled, code using Sentry\captureException() or SentrySdk::getCurrentHub() will not automatically use this package's hub.
Migration
See UPGRADE-2.0.md for breaking changes and migration steps from 1.x.
Development
Useful Composer scripts:
composer validate --strict– validate composer metadata and lock filecomposer analyse-deps– run dependency analysercomposer test– run PHPUnitcomposer cs-check/composer cs-fix– code stylecomposer phpstan– static analysiscomposer rector– automated refactoring (dry-run by default)composer check– run the local quality gate
License
MIT © Sirix