Prism client — captures Laravel telemetry (logs, requests, errors, traces, queries, jobs, crons, replica metrics) and ships it to a Prism observability workspace.

Maintainers

Package info

github.com/misakstvanu/prism

pkg:composer/misakstvanu/prism

Transparency log

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-07-24 12:49 UTC

This package is auto-updated.

Last update: 2026-08-02 17:59:43 UTC


README

The client package for the Prism observability platform. Drop it into any Laravel 12+ application to capture logs, requests, errors, traces, queries, jobs, scheduled tasks and replica metrics and ship them, correlated by trace, to a Prism workspace.

Everything is captured automatically once two variables are set — no code changes, no manual instrumentation required. The package deliberately depends on nothing in the Prism server application, so it installs cleanly into a stranger's project.

Requirements

  • PHP 8.4+
  • Laravel 12 or 13 (illuminate/support ^12.0|^13.0)
  • A Prism workspace and an ingest API token (Console → Settings → API tokens)

Quickstart

Three steps and telemetry starts flowing.

1. Install

composer require misakstvanu/prism

The service provider (Misakstvanu\Prism\PrismServiceProvider) is auto-discovered — there is no provider to register and no kernel file to edit.

2. Set the two required variables

PRISM_TOKEN=prism_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PRISM_APP=your-app-slug
PRISM_ENDPOINT=https://your-workspace.prism.app/api/ingest

PRISM_TOKEN and PRISM_APP are the only required settings. PRISM_ENDPOINT defaults to the hosted endpoint; set it to your workspace's /api/ingest if you self-host. Everything else has a working default.

3. Verify

php artisan prism:check

This validates the config, lists which capture domains are on, and POSTs a single test event to confirm the endpoint accepts your token. A green run means the next request, job or scheduled task your app runs is already reporting to the console.

Nothing else is needed. Requests, exceptions, logs, queries, traces, jobs, scheduled tasks and replica metrics are all captured by listeners the provider registers at boot.

Required variables

Variable Purpose
PRISM_TOKEN The ingest token minted in Console → Settings → API tokens. With no token the package silently no-ops and logs a single warning at boot — it never throws.
PRISM_APP The application slug this process reports as, inside the workspace the token belongs to.

Configuration

Publish the config to override any default:

php artisan vendor:publish --tag=prism-config

The published config/prism.php documents every setting inline. Every value can also be driven by an environment variable, so most installs never publish the file.

Master switch and credentials

Key Env Default Meaning
enabled PRISM_ENABLED true The kill switch. false registers no listeners and adds zero overhead — the package is inert, as if uninstalled.
token PRISM_TOKEN null Ingest token. (required)
app PRISM_APP null Application slug this process reports as. (required)
endpoint PRISM_ENDPOINT https://prism.dev/api/ingest Where batches are POSTed. Point at your workspace's /api/ingest when self-hosting.

Identity

Key Env Default Meaning
environment PRISM_ENVIRONMENT APP_ENV Deployment tier (production, staging, …) tagged onto every event.
replica PRISM_REPLICA gethostname() The name this process reports as. One replica row auto-registers per name; override with the pod/container name where the hostname is not meaningful.

Per-domain capture toggles

Each maps to one telemetry signal. A domain set to false registers no listener, so silencing a noisy source costs nothing.

Key Env Default
capture.requests PRISM_CAPTURE_REQUESTS true
capture.exceptions PRISM_CAPTURE_EXCEPTIONS true
capture.logs PRISM_CAPTURE_LOGS true
capture.queries PRISM_CAPTURE_QUERIES true
capture.traces PRISM_CAPTURE_TRACES true
capture.jobs PRISM_CAPTURE_JOBS true
capture.schedules PRISM_CAPTURE_SCHEDULES true
capture.metrics PRISM_CAPTURE_METRICS true

Client-side sample rates

A fraction in [0, 1] of each domain to keep before shipping — a first reduction on top of the server's trace-consistent sampling. 1.0 keeps everything. Exceptions are pinned to 1.0 and can never be sampled out at the client.

Key Env Default
sample_rates.requests PRISM_SAMPLE_REQUESTS 1.0
sample_rates.logs PRISM_SAMPLE_LOGS 1.0
sample_rates.queries PRISM_SAMPLE_QUERIES 1.0
sample_rates.traces PRISM_SAMPLE_TRACES 1.0
sample_rates.jobs PRISM_SAMPLE_JOBS 1.0
sample_rates.schedules PRISM_SAMPLE_SCHEDULES 1.0
sample_rates.metrics PRISM_SAMPLE_METRICS 1.0
sample_rates.exceptions 1.0 (fixed)

Batching and flush

Key Env Default Meaning
batch.size PRISM_BATCH_SIZE 1000 Max events one request may buffer; the excess is dropped and counted, bounding memory.
batch.flush PRISM_FLUSH_STRATEGY terminate terminate ships after the response is returned (off the critical path — the default). sync ships inline before the process ends (useful for one-off scripts and tests). spool parks the batch in the cache for one debounced drain job (see below).
batch.timeout PRISM_FLUSH_TIMEOUT 2.0 Ingest POST timeout in seconds, kept short so a slow endpoint never stalls the process.
batch.queue_threshold PRISM_QUEUE_THRESHOLD 100 A flush larger than this is handed to a queued job instead of sent inline. 0 keeps every flush inline.
batch.queue PRISM_FLUSH_QUEUE null Queue the send/drain job is dispatched onto; null = default queue.

The spool strategy

Under terminate and sync the process that captured the telemetry is also the one that ships it — a POST per request. spool decouples the two: a finished batch is written to the cache and a single debounced job drains everything spooled since the last drain, so a burst of requests costs one ingest POST instead of one each. It is the right choice when reaching the ingest host is expensive per request, and the necessary one when the host is the ingest host — an inline POST into a single-worker dev server deadlocks against the request making it.

Only finished work is ever spooled (a flush runs at the end of a request, job or scheduled task), and a cache lock guards the hand-off, so a drain can never pick up a half-written batch. A batch that fails to send goes back on the spool and a fresh drain is armed, so an outage costs a delay rather than the telemetry.

It needs two things: a cache store with atomic locks shared by every process that captures (redis, memcached, database, file — not array outside tests), and a queue worker (the drain never runs on the sync driver). Without either, the flush quietly falls back to terminate rather than piling up telemetry it cannot ship — php artisan prism:check reports which, and how many batches are waiting.

Key Env Default Meaning
batch.spool.delay PRISM_SPOOL_DELAY 5 Seconds the drain waits before running — the debounce window everything spooled during it ships in.
batch.spool.grace PRISM_SPOOL_GRACE 60 Extra seconds the "a drain is pending" marker is held, covering the wait for a free worker. Once it lapses the next flush arms a replacement.
batch.spool.ttl PRISM_SPOOL_TTL 900 Seconds a spooled batch survives in the cache.
batch.spool.max_batches PRISM_SPOOL_MAX_BATCHES 500 Batches held at once; past the cap the oldest are dropped. 0 = unbounded.
batch.spool.max_attempts PRISM_SPOOL_MAX_ATTEMPTS 3 Send attempts a batch gets before it is discarded, so a wrong endpoint cannot cycle forever.
batch.spool.store PRISM_SPOOL_STORE null Cache store backing the spool; null = default store.
batch.spool.lock_seconds PRISM_SPOOL_LOCK_SECONDS 5 How long the spool index lock is held.
batch.spool.lock_wait PRISM_SPOOL_LOCK_WAIT 3 How long a flush waits for that lock before giving up and sending inline.

Per-domain limits

Key Env Default Meaning
request.max_body PRISM_MAX_BODY_SIZE 65536 Byte cap on a captured request body (non-GET only, scrubbed then truncated). 0 = uncapped.
log.channels [] Logging channels to capture. Empty = the app's default channel/stack.
log.level PRISM_LOG_LEVEL debug Minimum PSR-3 level captured.
query.slow_threshold_ms PRISM_SLOW_QUERY_MS 100 A query at/above this is marked slow and always kept (it and its whole trace) regardless of sampling. 0 disables the marker.
schedule.max_output PRISM_MAX_OUTPUT_SIZE 16384 Byte cap on a scheduled task's captured output. 0 = uncapped.

Runtime metrics

Key Env Default Meaning
metrics.system_interval PRISM_METRICS_INTERVAL 60 Seconds between CPU/memory/uptime samples. Runs on every replica.
metrics.queue_interval PRISM_QUEUE_POLL_INTERVAL 30 Seconds between queue-depth/worker samples. Runs only on a process that works the queue.
metrics.replica_type PRISM_REPLICA_TYPE null web or worker. null infers it from the process.

Both metric samples piggyback onto the next flush, so a shorter interval never means a dedicated request.

Ignore lists

Activity the client never captures. Every list but ignore.exceptions matches with Str::is wildcards, so one pattern covers a family (api/internal/*, App\Jobs\Internal\*, prism:*); a pattern without a * matches exactly.

Key Default Meaning
ignore.paths Prism's own routes, telescope*, horizon*, _debugbar*, nova*, up, health* Request URI patterns never captured.
ignore.jobs [] Queued job class names, as resolved for display — for a queued broadcast that is the event class, not the framework's wrapper.
ignore.commands [] Scheduled task commands, matched on the task's description if it sets one, otherwise its command string.
ignore.http [] Outgoing HTTP destinations, matched against the host, the host and path, and the full URL without its query string — redis.internal, *.googleapis.com and http://ch:8123/* all work.
ignore.cache [] Cache keys, matched on the full key before it is truncated for display.
ignore.exceptions NotFoundHttpException, ValidationException Exception classes never reported. Matched with instanceof, so subclasses are covered too.

There are two reasons to add something here. The mild one is noise: a health check polled every second, or a cache key touched on every request, costs an event each time and tells you nothing.

The serious one is feedback. Capturing work that exists because of telemetry means the capture produces more work to capture. Prism excludes its own outbound batch automatically — it carries an internal marker header and runs under a suppression scope — and an inbound request carrying that same marker is treated the same way: capture is suppressed for its whole lifetime, so neither the request nor the queries, cache reads and log lines it triggers are recorded. An application that hosts the Prism workspace it reports to therefore never captures another client's ingest POST, on any endpoint, with no configuration. What it cannot know is the work your application does on Prism's behalf: the job that stores a batch, the counters it increments, the datastore it writes to. Those are what the lists above are for. Two cases worth checking in any install:

  • A datastore reached over HTTP rather than as a database connection (ClickHouse, OpenSearch, a cloud API) travels through Laravel's HTTP client, so every read and write becomes a span — and storing that span is another write. Add its host to ignore.http.
  • Cache is the chattiest signal. One operation touching half a dozen counters emits a span per counter, so a prefix used for internal bookkeeping is worth an ignore.cache entry.

Scrubbed keys

Keys whose values are redacted at the source before an event leaves the process — matched case-insensitively against request input, headers, job payloads and query bindings. The list is additive; extend it with any field specific to your app. Defaults:

password, password_confirmation, secret, token, authorization, cookie, api_key, access_token, refresh_token, credit_card, card_number, cvv, ssn.

Manual instrumentation

Automatic capture covers the common signals. Two static helpers on Misakstvanu\Prism\Prism let you record things the listeners cannot see. Both are safe no-ops when the client is inert (disabled or unconfigured), so you can call them unconditionally.

Report a handled exception — one your code caught but still wants in Prism (a swallowed integration error, a best-effort background failure). It is recorded as handled, scrubbed and buffered like any other:

use Misakstvanu\Prism\Prism;

try {
    $gateway->charge($order);
} catch (GatewayException $e) {
    report_to_ledger($order);
    Prism::captureException($e);
}

Instrument a block of code as a trace span — arbitrary work the automatic capturers do not see, such as an expensive computation or a third-party SDK call. The closure is timed and placed on the trace waterfall; spans opened inside it (cache lookups, outgoing HTTP, nested Prism::span() calls) nest beneath it automatically. The closure's return value is passed through:

$report = Prism::span('generate monthly report', function () use ($account) {
    return $this->reporting->build($account);
});

// Tint it as a database/http/… span with the optional type argument:
$rows = Prism::span('warehouse rollup', fn () => $warehouse->rollup(), type: 'db');

Octane

The package is Octane-aware. Per-request state (the event buffer, the trace context, the recursion guard and the span stack) is reset on each RequestReceived, so one request's telemetry never leaks into the next inside a long-lived worker. The terminate flush strategy fires under Octane's per-request kernel terminate, and the HTTP transport reuses a keep-alive connection pool across requests. No extra configuration is required.

Horizon

Queue-depth and worker-count metrics are read through Horizon automatically when it is installed — the package detects Laravel\Horizon\Contracts\SupervisorRepository at runtime, with no compile-time dependency, so nothing breaks when Horizon is absent. Job capture works the same way whether you run queue:work, queue:listen or Horizon.

For queue metrics to be reported, run prism:check (or any job) on a worker process — queue polling is skipped entirely on web replicas, and an idle worker with no jobs has no flush to piggyback on.

Troubleshooting

Run the built-in diagnostic first — it names the exact problem:

php artisan prism:check
Symptom Likely cause
prism:check reports a missing token or app PRISM_TOKEN / PRISM_APP not set, or config cached before they were — run php artisan config:clear.
A 401 from the endpoint The token is wrong, revoked, or not an ingest-scope token. Mint a fresh ingest token.
A 429 from the endpoint The workspace is over its monthly event quota. Connectivity and the token are fine — this is a billing state. Errors are still accepted.
Nothing appears in the console Confirm PRISM_ENABLED is not false, the path/job is not on an ignore list, and — for a worker — that a job has actually run (the batch flushes at the end of each job).
Nothing appears, and prism:check shows batches "waiting" Under spool, batches are landing but nothing drains them: check a worker is consuming the queue named by PRISM_FLUSH_QUEUE (default queue), and that the endpoint is reachable from the worker, which is where the send now happens.
Events flow but bodies/args are blank Expected: sensitive keys are scrubbed at the source, and non-GET bodies are truncated to request.max_body.
No telemetry after a deploy Config cache is stale — php artisan config:clear (or re-run config:cache).

PRISM_ENABLED=false and an over-quota 429 are both success exits for prism:check — a deliberate opt-out and an operational quota state are not wiring faults.

Tests

The package carries its own Pest suite, run through Testbench with no host application present — which is what proves it depends on nothing outside itself:

cd packages/prism
composer install
./vendor/bin/pest