Documentation menu
phphttpintegration

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

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:

MethodDescription
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 official scrywatch/laravel package described in Option A above rather than writing a custom Monolog handler.

Event fields reference

FieldTypeRequiredDescription
timestampnumberUnix milliseconds — use (int)(microtime(true) * 1000)
levelstringinfo | warn | error | debug
typestringcustom | crash | session | navigation | api_call
messagestringLog message
servicestringService name
environmentstringe.g. production, staging
user_idstringUser identifier
session_idstringSession identifier
metadataobjectAny 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.