dockcodes / dock-ray
PHP SDK for DockRay - application error and performance monitoring (https://dock.codes)
Requires
- php: ^7.4 || ^8.0
- ext-json: *
- ext-mbstring: *
- guzzlehttp/promises: ^1.0|^2.0
- jean85/pretty-package-versions: ^2.0.4
- nyholm/psr7: ^1.8
- nyholm/psr7-server: ^1.1
- php-http/client-common: ^2.7
- php-http/discovery: ^1.19
- php-http/httplug: ^2.4
- php-http/message-factory: ^1.1
- psr/http-factory: ^1.0
- psr/log: ^1.0|^2.0|^3.0
- symfony/http-client: ^4.4|^5.4|^6.0|^7.0
- symfony/options-resolver: ^4.4|^5.4|^6.0|^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.40
- monolog/monolog: ^1.3|^2.0|^3.0
- php-http/mock-client: ^1.6
- phpstan/phpstan: ^1.10|^2.0
- phpunit/phpunit: ^9.6|^10.0|^11.0
Suggests
- ext-zlib: Compresses the request body before it is sent (enabled by default)
- monolog/monolog: Reports records written to a Monolog channel
- php-http/guzzle7-adapter: Delivers events through Guzzle instead of the Symfony HTTP client
Provides
None
Conflicts
- php-http/client-common: 1.8.0
Replaces
None
README
Framework-agnostic PHP client for DockRay. It reports uncaught exceptions, PHP errors and HTTP transactions to a DockRay project.
Framework bridges build on this package and are shipped separately:
dock-ray-laravel, dock-ray-symfony, dock-ray-drupal,
dock-ray-joomla and the dockray WordPress plugin.
Installation
composer require dockcodes/dock-ray
Requires PHP 7.4 or newer (7.4, 8.0 through 8.4). The Monolog handler works with Monolog 1, 2 and 3.
Configuration
A project is identified by two values taken from the DockRay panel:
| Value | Where it goes | Secret |
|---|---|---|
| project token | path of the ingest URL | no |
| private key | Authorization: Bearer header |
yes |
use function Dock\Ray\init; init([ 'token' => 'PROJECT_TOKEN', 'private_key' => 'PROJECT_PRIVATE_KEY', 'environment' => 'production', 'release' => '1.4.0', ]);
token, and private_key also default to the RAY_TOKEN and RAY_PRIVATE_KEY server variables, so a deployment can
configure the SDK without touching code. When either credential is missing the
SDK stays loaded but sends nothing - it never throws for being unconfigured.
Options worth knowing
| Option | Default | Meaning |
|---|---|---|
environment |
null |
shown as the environment column in the panel |
release |
null |
version of the deployed application |
error_types |
error_reporting() |
which PHP errors become events |
sample_rate |
1.0 |
share of error events that get sent |
traces_sample_rate |
0.0 |
share of transactions that get sent; 0 disables tracing |
send_default_pii |
false |
attaches user id, e-mail, IP and user agent |
enable_compression |
true |
gzips the request body |
send_after_response |
true on web, false on CLI |
queues events and sends them after the response |
max_request_body_size |
medium |
none, small, medium or always |
in_app_exclude |
[] |
paths whose frames are marked as vendor code |
before_send |
identity | last chance to modify or drop an event |
Reporting
use Dock\Ray\Breadcrumb; use Dock\Ray\Severity; use function Dock\Ray\{addBreadcrumb, captureException, captureMessage, configureScope}; captureMessage('Cache cleared', Severity::info()); try { $order->settle(); } catch (\Throwable $exception) { captureException($exception); } addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, 'auth', 'User logged in')); configureScope(function (\Dock\Ray\State\Scope $scope) { $scope->setTag('feature', 'payments'); $scope->setUser(['id' => 42, 'email' => 'user@example.com']); });
Uncaught exceptions and fatal errors are captured automatically by the default
integrations; pass 'default_integrations' => false to opt out.
When events are sent
By default the SDK reports after the response. Events are queued during the request and delivered from a shutdown handler, once PHP-FPM or LiteSpeed has closed the connection to the browser. A slow or unreachable panel then costs the visitor nothing.
Where the SAPI cannot close the connection early (mod_php), the queue is still flushed at shutdown, so reporting never happens in the middle of handling a request.
The queue holds at most 50 events per request; beyond that new events are
dropped, since the panel counts repeats by fingerprint anyway. Set
'send_after_response' => false to send inline - the default is already
false under CLI, where a long-running worker should not hold reports until
the process ends.
Transactions
A transaction measures one HTTP request. The panel needs the request URL, the method and the response status, so the SDK ships a helper that fills all three:
use Dock\Ray\Framework\HttpTransaction; $transaction = HttpTransaction::start('GET /checkout', $url, 'GET'); $transaction->measureHandling('app.handle'); // ... handle the request ... $transaction->finish($response->getStatusCode());
Transactions are only sent when traces_sample_rate is above zero.
Browser errors
A browser cannot authenticate against the panel: the private key would have to sit in page source. The SDK therefore ships the collector script and the server-side normaliser, and the application in the middle does the reporting:
browser → your application → DockRay
(no key) (project key)
use Dock\Ray\Browser\BrowserEvent; use Dock\Ray\Browser\Collector; // Rendering the page: point the collector at your own endpoint. $config = Collector::config(endpoint: '/errors/browser', token: $csrfToken); // Collector::scriptPath() is the file to serve or enqueue. // Receiving a report: everything in $payload is untrusted. $event = BrowserEvent::fromArray($payload, $referer, $userAgent); if ($event !== null) { $hub->captureEvent($event); }
fromArray() returns null for anything it cannot make an event of, and
truncates every string it keeps. Guard the endpoint yourself - a CSRF token, a
body size limit and a per-IP rate limit are what the shipped plugins use.
The collector caps itself as well: one report per distinct error per page view,
ten per page view by default, with ResizeObserver loop and cross-origin
Script error. filtered out.
Writing a bridge for another framework
Every bridge does the same four things, and the SDK has a seam for each:
- Boot - call
init()(or build a client withClientBuilder) as early as the framework allows, so errors raised during bootstrap are still reported. - Report - hand the framework's exception handler to
captureException(). Frameworks with a PSR-3 logger can instead registerMonolog\Handleror anyLoggerInterfacedecorator that forwards to the hub. - Trace - wrap request handling in
Framework\HttpTransaction. - Enrich - set the user and tags on the scope, and mark framework code as
vendor code through
in_app_excludeso stack traces point at the application.
Anything the framework knows better than the SDK - how to read the current
request, who the current user is, where the application root lives - is passed
in through options and the scope. Nothing in Dock\Ray reaches back into a
framework.
License
MIT. See LICENSE.
Opt-in vulnerability monitoring (SDK 1.4+)
Inventory reporting is disabled by default and does not run while serving HTTP requests. It sends only the configured site URL, PHP/agent versions and names and versions of installed production Composer packages. DockRay matches them against Composer advisories and uses the project's existing vulnerability notifications. An installed framework, including Laravel, is a Composer package; this feature does not scan source code, the OS or PHP extensions.
For the CLI, supply these variables through your process manager or secure cron
environment, alongside the existing RAY_TOKEN and RAY_PRIVATE_KEY:
RAY_INVENTORY=true RAY_INVENTORY_SITE_URL=https://your-app.example.com # Optional: omit internal packages (comma-separated glob patterns) RAY_INVENTORY_EXCLUDE_PACKAGES=company/*
Run from the application root (Composer's vendor/bin/dockray-inventory proxy):
vendor/bin/dockray-inventory --force
Then run vendor/bin/dockray-inventory hourly with cron. The reporter sends an
unchanged snapshot every 12 hours, or a changed snapshot on the next invocation.
Use --force after deployment; it still respects retry backoff. Cron does not
load .env automatically: configure its environment securely, and do not put
private keys in command arguments or a world-readable crontab.
For an existing custom CLI scheduler, use the same reporter directly:
$reporter = new \Dock\Ray\Inventory\InventoryReporter( new \Dock\Ray\Options([ 'token' => $projectToken, 'private_key' => $privateKey, 'inventory_enabled' => true, 'inventory_site_url' => 'https://your-app.example.com', 'inventory_exclude_packages' => ['company/*'], ]), $applicationRoot, $privateStateDirectory ); $result = $reporter->report();
composer.json, composer.lock and vendor/composer/installed.json must exist.
Only installed entries in the lock file's production packages list are sent:
packages-dev and the root application are excluded. Private package names are
still names: explicitly exclude internal namespaces if you do not want to share
them. Excluded packages cannot be checked for vulnerabilities.
No source, .env, configuration values, repository URLs, descriptions, paths or
user data are included. Authentication uses a Bearer header over verified HTTPS,
without redirects. There are request/response size limits, timeouts, retry
backoff and a filesystem lock. The private state directory (0700, files 0600)
stores only a snapshot hash and timing information. Keep it persistent and outside
the public web root; CLI overrides are RAY_INVENTORY_PROJECT_DIR and
RAY_INVENTORY_STATE_DIR. Multiple workers on different machines need their own
scheduler coordination; the built-in lock is local to a filesystem.
The configured site URL must match the production or development URL of the DockRay project. Never derive it from an incoming Host header. Missing or invalid Composer metadata aborts the report instead of clearing the server's inventory. Reports show results immediately; confirmed findings are notified by DockRay's existing vulnerability alert scheduler (currently every 15 minutes), subject to project recipients, notification configuration and duplicate suppression.
Drupal module 1.1+ uses SDK 1.5+ and passes drupal as the reporter's platform.
The collector reports the installed drupal/core version as platform_version;
DockRay matches Drupal core/extensions against their dedicated advisory keys,
and other libraries against Composer. Enable inventory in the Drupal module
settings and run Drupal cron; no separate SDK cron command is needed there.