Agora

Diagnostics

A zero-dependency diagnostics bus over node:diagnostics_channel — emit once, observe anywhere, with an OpenTelemetry auto-bridge.

@adonis-agora/diagnostics is a tiny, standard convention for @adonis-agora/* AdonisJS libraries to emit observability events over Node's built-in diagnostics_channel. A library calls emit('billing', 'invoice-paid', payload) — or wraps an operation in trace('authz', 'decision', fn) — and is done. Whoever wants to observe it subscribes. It costs essentially nothing when nobody is listening, and never throws.

The point: producers stay decoupled from consumers

The key idea is that the channel is neutral. A library that emits a diagnostic event doesn't know — and doesn't care — who reads it. Telescope is one consumer (the batteries-included one), but the same event can just as easily become an OpenTelemetry span, feed an APM tool, drive a custom logger, fan out to other processes over a transport, or back an assertion in a test. See Consumers.

This is why the bus is a separate, dependency-free package: if emitting required Telescope (or OTel, or Redis), every producing library would be coupled to it. Instead, producers depend only on this thin layer, and observability is opt-in on the consumer side.

The model

Two emit surfaces, both over node:diagnostics_channel, both free when unobserved:

  • POINT eventsemit(lib, event, payload) publishes one DiagnosticEvent on agora:<lib>:<event>. Use it for "something happened" facts.
  • SPANStrace(lib, event, fn) runs fn and publishes start / end / error (plus async phases) on agora:<lib>:<event>:<phase>, with timing and a shared spanId. Use it for "this operation took N ms and succeeded/failed".

Every channel name shares the prefix agora and carries a standard envelope:

interface DiagnosticEvent<TPayload = unknown> {
  v?: number;        // SCHEMA_VERSION (1) — observers treat absent as v1
  ts: number;        // Date.now() at publish time
  lib: string;       // the <lib>, e.g. "billing"
  event: string;     // the <event>, e.g. "invoice-paid"
  traceId?: string;  // auto-filled from @adonis-agora/context when present
  payload: TPayload; // your library-defined data
  durationMs?: number; // wall-clock duration of the operation, when known
}

Why diagnostics_channel?

It's a Node built-in, so the bus has zero runtime dependencies. Channels are process-global and resolved by name, so a producer and an observer in different packages — that never import each other — meet on the same channel. And emit gates on channel.hasSubscribers: with no observer attached, the envelope is never even built, so libraries can emit by default and pay nothing in production.

Quickstart

Install and configure:

terminal
node ace add @adonis-agora/diagnostics

Emit an event from anywhere — it's published only when something is subscribed:

import { emit } from '@adonis-agora/diagnostics'

emit('billing', 'invoice-paid', { invoiceId: 'inv_123', amount: 4200 })

Observe it from any consumer — the simplest being onDiagnostic, registered in start/diagnostics.ts. See Getting Started for the full flow and trace correlation.

Explore

One package, @adonis-agora/diagnostics. Cross-process fan-out is a config choice, not an extra package — see Transports.

Where to go next

On this page