PHP Integration
Send logs from PHP applications to ScryWatch with the official scrywatch/php Composer package, or plain HTTP if you'd rather not add a dependency.
PHP Integration
ScryWatch has an official PHP client, scrywatch/php, plus a Laravel integration package. If you’d rather not add a dependency, ScryWatch also accepts logs over plain HTTP — use curl, Guzzle, or any HTTP client to send events to the ingest endpoint directly.
What you’ll need
- A ScryWatch project API key (see API Keys & Settings)
- PHP 8.1+
Option A: Official Composer package (recommended)
Install the client:
composer require scrywatch/php
use ScryWatch\ScryWatchClient;
$client = new ScryWatchClient(
endpoint: 'https://api.scrywatch.com',
apiKey: getenv('SCRYWATCH_API_KEY'),
service: 'api',
environment: 'production',
);
$client->info('User signed in', ['user_id' => 'u_123']);
$client->warn('Slow query', ['duration_ms' => 1450]);
$client->error('Payment failed', ['order_id' => 'ord_456', 'reason' => 'insufficient_funds']);
By default the client uses curl. To use your own PSR-18 HTTP client (Guzzle, Symfony HttpClient, etc.):
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Psr7\HttpFactory;
use ScryWatch\ScryWatchClient;
$factory = new HttpFactory();
$client = new ScryWatchClient(
endpoint: 'https://api.scrywatch.com',
apiKey: getenv('SCRYWATCH_API_KEY'),
httpClient: new GuzzleClient(),
requestFactory: $factory,
streamFactory: $factory,
);
Client API:
| Method | Description |
|---|---|
info(message, metadata?) | Send an info-level log |
warn(message, metadata?) | Send a warn-level log |
error(message, metadata?) | Send an error-level log |
debug(message, metadata?) | Send a debug-level log |
log(level, type, message, metadata?) | Send with explicit level and type |
send(events[]) | Send a batch of LogEvent objects or raw arrays |
setUserId(id) | Attach a user ID to all subsequent events |
The client retries up to maxRetries times (default 3) on network errors or 5xx responses, with backoff of 500ms × attempt number. 4xx responses throw ScryWatchException immediately — no retry.
Laravel
If you’re on Laravel, add the companion package instead — it wraps scrywatch/php in a Monolog-compatible log channel:
composer require scrywatch/laravel
See the scrywatch/laravel package docs for the config/scrywatch.php config and the ScryWatchServiceProvider. It registers a scrywatch log channel you can select via LOG_CHANNEL=scrywatch (or add it to a stack channel), so anything written with Laravel’s Log facade is forwarded to ScryWatch — no custom handler code required.
Option B: Plain HTTP (no dependency)
If you’d rather not add a Composer dependency, you can call the ingest endpoint directly with curl or any HTTP client.
Endpoint
POST https://api.scrywatch.com/api/ingest
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Step 1: Send a log event
<?php
function scrywatch_log(string $apiKey, string $level, string $message, array $metadata = []): void {
$payload = json_encode([
'events' => [[
'timestamp' => (int) (microtime(true) * 1000),
'level' => $level, // 'info' | 'warn' | 'error' | 'debug'
'type' => 'custom',
'message' => $message,
'service' => $_ENV['APP_SERVICE'] ?? 'php-app',
'environment' => $_ENV['APP_ENV'] ?? 'production',
'metadata' => $metadata ?: null,
]],
]);
$ch = curl_init('https://api.scrywatch.com/api/ingest');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 5,
]);
curl_exec($ch);
curl_close($ch);
}
// Usage
scrywatch_log('YOUR_API_KEY', 'info', 'User signed up', ['user_id' => '123']);
scrywatch_log('YOUR_API_KEY', 'error', 'Payment failed', ['order_id' => '456', 'reason' => 'card_declined']);
Step 2: Batch multiple events
Send up to 50 events in one request to reduce overhead (this is the ingest endpoint’s hard cap — larger batches are rejected with a 400):
<?php
function scrywatch_batch(string $apiKey, array $events): array {
$payload = json_encode(['events' => $events]);
$ch = curl_init('https://api.scrywatch.com/api/ingest');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
curl_close($ch);
return json_decode($body, true) ?? [];
// Returns: ['inserted' => N] on success (HTTP 202)
}
// Build and send a batch
$events = [];
foreach ($errors as $err) {
$events[] = [
'timestamp' => (int) (microtime(true) * 1000),
'level' => 'error',
'type' => 'custom',
'message' => $err->getMessage(),
'service' => 'php-app',
'environment' => 'production',
'metadata' => [
'file' => $err->getFile(),
'line' => $err->getLine(),
],
];
}
scrywatch_batch('YOUR_API_KEY', $events);
Laravel: If you’re on Laravel and want a log channel instead of hand-rolling one around
scrywatch_log(), use the officialscrywatch/laravelpackage described in Option A above rather than writing a custom Monolog handler.
Event fields reference
| Field | Type | Required | Description |
|---|---|---|---|
timestamp | number | ✅ | Unix milliseconds — use (int)(microtime(true) * 1000) |
level | string | ✅ | info | warn | error | debug |
type | string | ✅ | custom | crash | session | navigation | api_call |
message | string | ✅ | Log message |
service | string | — | Service name |
environment | string | — | e.g. production, staging |
user_id | string | — | User identifier |
session_id | string | — | Session identifier |
metadata | object | — | Any additional key/value pairs |
OpenTelemetry note
If you are already running an OpenTelemetry Collector, you can route traces to ScryWatch via the OTLP endpoint. For logs, use the HTTP ingest endpoint above — OTLP log ingestion is not yet supported.
See the OpenTelemetry guide for Collector pipeline configuration.