ranetrace/ranetrace-php is the framework-agnostic PHP SDK. It captures errors, log records, custom events and browser JavaScript errors, buffers them to a local file spool, and ships them to the Ranetrace API in batches.
Using Laravel? Install ranetrace/ranetrace-laravel instead. It wires the same capture into the framework for you.
From the package's composer.json:
ext-curl and ext-jsonmonolog/monolog ^3.0 and psr/log ^3.0composer require ranetrace/ranetrace-php
RANETRACE_ENABLED=true
RANETRACE_KEY=your-key-here
Both are needed. RANETRACE_ENABLED is the master switch and RANETRACE_KEY authenticates with the API; nothing is captured while either is missing.
Every config key falls back to a RANETRACE_* environment variable, read from $_ENV, $_SERVER or getenv() in that order. So the key can come from the environment, or you can pass it in code.
Build the SDK once, as early in your bootstrap as you can, and keep the instance around:
use Ranetrace\Php\Ranetrace;
$ranetrace = Ranetrace::init([
'key' => 'your-key-here',
'environment' => 'production',
]);
Ranetrace::init() remembers the instance, so code that cannot be handed the object can reach it with Ranetrace::instance() (which returns null when init() has not run). If you would rather own the instance yourself, new Ranetrace([...]) builds one without touching the static.
Every option is optional except the key. Values not passed fall back to their environment variable, then to the default:
| Config key | Environment variable | Default |
|---|---|---|
enabled |
RANETRACE_ENABLED |
true |
key |
RANETRACE_KEY |
empty |
base_url |
RANETRACE_BASE_URL |
https://api.ranetrace.com/v1 |
environment |
RANETRACE_ENVIRONMENT |
APP_ENV if set, else production |
project_root |
RANETRACE_PROJECT_ROOT |
the directory above vendor/, found via Composer's autoloader |
buffer_path |
RANETRACE_BUFFER_PATH |
ranetrace-buffer inside the system temp directory |
framework |
RANETRACE_FRAMEWORK |
none |
framework_version |
RANETRACE_FRAMEWORK_VERSION |
none |
fingerprint_salt |
RANETRACE_FINGERPRINT_SALT |
falls back to your API key |
flush_on_shutdown |
RANETRACE_FLUSH_ON_SHUTDOWN |
true |
user_resolver |
config only | none |
A malformed value is a mistake you can still fix, so it fails loudly: a non-string key or a non-callable user_resolver throws an InvalidArgumentException from the constructor. Everything that happens later, while capturing, is caught and dropped instead.
Error tracking is on by default, but nothing installs PHP's handlers for you:
$ranetrace->registerErrorHandlers();
This is the equivalent of the Laravel package's Ranetrace::handles($exceptions) wiring. It installs an exception handler and a shutdown handler, and it keeps whatever your application registered before it. See PHP error tracking for what that covers and what it deliberately leaves alone.
Captured items are written to a local file spool, one JSON file per type, under buffer_path. The spool is drained by a flush, which is the only place the SDK talks to the API.
There are two ways to flush, and they are safe to use together because the spool is locked and drained atomically.
On shutdown, automatically. flush_on_shutdown is true by default, so every PHP process drains the buffer as it exits. This is what makes the SDK work in the simplest possible deployment, with nothing scheduled.
From cron, on a schedule. The package ships a small binary that flushes once and exits:
* * * * * /path/to/your-app/vendor/bin/ranetrace-flush >/dev/null 2>&1
Every minute is the right cadence: the API accepts 60 requests per minute per endpoint per key, and one run sends at most one batch per type. The command reads its configuration entirely from the RANETRACE_* environment, because a cron entry has no application bootstrap to read a config array from, so make sure your key is in the environment the cron job runs with. It prints nothing on success and exits 0; it exits 1 when it could not run at all. Pass --type=errors, --type=events, --type=logs or --type=javascript_errors to drain one type only.
Cron is worth setting up on anything busier than a low-traffic site. Draining on shutdown ships telemetry at the pace of your traffic, which on a quiet site can mean sitting in the buffer for a while, and a spool that goes untouched for an hour, neither written to nor drained, is discarded on its idle TTL (batch.buffer_ttl, 3600 seconds).
The default buffer path lives in the system temp directory, which is fine for a single machine where every PHP process can read the same files. Two situations need an explicit path:
RANETRACE_BUFFER_PATH=/var/www/shared/ranetrace-buffer
0770, group readable and writable, so both can share it as long as they share a group. If they cannot, point them at a directory they can both write.The SDK also writes its own diagnostics into this directory, as a daily internal-YYYY-MM-DD.log file, so it is the first place to look when something is not arriving.
The SDK never guesses what it is running inside. If your application is on a framework, name it so error reports and log records say where they came from:
use Ranetrace\Php\Ranetrace;
use Symfony\Component\HttpKernel\Kernel;
$ranetrace = Ranetrace::init([
'key' => getenv('RANETRACE_KEY') ?: '',
'framework' => 'Symfony',
'framework_version' => Kernel::VERSION,
]);
Both keys are optional and both default to nothing, which reads as "not said" rather than "no framework".
Two other keys exist for the same reason. project_root is the directory reported file paths are made relative to; it is found through Composer's autoloader, which is right for almost every install. environment is the deployment environment name, taken from APP_ENV when you have one.
A minimal bootstrap with all four features wired up:
<?php
require __DIR__.'/../vendor/autoload.php';
use Monolog\Logger;
use Ranetrace\Php\Ranetrace;
$ranetrace = Ranetrace::init([
'key' => getenv('RANETRACE_KEY') ?: '',
'environment' => getenv('APP_ENV') ?: 'production',
'logging' => ['enabled' => true],
'javascript_errors' => ['enabled' => true],
'user_resolver' => static function (): ?array {
$user = currentUser();
return $user === null ? null : ['id' => $user->id, 'email' => $user->email];
},
]);
$ranetrace->registerErrorHandlers();
$logger = new Logger('app');
$logger->pushHandler($ranetrace->monologHandler());
// Mount the JavaScript error relay wherever your router sends this path.
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['REQUEST_URI'] === '/ranetrace/js-errors') {
$ranetrace->relay()->handle();
exit;
}
And in your layout, before </body>:
<?= $ranetrace->javascriptSnippet(['endpoint' => '/ranetrace/js-errors']) ?>