Configuration - PHP
Options worth knowing
| Option | Default | Meaning |
|---|---|---|
environment | null | 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 sent |
traces_sample_rate | 0.0 | share of transactions sent; 0 disables tracing |
send_default_pii | false | attaches user id and e-mail, IP address 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 |
Environments
Every environment should report under an unambiguous name - production, staging, preview. The name is a column in the panel and a filter on the error list, so without it a production outage looks exactly like an error someone triggered in a test. Leave your local environment without credentials: with no token and no key the integration loads and stays silent, so you do not need a separate switch to turn it off.
in_app_exclude decides whether a stack is readable
This is the most underrated option in the SDK. Frames under the paths listed in in_app_exclude are marked as vendor code, so the panel shows the failing place in your application rather than the deepest frame inside a library:
init([
'in_app_exclude' => [__DIR__.'/../vendor'],
]);
Without it every error looks like it happened in vendor/ - and what travels to Jira and Notion is precisely the significant part of the stack, meaning the frames marked as application code.
When events are sent
By default 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 monitoring service then does not delay the page for the visitor. Where the SAPI cannot close the connection early (mod_php, for instance), the queue is still flushed at shutdown, so reporting does not happen in the middle of handling a request.
The queue holds at most 50 events per request; beyond that new events are dropped, since repeated events are grouped by fingerprint anyway. 'send_after_response' => false switches to sending inline; on CLI that is already the default, because a long-running worker should not hold reports until the process ends.
HTTP transactions
use Dock\Ray\Framework\HttpTransaction;
$transaction = HttpTransaction::start('GET /checkout', $url, 'GET');
$transaction->measureHandling('app.handle');
// ... handle the request ...
$transaction->finish($response->getStatusCode());
The first argument is a route pattern, not a concrete URL: GET /orders/{order}, not GET /orders/8123. Otherwise the transaction list falls apart into thousands of entries. Transactions are only sent when traces_sample_rate is above zero.
Enriching events
use Dock\Ray\Breadcrumb;
use function Dock\Ray\{addBreadcrumb, configureScope};
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']);
});
Tags are the cheapest way to filter one area of the application in the panel. Set user data deliberately - it is personal data, and send_default_pii is off by default for a reason.
Browser errors
A browser cannot authenticate directly with DockRay, because the project private key must stay on the server. The SDK therefore provides a collector script and a server-side normaliser, and the event travels like this:
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 turn into an event and truncates every string it keeps. You write the endpoint's guards yourself - a CSRF token, a body size limit and a per-IP rate limit are exactly what our own plugins use. The collector caps itself too: one report per distinct error per page view and ten per page view, with ResizeObserver loop and cross-origin Script error. filtered out.
Protecting the private key
The private key is a project secret, not an identifier. Keep it in environment variables, in a secrets manager or in the server configuration - never in the repository, in logs, in a screenshot or in code sent to the browser. One project can hold many keys, so production and staging should each get their own: either can be revoked on its own without interrupting the others. A suspicion that a key leaked is reason enough to revoke it and generate a new one.