
# PHP installation

`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](/docs/installation) instead. It wires the same capture into the framework for you.

## Requirements

From the package's `composer.json`:

- PHP **^8.4**
- `ext-curl` and `ext-json`
- `monolog/monolog` **^3.0** and `psr/log` **^3.0**

## 1. Install the package

```bash
composer require ranetrace/ranetrace-php
```

## 2. Add your key to your environment

```dotenv
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.

## 3. Bootstrap the SDK

Build the SDK once, as early in your bootstrap as you can, and keep the instance around:

```php
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.

## 4. Register the error handlers

Error tracking is on by default, but nothing installs PHP's handlers for you:

```php
$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](/docs/php-error-tracking) for what that covers and what it deliberately leaves alone.

## The buffer and flushing

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:

```bash
* * * * * /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, so on a quiet site captured data waits in the spool until the next request flushes it. Nothing is lost while it waits: the spool is kept until it is delivered, and only `batch.max_buffer_size` (5000 items per type) bounds it, dropping the oldest items past that. Cron makes it arrive promptly.

### Buffer path on multi-worker hosts

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:

```dotenv
RANETRACE_BUFFER_PATH=/var/www/shared/ranetrace-buffer
```

- **Your web processes and your cron job run as different users.** The SDK creates the directory `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.
- **Your application runs on more than one machine.** Each machine keeps its own spool and flushes it independently. That works, but every machine then needs a flush path of its own, whether that is the shutdown flush or its own cron entry.

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.

## Applications on a framework

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:

```php
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 complete example

A minimal bootstrap with all four features wired up:

```php
<?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>`:

```php
<?= $ranetrace->javascriptSnippet(['endpoint' => '/ranetrace/js-errors']) ?>
```

## Next steps

- [PHP error tracking](/docs/php-error-tracking)
- [PHP centralized logging](/docs/php-centralized-logging)
- [PHP event tracking](/docs/php-event-tracking)
- [PHP JavaScript errors](/docs/php-javascript-errors)
