Aviary
Packages

@dudousxd/nestjs-telescope (core)

The core module — request + exception watchers, the recorder, ALS correlation, the zero-config SQLite store, the headless API, the gate, and the pruner.

pnpm add @dudousxd/nestjs-telescope
npm install @dudousxd/nestjs-telescope

Core is everything you need to start capturing: it wires request and exception watchers automatically, turns on a zero-config SQLite store and pruner, and exposes the headless API. Import the module and you're recording — no adapter required.

import { TelescopeModule } from '@dudousxd/nestjs-telescope';

@Module({
  imports: [
    TelescopeModule.forRoot({
      enabled: process.env.NODE_ENV !== 'production',
      authorizer: () => true, // gates the API; defaults to deny in production
      prune: { after: '24h' },
    }),
  ],
})
export class AppModule {}

Hit GET /telescope/api/entries (or /telescope/api/entries/:id for a request and everything it caused).

Built-in watchers

ExportCaptures
request watcherEvery HTTP request (wired automatically) — opens the batch.
exception watcherEvery thrown exception (wired automatically).
HttpClientWatcherOutbound fetch calls, correlated to the request/job. Optionally also axios / @nestjs/axios. Sanitizes credentials and secret query params; no peer dependency.
import { TelescopeModule, HttpClientWatcher } from '@dudousxd/nestjs-telescope';

TelescopeModule.forRoot({ watchers: [new HttpClientWatcher({ slowMs: 1000 })] });

HttpClientWatcher + axios / @nestjs/axios

fetch is patched out of the box, but NestJS apps mostly call out through @nestjs/axios's HttpService — and in Node, axios uses the http adapter, not fetch, so those calls are invisible to the fetch patch. Pass an axios source to capture them too, via axios's public interceptor API (no monkey-patching). Both paths share the same content shape, tags, and family-hash, so axios and fetch entries are indistinguishable in the dashboard.

@nestjs/axios — resolve HttpService lazily. HttpService isn't constructed until the container is up, so use the custom-source instrument hook to grab axiosRef at register time from ctx.moduleRef:

import { TelescopeModule, HttpClientWatcher } from '@dudousxd/nestjs-telescope';
import { HttpService } from '@nestjs/axios';

TelescopeModule.forRoot({
  watchers: [
    new HttpClientWatcher({
      slowMs: 1000,
      axios: {
        instrument(attach, ctx) {
          const http = ctx.moduleRef.get(HttpService, { strict: false });
          attach(http.axiosRef); // wire interceptors onto the real instance
        },
      },
    }),
  ],
});

Plain axios instance — hand it over directly:

import axios from 'axios';
import { TelescopeModule, HttpClientWatcher } from '@dudousxd/nestjs-telescope';

const client = axios.create({ baseURL: 'https://api.example.com' });

TelescopeModule.forRoot({ watchers: [new HttpClientWatcher({ axios: client })] });

On a non-2xx response the entry records error.response.status; on a transport failure (no response) it records statusCode: null (tagged failed). Either way the rejection is re-thrown, so your error handling is untouched. Instrumentation is idempotent per axios instance, so sharing one instance across watchers never double-records.

Double-capture edge case: if you explicitly configure axios with the fetch adapter (Node's default is the http adapter), a single call could record on both the axios and fetch paths. Keep the http adapter, or pass only one source.

Headless API

Method & pathReturns
GET /telescope/api/entriesA page of entries (filter by type, tag, batch, family, time).
GET /telescope/api/entries/:idOne entry plus its correlated batch.
GET /telescope/api/pulse?window=1hPer-type counts, slowest entries, top exceptions, N+1 occurrences.
GET /telescope/api/queues?window=1hPer-queue throughput, runtime/wait-time percentiles, failure rate.
GET /telescope/api/timeseries?window=1h&buckets=60Bucketed throughput — the sparkline data.
GET /telescope/api/healthTelescope's own self-overhead (capture cost, buffer, flush, drops).

Key options

OptionDescription
enabledMaster switch. Compute inline (e.g. NODE_ENV !== 'production').
storageStorageProvider (or factory). Defaults to the embedded SQLite store.
watchersBuilt-in + custom watchers; each individually configurable.
authorizerGates the API. Denies in production by default until you supply one.
authorizeActionSeparate, default-deny gate for queue mutations.
dashboardAuthSigned-cookie login for the dashboard — see Dashboard auth.
redactDeep-redact configured headers / query / body paths (default sensitive keys always run).
pruneRetention window driving the scheduled pruner (e.g. { after: '24h' }). Supports perType for per-type cutoffs (e.g. { after: '5m', perType: { exception: '7d' } }).
archiveExport a type's entries to a sink before the pruner deletes them — see Archiving before prune.
pulseHealth-snapshot tuning. slowRouteMs (default 1000) is the p99 a route must reach to count as a slow-request hotspot — matches the slow tag threshold, so a quiet host shows no false hotspots.
traceContext / traceLinkOpenTelemetry trace stamping + deep-link template — see -otel.
queueManagersLive queue managers — see queue managers.
mcpOptional MCP server for coding agents — true (dev-only) or { token }. Disabled by default. See MCP server.
overloadProtectionPause capture under event-loop pressure. true (default, 200ms p99), false, or { maxEventLoopLagMs }. See Performance.

See Capture & correlation for the recorder pipeline and the Entry / Watcher / StorageProvider SPIs.

On this page