Search by

octri / monitoring

octri

Standalone application monitoring and error reporting for PHP.

v1.2.0 2026-09-19 10:08 UTC

This package is auto-updated.

Last update: 2026-10-24 00:29:04 UTC


README

Error and performance monitoring for PHP applications. Report an uncaught Throwable with its stack frames, emit your own application events, time spans into a request waterfall, and continue a W3C distributed trace that started in whichever client called you.

Octri turns an OpenAPI spec into a documentation site, client SDKs for ten languages, an MCP server your AI assistant can call, and monitoring for the API behind them. This package is the PHP monitoring runtime, and it works on its own: a generated Octri API SDK is not required. See octri.dev/monitoring.

PHP 8.1 or newer, with ext-curl and ext-json.

Install

composer require octri/monitoring

Setup

Call init once during bootstrap.

use Octri\Monitoring\Config;
use Octri\Monitoring\Octri;

Octri::init(new Config(
    'https://monitoring.example.com',   // your monitoring base URL
    getenv('OCTRI_TOKEN') ?: null,      // your project ingest token
    '<your project id>',                // the dashboard project id
    getenv('GIT_SHA') ?: null,          // optional
));

Hosted users copy the project-scoped URL, token, and environment from the Monitoring connection settings in the dashboard. Pass null for the token only when you point at an open self-hosted ingest endpoint.

Enable Monitoring for this project in the dashboard (or its API) before deploying. Enabling it creates or rotates the project ingest token; events are rejected while Monitoring is off. This SDK configures local reporting only and cannot enable Monitoring for a project.

Every call is best effort and carries an idempotency key. Transport failures are suppressed, so a monitoring outage cannot affect the application it is watching.

Application events

Octri::captureEvent('checkout.completed', [
    'level'   => 'info',
    'user'    => ['id' => $customer->id],
    'tags'    => ['region' => 'eu-west', 'plan' => 'growth'],
    'context' => ['orderId' => $order->id],
]);

The options array also accepts breadcrumbs, fingerprint, operationId, method, path, statusCode, latencyMs, attempt, requestId, and timestamp. Supplying eventId makes a retried delivery idempotent.

Errors

try {
    $response = $handler->handle($request);
} catch (Throwable $error) {
    Octri::captureError($error, [
        'method'     => 'POST',
        'path'       => '/orders',
        'statusCode' => 500,
    ]);
    throw $error;
}

To cover everything a request can throw, register it once at bootstrap:

set_exception_handler(static fn (Throwable $e) => Octri::captureError($e));

Joining the caller's trace

Your generated client SDK sends traceparent: 00-<traceId>-<spanId>-01 on every request. Read it on the way in and pass the result as trace, and the dashboard groups the client call and the server error under one traceId: the request that failed, beside the frame that threw.

$trace = Octri::traceFromHeader($_SERVER['HTTP_TRACEPARENT'] ?? null);

Octri::captureError($error, ['trace' => $trace]);

traceFromHeader(null) starts a fresh trace, so the same code path works for traffic that arrives without a header.

Spans

A span is a completed unit of work with ISO-8601 timestamps. Report one per request to get the waterfall, and one per sub-operation to see where the time went inside it.

$started = (new DateTimeImmutable())->format(DATE_ATOM);
$rows = $db->query($sql);

Octri::captureSpan([
    'traceId'      => $trace->traceId,
    'spanId'       => $spanId,
    'parentSpanId' => $trace->parentSpanId,
    'name'         => 'orders.list',
    'operationId'  => 'listOrders',
    'startTime'    => $started,
    'endTime'      => (new DateTimeImmutable())->format(DATE_ATOM),
]);

traceId, spanId, name, and startTime are required; a span missing any of them is dropped. Spans sharing a traceId nest by parentSpanId in the dashboard waterfall.

What gets redacted

Payloads are scrubbed on the way out, so a credential that ended up in a log line or a context object never reaches the dashboard.

Any key whose name looks like a credential (password, secret, token, apiKey, authorization, cookie, ssn and the rest of the usual list) has its value replaced with [redacted], at any depth. Matching ignores case and separators, so api_key, apiKey and X-API-KEY are all the same key.

Free text is swept too: the message, an error message and its stack, and anything else you send as a string. Bearer tokens, JWTs, card numbers and email addresses come out as [redacted]. A card number has to pass the Luhn check first, so an order number or a timestamp survives.

user.id is retained so the dashboard can count affected users. Direct identifier keys in user, including email, phone, address, userAgent, and ipAddress, are replaced with [redacted] before sending. Other strings inside user are not scanned as free text, so do not put personal data there.

Redaction is not consent management. This SDK does not collect or record end-user consent; call capture methods only after your application decides it may send data. Use the hook below to remove fields or drop events.

Add your own key names:

Octri::addScrubFields('accountNumber', 'otp');

Or take the payload yourself, and return null to drop the event:

Octri::setBeforeSend(fn (array $payload) => ($payload['path'] ?? null) === '/health' ? null : $payload);

Redaction runs after your hook, so a hook cannot leak a credential by accident.

The rest of Octri

Product What it does
API Studio Your OpenAPI spec becomes a hosted documentation site with a live request playground, editable page by page.
SDK Studio The same spec becomes client libraries for ten languages, versioned and released together.
MCP Your endpoints and docs become tools an AI assistant can call, generated from the same spec.
Monitoring Errors, traces, uptime and releases for the API, joined to the SDK calls that reached it.

Monitoring runtimes

More

MIT licensed.