@docx-editor.dev/telemetry/server
Room telemetry for any Yjs collaboration server: rooms, connections, the people in each room, the memory each room holds, and why connections and messages were refused.
No metric carries a room name. Per-room detail is in [RoomTelemetry.list](RoomTelemetry.list), for an admin endpoint you protect.
Functions (2)
hmacRoomLabelfunctionSource ↗
A stable room label that does not reveal the name: an HMAC of the name with your secret. The same name gives the same label across processes and restarts.
declare function hmacRoomLabel(secret: string | undefined): (name: string) => string;```ts
const rooms = roomTelemetry({
exporters: [metrics],
roomLabel: hmacRoomLabel(process.env.ROOM_LABEL_SECRET),
});
```roomTelemetryfunctionSource ↗
Track rooms on a Yjs server.
declare function roomTelemetry(options: RoomTelemetryOptions): RoomTelemetry;```ts
import { roomTelemetry } from '@docx-editor.dev/telemetry/server';
import { prometheusRegistry, metricsHandler } from '@docx-editor.dev/telemetry/prometheus';
const metrics = prometheusRegistry();
const rooms = roomTelemetry({ exporters: [metrics] });
const room = rooms.open(ydoc, { name, awareness });
await room.time('load', () => loadRoom(ydoc)); // the loaded state is not counted as edits
room.connected();
```Interfaces (7)
AwarenessLikeinterfaceSource ↗
The part of a y-protocols Awareness the telemetry reads.
interface AwarenessLike| Member | Type | Summary |
|---|---|---|
| getStates | | |
| off | | |
| on | |
RoomHandleinterfaceSource ↗
One open room. Returned by [RoomTelemetry.open](RoomTelemetry.open).
interface RoomHandle| Member | Type | Summary |
|---|---|---|
| begin | | Start timing `operation` where `time` cannot wrap it; call the result once it settles. |
| close | | Stop tracking the room, for example when the server unloads it. |
| connected | | |
| disconnected | | |
| label | string | The room's label, as [RoomTelemetryOptions.roomLabel](RoomTelemetryOptions.roomLabel) made it. |
| time | | Run `operation` and record its duration and outcome. Rethrows its error. |
RoomStatsinterfaceSource ↗
One room in [RoomTelemetry.list](RoomTelemetry.list).
interface RoomStats| Member | Type | Summary |
|---|---|---|
| ageSeconds | number | |
| blobBytes | number | null | |
| connections | number | |
| idleSeconds | number | |
| limitRatio | number | null | The largest share of any room limit in use, from 0 to 1. |
| nodes | number | null | |
| participants | number | People present, from awareness states. A person with two tabs counts twice. |
| pendingBytes | number | Bytes of updates applied since the room was last stored. |
| room | string | |
| stateBytes | number | null | Encoded Yjs state at the last sample, or `null` before the first one. |
RoomTelemetryinterfaceSource ↗
Room telemetry for one server process.
interface RoomTelemetry| Member | Type | Summary |
|---|---|---|
| begin | | Start timing an operation that has no room yet; call the result once it settles. |
| flush | | |
| list | | Every open room, for an admin endpoint you protect. |
| migrated | | A room migration finished. |
| open | | Start tracking a loaded room. Pass the room's awareness to count people; without it the number of connections stands in. |
| refused | | A message refused before it applied, such as `room-generation-changed`. |
| rejected | | A connection refused at admission. `reason` can be a string or an error with `reason`. |
| sample | | Sample people, memory, and size now. |
| shutdown | | Stop the timer, close every room, and shut the exporters down. |
| time | | Time an operation that happens before the room is open, such as a load that fails. |
RoomTelemetryOptionsinterfaceSource ↗
Options for [roomTelemetry](roomTelemetry).
interface RoomTelemetryOptions extends TelemetryPipelineOptions| Member | Type | Summary |
|---|---|---|
| measureState? | boolean | Measure each room's encoded Yjs state. This encodes the room once per sample, and only when it changed since the last one. Defaults to `true`. |
| perRoomSeries? | boolean | Also record each room's people and memory as gauges labeled `room`. Off by default: a label per room makes one series per room, and a large server has many rooms. A closed room's series is dropped. Only the default label or `hmacRoomLabel` is accepted, so no room name can become a label; any other `roomLabel` throws. |
| reasonOf? | (error: unknown) => string | undefined | Your own reading of a refusal, for errors whose `reason` or `code` is free text. Return a common reason such as `unauthorized`, a collaboration failure code, or one of `reasons`; anything else is counted as `other`. Return undefined to use the default reading. |
| reasons? | readonly string[] | Refusal reasons to keep as metric labels. Compared without case and recorded in lowercase. Collaboration failure codes are always kept. Any other reason is counted as `other`, so a free-text error never becomes a time series. |
| roomLabel? | (name: string) => string | How a room appears in [RoomTelemetry.list](RoomTelemetry.list). Defaults to an index that is valid only inside this process, such as `room-3`. Use `(name) => name` to see real names, or [hmacRoomLabel](hmacRoomLabel) for a stable label that does not reveal the name. |
| sampleInterval? | number | false | How often to sample people, memory, and room size, in milliseconds. Defaults to 60 000. `false` turns the timer off; call `sample()` yourself, for example before each scrape. |
TelemetryExporterinterfaceSource ↗
Where telemetry goes. Implement event, metric, or both.
An exporter never throws into the editor or the server: a failure is caught and reported once through onExporterError.
interface TelemetryExporter```ts
const toMyLogger: TelemetryExporter = {
name: 'my-logger',
event: (event) => myLogger.warn(event.kind, { code: 'code' in event ? event.code : undefined }),
};
```| Member | Type | Summary |
|---|---|---|
| attach | | Called once when a telemetry client or room server takes the exporter. An exporter that holds events, such as a batching one, reads `consented()` before it sends, so events queued before consent was withdrawn are discarded, not sent. |
| declare | | Start a series at zero before its first measurement. A scraper that sees a counter first at 1 cannot count that first increase, so the room server declares its known series when it starts. Optional: an exporter without it creates each series at its first measurement. |
| event | | |
| flush | | Send anything buffered. Called by `flush()` and when a page is hidden. |
| forget | | Drop a series that no longer exists, such as a closed room's per-room gauge. Optional: an exporter without it keeps the series until it expires on its own. |
| metric | | |
| name | string | Shown in `onExporterError` reports. |
| shutdown | | Flush and release resources. Called by `shutdown()`. |
TelemetryPipelineOptionsinterfaceSource ↗
Options shared by the editor client and the room server.
interface TelemetryPipelineOptions| Member | Type | Summary |
|---|---|---|
| consent? | boolean | (() => boolean) | Whether the viewer agreed to telemetry. Read before each event and metric, so a consent banner can change it at any time. Events before consent are dropped, not buffered. Defaults to `true`: without this option, events are sent. Pass it whenever your app asks for consent. |
| exporters | readonly TelemetryExporter[] | Where telemetry goes. Nothing is recorded or sent without at least one exporter. |
| metricLabels? | readonly string[] | Keys of your own attributes that may also label metrics, such as `tenant.tier` or `app.region`. Only for values with few distinct values: each value makes a new series. After [MAX_METRIC_LABEL_VALUES](MAX_METRIC_LABEL_VALUES) distinct values of one key, later values become `other`. Never list a user, document, or room id here; put those on events instead. |
| onExporterError? | (error: unknown, exporter: string) => void | Told once per exporter when it throws. Defaults to one `console.warn`. |
| resource? | Attributes | Values added to every event, such as `{ 'service.name': 'contracts-app' }`. Metrics do not carry them: set resource attributes on your OpenTelemetry SDK or Prometheus target instead. You own these values: do not put a user, a document, or a room name here. |
Type aliases (2)
RoomOperationtypeSource ↗
What a timed room operation is.
type RoomOperation = 'load' | 'store';SettleOperationtypeSource ↗
Records a started operation's duration with its outcome. Later calls do nothing.
type SettleOperation = (outcome: 'success' | 'failure') => void;