Aviary

Recent events

Keep the last diagnostics events of a process in memory and read them back, on by default outside production.

DiagnosticsModule keeps the last diagnostics events of the process in a fixed-size ring: every aviary:* event, the trace() span phases, and the first-party nestjs:* events. Read them from code, from a test, or from the diagnostics_list_recent_events dev tool.

Turning it on and off

DiagnosticsModule.forRoot({
  recent: { size: 1000 }, // default: on unless NODE_ENV is 'production', 500 events
});
recentBuffer
omittedon unless NODE_ENV is 'production', 500 events
trueon everywhere, 500 events
{ size }on everywhere, size events
falseoff

The buffer subscribes to every channel, so emit() builds its envelope even when nothing else listens. That is why it is off in production unless you turn it on.

Reading it

Inject DiagnosticsRecentEvents:

import { Injectable } from '@nestjs/common';
import { DiagnosticsRecentEvents } from '@dudousxd/nestjs-diagnostics/nestjs';

@Injectable()
export class Debugging {
  constructor(private readonly recent: DiagnosticsRecentEvents) {}

  failedRuns() {
    return this.recent.list({ lib: 'durable', event: /run\.(failed|cancelled)/, limit: 50 });
  }
}

Or call recentEvents() anywhere, with or without Nest:

import { recentEvents } from '@dudousxd/nestjs-diagnostics';

recentEvents({ traceId: 'req-42' });

Both return the newest events first. recentEvents() returns [] when the buffer is off; DiagnosticsRecentEvents.enabled tells you whether it runs.

FilterMatches
libthe emitting library, e.g. 'durable' or 'resilience'
eventan exact event name, a RegExp, or its string form '/run\\./'
traceIdevents of one trace
limithow many to return (default 20)
offsethow many of the newest matches to skip, for paging

What an entry holds

{
  id: '0b7c…',            // the envelope id
  ts: 1731400000000,
  lib: 'durable',
  event: 'run.failed',
  traceId: 'req-42',
  durationMs: 12,          // when the event has one
  source: 'nestjs',        // only on first-party nestjs:* events
  payload: { runId: 'r1', token: '[redacted]' },
  phase: 'end',            // trace() spans: start, end, asyncEnd or error
  spanId: '…',             // shared by every phase of one span
  result: …,               // a span's result
  error: { name: 'TypeError', message: '…' },
}

Payloads are sanitized before they are stored:

  • keys that look secret (password, token, secret, authorization, cookie, apiKey, credential, privateKey) become '[redacted]', at any depth;
  • a payload over 2 KB of JSON becomes { truncated: true, bytes, preview };
  • dates, maps, sets, bigints, errors and cycles are turned into JSON-safe values.

An async trace() span records three entries with the same spanId: start, then end when the function returns its promise (no result yet), then asyncEnd with the result (or error) when it settles. The span is over at asyncEnd.

Without the module

import { startRecentEvents, stopRecentEvents } from '@dudousxd/nestjs-diagnostics';

startRecentEvents(200);
// …
stopRecentEvents();

The buffer is process-wide: every copy of the package loaded in the process reads the same one.

On this page