SIGNALS Documentation
API Reference

Observability

What Signals ships for monitoring — Nightwatch as a tenant-configured APM, health probes, structured log channels, request correlation, slow-query logging, and failed-job alerting.

The shape of it

Signals treats observability as a tenant-configured integration, not a build-time decision. Nothing here requires a vendor account: every layer degrades to a durable local artefact — a log channel, a health card, an in-app notification — and an installation that does subscribe to an APM gets the richer view on top.

Layer Ships as Configured in
APM Laravel Nightwatch Admin → Integrations → Monitoring (nightwatch settings group)
Queue depth System Health card + GET /api/v1/system/health config/queue.php workers map
Health probes /health/live, /health/ready, GET /api/v1/system/health Nothing to configure
Health diagnostics Admin → System → System Health, php artisan doctor config/observability.php thresholds
Structured logs webhooks, imports, auth, api, usage channels config/logging.php
Request correlation X-Request-Id, end to end config/observability.php
Slow queries Log warning per query over threshold config/observability.php
Failed jobs queue.job_failed notification Admin → Notifications (channels + recipients)

Health endpoints

Two unauthenticated probes, registered outside the web and api middleware groups so they never touch sessions, CSRF, or API metering:

Endpoint Answers Cost
GET /health/live Is the process up? Touches no dependency at all
GET /health/ready Can it serve traffic? Database, Redis, and queue reachability; cached 5s, lock-guarded against probe stampedes, throttled 60/min

GET /api/v1/system/health is the authenticated equivalent (system.read permission, system:read ability), deriving its status from the same readiness subset.

Readiness deliberately stays cheap. The richer diagnostics — disk space, pending migrations, S3, scheduler heartbeat, PHP limits, per-queue depth, failed-job detail — live on the System Health page and in php artisan doctor, which are pulled rather than polled.

Structured log channels

Domain traffic is separated so it can be retained and monitored independently:

Channel Retention Carries
webhooks 7 days Outbound delivery attempts, refusals, permanent failures
imports 7 days Import plan start, per-step completion, pause reason, completion
auth 90 days Sign-in, sign-out, failed attempt, lockout, password reset, SSO callback
api 1 day Request/response detail when API metering capture is on
usage 14 days Throughput-bucket accounting

All are JSON-formatted. Two of them are security-sensitive by construction and are written accordingly: the auth channel never records a credential (only the account identifier from the attempt), and slow-query logging never records bindings. Both routinely carry passwords, tokens, and personal data, and a diagnostic log must not become a second copy of them.

Request correlation

Every request gets an X-Request-Id — echoed on the response, honoured if the caller supplied one — and it follows the work all the way through:

  1. App\Http\Middleware\LogContext mints it and shares request_id, user_id, and warehouse_id into the log context, so every channel inherits them.
  2. App\Support\Logging\QueueRequestIdPropagation stamps it into every queued job's payload and adopts it in the worker, chaining through nested dispatches.
  3. A global HTTP client middleware carries it onto outgoing calls, so a trace survives into whatever Signals talks to. A caller that sets its own header keeps it.

Turn step 3 off with OBSERVABILITY_PROPAGATE_REQUEST_ID=false if an upstream rejects unexpected headers.

Slow queries

Any single query slower than the threshold is logged at warning with its SQL template, duration, and the ambient request context:

OBSERVABILITY_SLOW_QUERY_LOGGING=true
OBSERVABILITY_SLOW_QUERY_THRESHOLD_MS=500
OBSERVABILITY_SLOW_QUERY_LOG_CHANNEL=stack

Nightwatch detects the same queries and additionally flags N+1 patterns; this is the floor underneath it for installations without a subscription.

Failed-job alerting

A job that exhausts its retries now tells someone rather than only landing in failed_jobs:

  • Always — an error line on the configured channel with job, queue, connection, exception class, and message.
  • Then — the queue.job_failed notification, whose channels and recipients are configured per installation in Admin → Notifications. Default recipients are the active owners.

Identical failures are deduplicated by a fingerprint of job name, exception class, and message, so one broken job cannot flood an inbox:

OBSERVABILITY_FAILED_JOB_ALERTS=true
OBSERVABILITY_FAILED_JOB_ALERT_COOLDOWN=60
OBSERVABILITY_FAILED_JOB_LOG_CHANNEL=stack

The log line is never suppressed — only the notification is.

Error tracking

Unhandled exceptions are reported with request_id, user_id, and warehouse_id attached, even when they escape outside the middleware stack. Reports go to the configured log channels and to Nightwatch when it is enabled. Signals does not bundle a second error-tracking vendor; adding one is an application-level dependency choice.

Multi-tenancy

Cloud tenancy plumbing lives in core behind SIGNALS_CLOUD / config('signals.cloud') (false by default). There are still no tenant_id columns in log context. When the gate is on, VerifyProxySecret then ResolveTenant bind a request-scoped TenantContext from the edge-router headers; self-hosted installs skip that path.