Looking to hire Laravel developers? Try LaraJobs

laravel maintained by daemon8

Description
Laravel integration for Daemon8 -- automatic runtime observation via event watchers
Last update
2026/04/21 22:03 (dev-main)
License
Downloads
2

Comments
comments powered by Disqus

Active development. This package is in public alpha. Tracked work for this SDK lives as GitHub Issues; the broader roadmap is maintained on the primary daemon8 repo.

Daemon8 for Laravel

Laravel 11 / 12 / 13. PHP 8.4+. Zero performance cost when the daemon is offline. Octane and long-running workers are under active hardening — see the roadmap on the primary repo.

Getting Started

Install the package:

composer require daemon8/laravel
php artisan vendor:publish --tag=daemon8-config

Start the local daemon (if you haven't already):

daemon8 install   # one-time setup + start as a system service

Every HTTP request, slow query, exception, and queued job now streams into Daemon8 automatically. Emit an observation manually from anywhere:

use Daemon8\Laravel\Facades\Daemon8;

Daemon8::log('checkout completed', severity: 'info');
Daemon8::warn('payment retry threshold reached');
Daemon8::exception($e);

Query from your terminal or agent:

daemon8 tail --kinds log,exception --severity warn

Or from code, against the local daemon's HTTP API:

curl 'http://127.0.0.1:9077/api/observe?kinds=log,exception&severity_min=warn&limit=20'

Manual observations

Inject Daemon8Client for testable classes (recommended):

use Daemon8\Daemon8Client;

final class CheckoutService
{
    public function __construct(private readonly Daemon8Client $daemon8) {}

    public function process(Order $order): void
    {
        $this->daemon8->log('processing order #' . $order->id);

        if ($order->needsManualReview()) {
            $this->daemon8->send(
                ['order_id' => $order->id, 'reason' => $order->reviewReason()],
                severity: 'warn',
                channel: 'orders.review',
            );
        }
    }
}

Or reach for the facade when a one-off emission is simplest:

use Daemon8\Laravel\Facades\Daemon8;

Daemon8::send(['event' => 'deploy', 'version' => '2.4.1'], severity: 'info', channel: 'deploys');

Sensitive data

Default redaction covers passwords, tokens, API keys, authorization headers, JWTs, Stripe keys, AWS access keys, GitHub PATs, and common credential shapes. Three lanes configured in config/daemon8.php:

'sensitive' => [
    'fields'   => ['password', 'api_key', 'custom_secret'],
    'headers'  => ['authorization', 'x-api-key'],
    'patterns' => ['/sk_(?:live|test)_[A-Za-z0-9]{24,}/'],
],

Plus two freebies: #[\SensitiveParameter]-marked arguments are redacted in captured backtraces, and Eloquent's $hidden is honored when models are serialized.

Wrap sensitive values at their origin when key-name matching isn't enough:

use function Daemon8\sensitive;

Daemon8::send(['token' => sensitive($token)]);

Respondents — the reactive runtime

Watchers push observations into Daemon8. Respondents react to them. A respondent is a first-class Laravel class with full container access: Eloquent, queues, cache, mail — everything you'd have in a job or controller.

use Daemon8\Contracts\Respondent;
use Daemon8\Filter;
use Daemon8\Observation;
use Daemon8\Severity;

final class SlowQueryAnalyst implements Respondent
{
    public function interest(): Filter
    {
        return new Filter(kinds: ['query'], severityMin: Severity::Warn);
    }

    public function respond(Observation $observation): void
    {
        $sql      = (string) ($observation->data['sql'] ?? '');
        $duration = (float)  ($observation->data['duration_ms'] ?? 0);

        SlowQuery::create([
            'sql'         => $sql,
            'duration_ms' => $duration,
            'captured_at' => now(),
        ]);
    }
}

Register in config/daemon8.php:

'respondents' => [
    App\Daemon8\Respondents\SlowQueryAnalyst::class,
],

Run the subscriber process:

php artisan daemon8:run

This is a long-running command — drop it into a Horizon process, a Supervisor config, or systemd. Respondents receive observations via SSE with exponential-backoff reconnect and Last-Event-ID resume.

Configuration

# Master switch
DAEMON8_ENABLED=true

# Transport
DAEMON8_URL=http://127.0.0.1:9077/ingest
DAEMON8_TRANSPORT=            # 'sync' | 'queue' (default dispatches by scheme)
DAEMON8_TIMEOUT_MS=50
DAEMON8_BATCH_SIZE=100

# App identity (defaults to config/app.php name)
DAEMON8_APP=checkout-api

# Debug — surfaces transport errors to STDERR
DAEMON8_DEBUG=false

See config/daemon8.php for per-watcher options (slow-query threshold, request body size limit, exception argument capture, etc.).

Watcher inventory

Each watcher is individually toggled via environment variable or config/daemon8.php. Example toggles:

DAEMON8_WATCH_REQUEST=true
DAEMON8_WATCH_QUERY=true
DAEMON8_WATCH_JOBS=true
DAEMON8_WATCH_CACHE=false        # opt-in watchers default to false
DAEMON8_WATCH_MODEL=false
DAEMON8_WATCH_MAIL=false
DAEMON8_WATCH_REDIS=false

The full list of captured events and what gets sent for each:

Watcher Event trigger Fields sent on each observation
RequestWatcher Every HTTP request/response cycle method, uri, status, duration_ms, input (redacted), route, controller
QueryWatcher Queries exceeding the slow-query threshold (100ms by default) sql, bindings (interpolated + redacted), duration_ms, connection
HttpClientWatcher Outbound Http:: client requests method, url, status, duration_ms, request_headers (redacted), response_headers (redacted)
ExceptionWatcher Every reported \Throwable class, message, file, line, trace (structured, with arguments redacted)
LogWatcher Log:: entries at warning level or higher level, message, context (redacted), channel
JobWatcher Queue jobs — dispatched, processing, processed, failed job, queue, connection, attempts, duration_ms, exception (on failure)
AuthWatcher Login, logout, failed authentication guard, user_id, event
NotificationWatcher Notifications sent / failed notifiable_type, notifiable_id, channel, notification, status
ScheduleWatcher Scheduled task lifecycle command, expression, duration_ms, output (truncated)
MigrationWatcher php artisan migrate up / down migration, direction, batch, duration_ms
TransactionWatcher DB transaction begin / commit / rollback connection, event, level
CacheWatcher (opt-in) Cache hit, miss, write, forget store, key, event, ttl (on write)
ModelWatcher (opt-in) Eloquent model events model, event (created/updated/deleted/...), attributes (redacted)
MailWatcher (opt-in) Outbound mail mailer, from, to, subject, queued
RedisWatcher (opt-in) Redis command execution connection, command, parameters (truncated)

Octane and queue workers

The package binds its observation buffer via scoped() so state resets per request under Octane, and no static state survives across workers. Full Octane test coverage (worker lifecycle, concurrent requests, memory growth over time) is tracked on the roadmap — the implementation is Octane-aware but formal validation across Octane server versions is not yet complete.

Set DAEMON8_TRANSPORT=queue to push observations through Laravel's queue. A SendObservationsJob runs on the default queue and flushes via the configured HTTP transport. Useful when request paths must never block on the daemon being reachable.

Development

composer install
composer check      # phpstan + rector dry-run + phpcs + phpunit
composer test

Requirements

  • PHP 8.4+
  • Laravel 11 / 12 / 13
  • daemon8/php (pulled in automatically)
  • Daemon8 daemon running locally

License

MIT. See LICENSE.