Periscope is a Laravel Telescope-style runtime recorder and local dashboard for AdonisJS v7. It correlates the work an application actually performs—HTTP requests, database queries, exceptions, logs, events, commands, mail, cache operations, model changes, authorization checks, validation failures, rate limits, distributed locks, Drive operations, Ally OAuth flows, translations, notifications, socket traffic, dumps, outbound HTTP calls, Edge view renders, health check reports, queue jobs and scheduled tasks, Redis commands, sessions, and Transmit broadcasts—without sending telemetry to an external service.
Periscope is a development and staging diagnostic tool, not an APM or a production tracing backend. Recorded values remain in the configured local store.
Explore the documentation
- Dashboard and security: navigation, live updates, and safe access
- Watchers and troubleshooting: recorded signals, capture behavior, and common failures
- Operations: shared stores, retention, and production posture
- Testing: recorder assertions and the Japa plugin
- Configuration: every option, default, and merge rule
- CLI commands: diagnosis, maintenance, pause, export, and import
- Architecture: runtime pipeline, lifecycle, storage, and invariants
- HTTP API: experimental dashboard JSON and SSE routes
- Adapter authoring: watcher, integration, store, and fanout seams
- Batch export: versioned
periscope.batchinterchange schema - Upgrading: release-specific migration notes
Requirements
- Node.js 24 or newer
- AdonisJS 7
- npm 11 or newer when contributing to this repository
Optional integrations activate only when their host packages are installed: Lucid, Mail, Cache,
Bouncer, Edge, @adonisjs/transmit, @adonisjs/redis, @adonisjs/session, BullMQ, and the
experimental @adonisjs/queue.
Two-minute quickstart
Install and configure the package:
npm install @rikology/adonisjs-periscope
node ace add @rikology/adonisjs-periscopeThe configure hook creates config/periscope.ts, registers the provider early in adonisrc.ts,
adds the request watcher as the first server middleware, and installs the exception reporter mixin.
If you select the shared database driver, also run:
node ace migration:runStart the application and open /periscope:
node ace serve --hmrGenerate some application traffic. New entries appear live and related work shares one batch. With
the default sqlite-local driver, data is written to tmp/periscope.sqlite.
Run the installation doctor after configuration and after wiring changes:
node ace periscope:doctorVerify the generated wiring
The request middleware must remain first so it can establish the correlation scope around all downstream work:
// start/kernel.ts
server.use([
() => import('@rikology/adonisjs-periscope/middleware/request_watcher'),
// other server middleware
])The exception reporter preserves the application’s existing handler and records during report():
// app/exceptions/handler.ts
import { ExceptionHandler } from '@adonisjs/core/http'
import { withPeriscope } from '@rikology/adonisjs-periscope/exception_reporter'
class HttpExceptionHandler extends ExceptionHandler {}
export default withPeriscope(HttpExceptionHandler)Query recording requires Lucid query events. Enable debug: true on the Lucid connection you want
to observe.
Configuration
config/periscope.ts exports defineConfig(...). The generated stub starts with safe local defaults;
the configuration reference documents every option,
default, and merge rule:
import { defineConfig } from '@rikology/adonisjs-periscope/periscope_config'
export default defineConfig({
applicationName: 'billing-api',
enabledIn: ['development', 'test'],
storage: {
driver: 'sqlite-local',
maxEntries: 10_000,
},
recording: {
caps: { default: 100, query: 200 },
sampleRate: 1,
},
dashboard: {
path: '/periscope',
},
})PERISCOPE_ENABLED=true explicitly enables recording outside enabledIn;
PERISCOPE_ENABLED=false disables it everywhere. When disabled, the provider installs no watcher,
logger, process, model, or dashboard hooks.
Storage drivers
| Driver | Use | Notes |
|---|---|---|
sqlite-local |
Default local development | Dedicated SQLite database, defaulting to tmp/periscope.sqlite; no Lucid dependency |
database |
Shared or remote inspection | Uses a Lucid connection and the package migration; configure storage.connection when needed |
memory |
Tests and short-lived processes | Bounded process-local ring buffer; all entries disappear at process exit |
All drivers enforce storage.maxEntries. Configure storage.retention for automatic age-based
pruning: when set, the provider prunes entries older than hours shortly after boot and every 15
minutes thereafter (optionally sparing exceptions with keepExceptions). sqlite-local uses WAL
mode, indexed chunked operations, and a trigram FTS5 index for text search with a transparent
LIKE fallback; the database driver keeps the same storage contract across supported Lucid
databases.
Every stored entry carries applicationName. A shared database can therefore serve several
applications without mixing counts, indexes, exception groups, or scoped clears. The dashboard
application selector persists its choice in the URL.