@docx-editor.dev/telemetry 0.0.0-stage → 2.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +121 -0
- package/README.md +94 -2
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/dist/chunk-73YEPWY5.js +1 -0
- package/dist/chunk-7ZXJ6HP2.cjs +1 -0
- package/dist/chunk-DT2KMPUJ.js +1 -0
- package/dist/chunk-DTEVX2XS.js +1 -0
- package/dist/chunk-LFQSY2TD.cjs +1 -0
- package/dist/chunk-LFX65QJG.js +4 -0
- package/dist/chunk-LGNT4I5W.cjs +1 -0
- package/dist/chunk-MJX3ECH3.cjs +4 -0
- package/dist/chunk-NUN46EXH.js +2 -0
- package/dist/chunk-RRYW7EAV.js +1 -0
- package/dist/chunk-S7KBMROA.js +1 -0
- package/dist/chunk-U6ERFNUM.cjs +1 -0
- package/dist/chunk-UK2MNJCG.cjs +2 -0
- package/dist/chunk-WGWKNKUT.cjs +1 -0
- package/dist/hocuspocus/index.cjs +1 -0
- package/dist/hocuspocus/index.d.cts +118 -0
- package/dist/hocuspocus/index.d.ts +118 -0
- package/dist/hocuspocus/index.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +332 -0
- package/dist/index.d.ts +332 -0
- package/dist/index.js +1 -0
- package/dist/ingest/index.cjs +1 -0
- package/dist/ingest/index.d.cts +107 -0
- package/dist/ingest/index.d.ts +107 -0
- package/dist/ingest/index.js +1 -0
- package/dist/otel/index.cjs +1 -0
- package/dist/otel/index.d.cts +57 -0
- package/dist/otel/index.d.ts +57 -0
- package/dist/otel/index.js +1 -0
- package/dist/pipeline-DbVW7S-K.d.cts +223 -0
- package/dist/pipeline-DbVW7S-K.d.ts +223 -0
- package/dist/prometheus/index.cjs +1 -0
- package/dist/prometheus/index.d.cts +88 -0
- package/dist/prometheus/index.d.ts +88 -0
- package/dist/prometheus/index.js +1 -0
- package/dist/sentry/index.cjs +1 -0
- package/dist/sentry/index.d.cts +134 -0
- package/dist/sentry/index.d.ts +134 -0
- package/dist/sentry/index.js +1 -0
- package/dist/server/index.cjs +1 -0
- package/dist/server/index.d.cts +156 -0
- package/dist/server/index.d.ts +156 -0
- package/dist/server/index.js +1 -0
- package/package.json +114 -4
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry: metrics and traces through the OpenTelemetry SDK you already run. This entry
|
|
3
|
+
* depends only on `@opentelemetry/api`; your SDK decides where data goes (OTLP to a
|
|
4
|
+
* collector, Datadog, Grafana, Honeycomb, New Relic, and others).
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import { MeterProvider, TracerProvider, Context } from '@opentelemetry/api';
|
|
9
|
+
import { b as TelemetryExporter } from '../pipeline-DbVW7S-K.js';
|
|
10
|
+
import '@docx-editor.dev/core';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* OpenTelemetry: metrics and traces through the OpenTelemetry SDK you already run. This entry
|
|
14
|
+
* depends only on `@opentelemetry/api`; your SDK decides where data goes (OTLP to a
|
|
15
|
+
* collector, Datadog, Grafana, Honeycomb, New Relic, and others).
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Options for {@link otelExporter}. @public */
|
|
21
|
+
interface OtelExporterOptions {
|
|
22
|
+
/** Defaults to the global provider your SDK registered. */
|
|
23
|
+
readonly meterProvider?: MeterProvider;
|
|
24
|
+
/** Defaults to the global provider your SDK registered. */
|
|
25
|
+
readonly tracerProvider?: TracerProvider;
|
|
26
|
+
/**
|
|
27
|
+
* The parent of each `docx_editor.document.open` span. Browsers have no async context, so
|
|
28
|
+
* without this the span is a root span. Return your page-load or route span's context.
|
|
29
|
+
*/
|
|
30
|
+
readonly parentContext?: () => Context | undefined;
|
|
31
|
+
/** Instrumentation scope name. Defaults to `@docx-editor.dev/telemetry`. */
|
|
32
|
+
readonly scope?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Record metrics and traces with OpenTelemetry. The exporter reads the global providers at
|
|
36
|
+
* each use, so it can be created before your SDK starts.
|
|
37
|
+
*
|
|
38
|
+
* Metrics use the names in `METRICS`. Each document open becomes a
|
|
39
|
+
* `docx_editor.document.open` span, with an error status when it is refused. Other events
|
|
40
|
+
* become events on the active span; without one, a failure gets a short span of its own.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { createTelemetry } from '@docx-editor.dev/telemetry';
|
|
45
|
+
* import { otelExporter } from '@docx-editor.dev/telemetry/otel';
|
|
46
|
+
*
|
|
47
|
+
* // Before or after your OpenTelemetry SDK starts: the exporter reads the global providers at
|
|
48
|
+
* // each use.
|
|
49
|
+
* export const telemetry = createTelemetry({ exporters: [otelExporter()] });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
declare function otelExporter(options?: OtelExporterOptions): TelemetryExporter;
|
|
55
|
+
|
|
56
|
+
export { TelemetryExporter, otelExporter };
|
|
57
|
+
export type { OtelExporterOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {c}from'../chunk-NUN46EXH.js';import {a}from'../chunk-DT2KMPUJ.js';import {SpanStatusCode,trace,metrics}from'@opentelemetry/api';var w="@docx-editor.dev/telemetry";function O(o){let n=o.getDelegate?.()??o;return /^Noop/.test(n?.constructor?.name??"")}var M=false;function E(o,n){let s=a();M||s==="production"||!O(o)||!O(n)||(M=true,console.warn("[docx-editor telemetry] otelExporter found no OpenTelemetry SDK. Register a MeterProvider and a TracerProvider before creating the exporter, or pass them as options. Until then every measurement is dropped."));}function h(o,n,s){return o.createObservableGauge(n.name,{unit:n.unit,description:n.description}).addCallback(t=>{for(let{value:i,attributes:a}of s.values())t.observe(i,a);}),{record(t,i={}){s.set(f(i),{value:t,attributes:i});},forget(t){s.delete(f(t));}}}function f(o){return JSON.stringify(Object.keys(o).sort().map(n=>[n,o[n]]))}function m(o){let n={...o.resource,...o.attributes};for(let[s,t]of Object.entries(o))if(!(s==="resource"||s==="attributes"||s==="hint"||s==="docs")){if(typeof t=="string"||typeof t=="number"||typeof t=="boolean")n[`docx_editor.${s}`]=t;else if(s==="shape"&&t&&typeof t=="object")for(let[i,a]of Object.entries(t))typeof a=="string"&&(n[`docx_editor.shape.${i}`]=a);else if(t&&typeof t=="object"&&!Array.isArray(t))for(let[i,a]of Object.entries(t))typeof a=="number"&&(n[`docx_editor.${s}.${i}`]=a);}return n}function j(o={}){let n=o.scope??w,s=()=>o.meterProvider??metrics.getMeterProvider(),t=()=>o.tracerProvider??trace.getTracerProvider(),i,a,l=new Map,p=new Map,g=false;function P(e){let r=p.get(e);return r||(r=new Map,p.set(e,r)),r}function k(){let e=s();return (e!==i||!a)&&(i=e,a=e.getMeter(n),l.clear()),a}function T(e){let r=k();g||(g=true,setTimeout(()=>E(s(),t()),5e3).unref?.());let c=l.get(e.name);if(c)return c;let d={unit:e.unit,description:e.description},u=e.kind==="counter"?r.createCounter(e.name,d):e.kind==="histogram"?r.createHistogram(e.name,{...d,...e.buckets?{advice:{explicitBucketBoundaries:[...e.buckets]}}:{}}):h(r,e,P(e.name));return l.set(e.name,u),u}return {name:"otel",metric(e){let r=T(e.definition),c=e.attributes;"add"in r?r.add(e.value,c):r.record(e.value,c);},event(e){if(e.kind==="document.open"){let d=e.time-(e.outcome==="opened"?e.durationMs:0),u=t().getTracer(n).startSpan("docx_editor.document.open",{startTime:d,attributes:m(e)},o.parentContext?.());e.outcome==="rejected"&&u.setStatus({code:SpanStatusCode.ERROR,message:e.code}),u.end(e.time);return}let r=trace.getActiveSpan();if(r){r.addEvent(`docx_editor.${e.kind}`,m(e));return}if(!c(e))return;let c$1=t().getTracer(n).startSpan(`docx_editor.${e.kind}`,{startTime:e.time,attributes:m(e)},o.parentContext?.());c$1.setStatus({code:SpanStatusCode.ERROR,message:"code"in e?e.code:e.kind}),c$1.end(e.time);},forget(e,r){p.get(e.name)?.delete(f(r));},async flush(){let e=r=>(r.getDelegate?.()??r).forceFlush?.();await Promise.all([e(s()),e(t())]);}}}export{j as otelExporter};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { EditorDiagnostic } from '@docx-editor.dev/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The metric catalog: one definition per metric, in OpenTelemetry terms. Prometheus names are
|
|
5
|
+
* derived from it mechanically by {@link prometheusName}, so the two outputs cannot drift.
|
|
6
|
+
*
|
|
7
|
+
* Every label is a bounded enum. No metric carries a room name, a user, or a document.
|
|
8
|
+
*/
|
|
9
|
+
/** How a metric aggregates. @public */
|
|
10
|
+
type MetricKind = 'counter' | 'gauge' | 'histogram';
|
|
11
|
+
/** One metric in the catalog. @public */
|
|
12
|
+
interface MetricDefinition {
|
|
13
|
+
/** OpenTelemetry name, such as `docx_editor.collab.room.load.duration`. */
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly kind: MetricKind;
|
|
16
|
+
/** UCUM unit as OpenTelemetry writes it: `s`, `By`, `1`, or a `{annotation}`. */
|
|
17
|
+
readonly unit: string;
|
|
18
|
+
readonly description: string;
|
|
19
|
+
/** The label keys this metric may carry. Each value is a bounded enum. */
|
|
20
|
+
readonly labels: readonly string[];
|
|
21
|
+
/** Histogram bucket upper bounds, in the metric's unit. */
|
|
22
|
+
readonly buckets?: readonly number[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Every metric the package records.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { METRICS, prometheusName } from '@docx-editor.dev/telemetry';
|
|
30
|
+
* prometheusName(METRICS.roomLoadDuration); // 'docx_editor_collab_room_load_duration_seconds'
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
declare const METRICS: Readonly<{
|
|
36
|
+
documentOpens: MetricDefinition;
|
|
37
|
+
documentOpenDuration: MetricDefinition;
|
|
38
|
+
editRejections: MetricDefinition;
|
|
39
|
+
reviewSkipped: MetricDefinition;
|
|
40
|
+
fontFailures: MetricDefinition;
|
|
41
|
+
historyDiagnostics: MetricDefinition;
|
|
42
|
+
customNodeDiagnostics: MetricDefinition;
|
|
43
|
+
collabClientStatusTransitions: MetricDefinition;
|
|
44
|
+
collabClientConnectDuration: MetricDefinition;
|
|
45
|
+
collabClientParticipants: MetricDefinition;
|
|
46
|
+
editorInteractionDuration: MetricDefinition;
|
|
47
|
+
editorLayoutDuration: MetricDefinition;
|
|
48
|
+
editorPaintDuration: MetricDefinition;
|
|
49
|
+
droppedEvents: MetricDefinition;
|
|
50
|
+
roomsActive: MetricDefinition;
|
|
51
|
+
connectionsActive: MetricDefinition;
|
|
52
|
+
participantsActive: MetricDefinition;
|
|
53
|
+
roomParticipants: MetricDefinition;
|
|
54
|
+
roomParticipantsMax: MetricDefinition;
|
|
55
|
+
roomMemory: MetricDefinition;
|
|
56
|
+
roomsMemory: MetricDefinition;
|
|
57
|
+
roomNodes: MetricDefinition;
|
|
58
|
+
roomResourceUsage: MetricDefinition;
|
|
59
|
+
roomAge: MetricDefinition;
|
|
60
|
+
roomIdle: MetricDefinition;
|
|
61
|
+
roomLoadDuration: MetricDefinition;
|
|
62
|
+
roomStoreDuration: MetricDefinition;
|
|
63
|
+
admissionRejections: MetricDefinition;
|
|
64
|
+
messageRefusals: MetricDefinition;
|
|
65
|
+
updates: MetricDefinition;
|
|
66
|
+
updateBytes: MetricDefinition;
|
|
67
|
+
roomParticipantsByRoom: MetricDefinition;
|
|
68
|
+
roomMemoryByRoom: MetricDefinition;
|
|
69
|
+
migrations: MetricDefinition;
|
|
70
|
+
}>;
|
|
71
|
+
/** A key of {@link METRICS}. @public */
|
|
72
|
+
type MetricId = keyof typeof METRICS;
|
|
73
|
+
/**
|
|
74
|
+
* The Prometheus name of a metric: dots become underscores, the unit becomes a suffix, and a
|
|
75
|
+
* counter ends in `_total`, as the OpenTelemetry Prometheus exporter writes it.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* prometheusName(METRICS.editRejections); // 'docx_editor_edit_rejections_total'
|
|
80
|
+
* prometheusName(METRICS.editorInteractionDuration); // 'docx_editor_editor_interaction_duration_seconds'
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @public
|
|
84
|
+
*/
|
|
85
|
+
declare function prometheusName(definition: MetricDefinition): string;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The shared delivery path: consent, the anonymity guard, and exporters that can never throw
|
|
89
|
+
* into the code they observe. The editor client and the room server both send through it.
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/** A metric attribute or a resource value. @public */
|
|
93
|
+
type AttributeValue = string | number | boolean;
|
|
94
|
+
/** Attributes or resource values, keyed by name. @public */
|
|
95
|
+
type Attributes = Readonly<Record<string, AttributeValue>>;
|
|
96
|
+
/** Context the package adds to every diagnostic before it reaches an exporter. @public */
|
|
97
|
+
interface TelemetryEventContext {
|
|
98
|
+
/** The fixed hint for the event's code. A constant, never document content. */
|
|
99
|
+
readonly hint?: string;
|
|
100
|
+
/** The documentation section for the event's code. */
|
|
101
|
+
readonly docs?: string;
|
|
102
|
+
/** The version of the editor packages. Core and this package release together. */
|
|
103
|
+
readonly engineVersion: string;
|
|
104
|
+
/**
|
|
105
|
+
* Present only on a summary: the event that closes a deduplication window repeats the first
|
|
106
|
+
* one, with the number of identical events folded into it beyond the first. Count it, do not
|
|
107
|
+
* treat it as a new failure. Its `time` is the first event's. The Sentry exporter records
|
|
108
|
+
* summaries as breadcrumbs.
|
|
109
|
+
*/
|
|
110
|
+
readonly repeated?: number;
|
|
111
|
+
/**
|
|
112
|
+
* The host's own values from `createTelemetry({ resource })`, such as `service.name`. The
|
|
113
|
+
* host decides what goes here, and every event carries it.
|
|
114
|
+
*/
|
|
115
|
+
readonly resource: Attributes;
|
|
116
|
+
/**
|
|
117
|
+
* Your identifiers for this event: the client's `attributes`, then the values of the
|
|
118
|
+
* `moduleFor` module of the editor that sent it, such as a room, a document, or a user.
|
|
119
|
+
* Empty when you set none. The engine never adds a value here.
|
|
120
|
+
*/
|
|
121
|
+
readonly attributes: Attributes;
|
|
122
|
+
}
|
|
123
|
+
/** A diagnostic as an exporter receives it. Switch on `kind` to narrow it. @public */
|
|
124
|
+
type TelemetryEvent = EditorDiagnostic & TelemetryEventContext;
|
|
125
|
+
/** One metric measurement. @public */
|
|
126
|
+
interface MetricRecord {
|
|
127
|
+
readonly definition: MetricDefinition;
|
|
128
|
+
/** A counter increment, a gauge value, or a histogram sample, in the metric's unit. */
|
|
129
|
+
readonly value: number;
|
|
130
|
+
readonly attributes: Attributes;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Where telemetry goes. Implement `event`, `metric`, or both.
|
|
134
|
+
*
|
|
135
|
+
* An exporter never throws into the editor or the server: a failure is caught and reported
|
|
136
|
+
* once through `onExporterError`.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* const toMyLogger: TelemetryExporter = {
|
|
141
|
+
* name: 'my-logger',
|
|
142
|
+
* event: (event) => myLogger.warn(event.kind, { code: 'code' in event ? event.code : undefined }),
|
|
143
|
+
* };
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @public
|
|
147
|
+
*/
|
|
148
|
+
interface TelemetryExporter {
|
|
149
|
+
/** Shown in `onExporterError` reports. */
|
|
150
|
+
readonly name: string;
|
|
151
|
+
event?(event: TelemetryEvent): void;
|
|
152
|
+
metric?(record: MetricRecord): void;
|
|
153
|
+
/**
|
|
154
|
+
* Drop a series that no longer exists, such as a closed room's per-room gauge. Optional:
|
|
155
|
+
* an exporter without it keeps the series until it expires on its own.
|
|
156
|
+
*/
|
|
157
|
+
forget?(definition: MetricDefinition, attributes: Attributes): void;
|
|
158
|
+
/**
|
|
159
|
+
* Start a series at zero before its first measurement. A scraper that sees a counter first
|
|
160
|
+
* at 1 cannot count that first increase, so the room server declares its known series when
|
|
161
|
+
* it starts. Optional: an exporter without it creates each series at its first measurement.
|
|
162
|
+
*/
|
|
163
|
+
declare?(definition: MetricDefinition, attributes: Attributes): void;
|
|
164
|
+
/** Send anything buffered. Called by `flush()` and when a page is hidden. */
|
|
165
|
+
flush?(): void | Promise<void>;
|
|
166
|
+
/** Flush and release resources. Called by `shutdown()`. */
|
|
167
|
+
shutdown?(): void | Promise<void>;
|
|
168
|
+
/**
|
|
169
|
+
* Called once when a telemetry client or room server takes the exporter. An exporter that
|
|
170
|
+
* holds events, such as a batching one, reads `consented()` before it sends, so events queued
|
|
171
|
+
* before consent was withdrawn are discarded, not sent.
|
|
172
|
+
*/
|
|
173
|
+
attach?(context: TelemetryExporterContext): void;
|
|
174
|
+
}
|
|
175
|
+
/** What a telemetry client tells an exporter it takes. @public */
|
|
176
|
+
interface TelemetryExporterContext {
|
|
177
|
+
/** Whether the host's consent is given, read now. */
|
|
178
|
+
consented(): boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Whether the page is hidden or closing, read now. While it is, a browser exporter sends with
|
|
181
|
+
* `sendBeacon` or `keepalive`, which still deliver after the page goes away.
|
|
182
|
+
*/
|
|
183
|
+
pageClosing(): boolean;
|
|
184
|
+
}
|
|
185
|
+
/** Options shared by the editor client and the room server. @public */
|
|
186
|
+
interface TelemetryPipelineOptions {
|
|
187
|
+
/** Where telemetry goes. Nothing is recorded or sent without at least one exporter. */
|
|
188
|
+
readonly exporters: readonly TelemetryExporter[];
|
|
189
|
+
/**
|
|
190
|
+
* Whether the viewer agreed to telemetry. Read before each event and metric, so a consent
|
|
191
|
+
* banner can change it at any time. Events before consent are dropped, not buffered.
|
|
192
|
+
* Defaults to `true`: without this option, events are sent. Pass it whenever your app asks
|
|
193
|
+
* for consent.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* consent: () => window.cookieConsent?.analytics === true
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
readonly consent?: boolean | (() => boolean);
|
|
201
|
+
/**
|
|
202
|
+
* Values added to every event, such as `{ 'service.name': 'contracts-app' }`. Metrics do not
|
|
203
|
+
* carry them: set resource attributes on your OpenTelemetry SDK or Prometheus target instead.
|
|
204
|
+
* You own these values: do not put a user, a document, or a room name here.
|
|
205
|
+
*/
|
|
206
|
+
readonly resource?: Attributes;
|
|
207
|
+
/** Told once per exporter when it throws. Defaults to one `console.warn`. */
|
|
208
|
+
readonly onExporterError?: (error: unknown, exporter: string) => void;
|
|
209
|
+
/**
|
|
210
|
+
* Keys of your own attributes that may also label metrics, such as `tenant.tier` or
|
|
211
|
+
* `app.region`. Only for values with few distinct values: each value makes a new series.
|
|
212
|
+
* After {@link MAX_METRIC_LABEL_VALUES} distinct values of one key, later values become
|
|
213
|
+
* `other`. Never list a user, document, or room id here; put those on events instead.
|
|
214
|
+
*/
|
|
215
|
+
readonly metricLabels?: readonly string[];
|
|
216
|
+
}
|
|
217
|
+
/** Distinct values one of your metric labels may take before later values become `other`. @public */
|
|
218
|
+
declare const MAX_METRIC_LABEL_VALUES = 50;
|
|
219
|
+
/** The version of the editor packages, or `'development'` when running from source. @public */
|
|
220
|
+
declare const ENGINE_VERSION: string;
|
|
221
|
+
|
|
222
|
+
export { ENGINE_VERSION as E, MAX_METRIC_LABEL_VALUES as d, METRICS as e, prometheusName as p };
|
|
223
|
+
export type { Attributes as A, MetricRecord as M, TelemetryPipelineOptions as T, TelemetryEvent as a, TelemetryExporter as b, AttributeValue as c, MetricDefinition as f, MetricId as g, MetricKind as h, TelemetryEventContext as i, TelemetryExporterContext as j };
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { EditorDiagnostic } from '@docx-editor.dev/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The metric catalog: one definition per metric, in OpenTelemetry terms. Prometheus names are
|
|
5
|
+
* derived from it mechanically by {@link prometheusName}, so the two outputs cannot drift.
|
|
6
|
+
*
|
|
7
|
+
* Every label is a bounded enum. No metric carries a room name, a user, or a document.
|
|
8
|
+
*/
|
|
9
|
+
/** How a metric aggregates. @public */
|
|
10
|
+
type MetricKind = 'counter' | 'gauge' | 'histogram';
|
|
11
|
+
/** One metric in the catalog. @public */
|
|
12
|
+
interface MetricDefinition {
|
|
13
|
+
/** OpenTelemetry name, such as `docx_editor.collab.room.load.duration`. */
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly kind: MetricKind;
|
|
16
|
+
/** UCUM unit as OpenTelemetry writes it: `s`, `By`, `1`, or a `{annotation}`. */
|
|
17
|
+
readonly unit: string;
|
|
18
|
+
readonly description: string;
|
|
19
|
+
/** The label keys this metric may carry. Each value is a bounded enum. */
|
|
20
|
+
readonly labels: readonly string[];
|
|
21
|
+
/** Histogram bucket upper bounds, in the metric's unit. */
|
|
22
|
+
readonly buckets?: readonly number[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Every metric the package records.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { METRICS, prometheusName } from '@docx-editor.dev/telemetry';
|
|
30
|
+
* prometheusName(METRICS.roomLoadDuration); // 'docx_editor_collab_room_load_duration_seconds'
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
declare const METRICS: Readonly<{
|
|
36
|
+
documentOpens: MetricDefinition;
|
|
37
|
+
documentOpenDuration: MetricDefinition;
|
|
38
|
+
editRejections: MetricDefinition;
|
|
39
|
+
reviewSkipped: MetricDefinition;
|
|
40
|
+
fontFailures: MetricDefinition;
|
|
41
|
+
historyDiagnostics: MetricDefinition;
|
|
42
|
+
customNodeDiagnostics: MetricDefinition;
|
|
43
|
+
collabClientStatusTransitions: MetricDefinition;
|
|
44
|
+
collabClientConnectDuration: MetricDefinition;
|
|
45
|
+
collabClientParticipants: MetricDefinition;
|
|
46
|
+
editorInteractionDuration: MetricDefinition;
|
|
47
|
+
editorLayoutDuration: MetricDefinition;
|
|
48
|
+
editorPaintDuration: MetricDefinition;
|
|
49
|
+
droppedEvents: MetricDefinition;
|
|
50
|
+
roomsActive: MetricDefinition;
|
|
51
|
+
connectionsActive: MetricDefinition;
|
|
52
|
+
participantsActive: MetricDefinition;
|
|
53
|
+
roomParticipants: MetricDefinition;
|
|
54
|
+
roomParticipantsMax: MetricDefinition;
|
|
55
|
+
roomMemory: MetricDefinition;
|
|
56
|
+
roomsMemory: MetricDefinition;
|
|
57
|
+
roomNodes: MetricDefinition;
|
|
58
|
+
roomResourceUsage: MetricDefinition;
|
|
59
|
+
roomAge: MetricDefinition;
|
|
60
|
+
roomIdle: MetricDefinition;
|
|
61
|
+
roomLoadDuration: MetricDefinition;
|
|
62
|
+
roomStoreDuration: MetricDefinition;
|
|
63
|
+
admissionRejections: MetricDefinition;
|
|
64
|
+
messageRefusals: MetricDefinition;
|
|
65
|
+
updates: MetricDefinition;
|
|
66
|
+
updateBytes: MetricDefinition;
|
|
67
|
+
roomParticipantsByRoom: MetricDefinition;
|
|
68
|
+
roomMemoryByRoom: MetricDefinition;
|
|
69
|
+
migrations: MetricDefinition;
|
|
70
|
+
}>;
|
|
71
|
+
/** A key of {@link METRICS}. @public */
|
|
72
|
+
type MetricId = keyof typeof METRICS;
|
|
73
|
+
/**
|
|
74
|
+
* The Prometheus name of a metric: dots become underscores, the unit becomes a suffix, and a
|
|
75
|
+
* counter ends in `_total`, as the OpenTelemetry Prometheus exporter writes it.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* prometheusName(METRICS.editRejections); // 'docx_editor_edit_rejections_total'
|
|
80
|
+
* prometheusName(METRICS.editorInteractionDuration); // 'docx_editor_editor_interaction_duration_seconds'
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @public
|
|
84
|
+
*/
|
|
85
|
+
declare function prometheusName(definition: MetricDefinition): string;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The shared delivery path: consent, the anonymity guard, and exporters that can never throw
|
|
89
|
+
* into the code they observe. The editor client and the room server both send through it.
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/** A metric attribute or a resource value. @public */
|
|
93
|
+
type AttributeValue = string | number | boolean;
|
|
94
|
+
/** Attributes or resource values, keyed by name. @public */
|
|
95
|
+
type Attributes = Readonly<Record<string, AttributeValue>>;
|
|
96
|
+
/** Context the package adds to every diagnostic before it reaches an exporter. @public */
|
|
97
|
+
interface TelemetryEventContext {
|
|
98
|
+
/** The fixed hint for the event's code. A constant, never document content. */
|
|
99
|
+
readonly hint?: string;
|
|
100
|
+
/** The documentation section for the event's code. */
|
|
101
|
+
readonly docs?: string;
|
|
102
|
+
/** The version of the editor packages. Core and this package release together. */
|
|
103
|
+
readonly engineVersion: string;
|
|
104
|
+
/**
|
|
105
|
+
* Present only on a summary: the event that closes a deduplication window repeats the first
|
|
106
|
+
* one, with the number of identical events folded into it beyond the first. Count it, do not
|
|
107
|
+
* treat it as a new failure. Its `time` is the first event's. The Sentry exporter records
|
|
108
|
+
* summaries as breadcrumbs.
|
|
109
|
+
*/
|
|
110
|
+
readonly repeated?: number;
|
|
111
|
+
/**
|
|
112
|
+
* The host's own values from `createTelemetry({ resource })`, such as `service.name`. The
|
|
113
|
+
* host decides what goes here, and every event carries it.
|
|
114
|
+
*/
|
|
115
|
+
readonly resource: Attributes;
|
|
116
|
+
/**
|
|
117
|
+
* Your identifiers for this event: the client's `attributes`, then the values of the
|
|
118
|
+
* `moduleFor` module of the editor that sent it, such as a room, a document, or a user.
|
|
119
|
+
* Empty when you set none. The engine never adds a value here.
|
|
120
|
+
*/
|
|
121
|
+
readonly attributes: Attributes;
|
|
122
|
+
}
|
|
123
|
+
/** A diagnostic as an exporter receives it. Switch on `kind` to narrow it. @public */
|
|
124
|
+
type TelemetryEvent = EditorDiagnostic & TelemetryEventContext;
|
|
125
|
+
/** One metric measurement. @public */
|
|
126
|
+
interface MetricRecord {
|
|
127
|
+
readonly definition: MetricDefinition;
|
|
128
|
+
/** A counter increment, a gauge value, or a histogram sample, in the metric's unit. */
|
|
129
|
+
readonly value: number;
|
|
130
|
+
readonly attributes: Attributes;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Where telemetry goes. Implement `event`, `metric`, or both.
|
|
134
|
+
*
|
|
135
|
+
* An exporter never throws into the editor or the server: a failure is caught and reported
|
|
136
|
+
* once through `onExporterError`.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* const toMyLogger: TelemetryExporter = {
|
|
141
|
+
* name: 'my-logger',
|
|
142
|
+
* event: (event) => myLogger.warn(event.kind, { code: 'code' in event ? event.code : undefined }),
|
|
143
|
+
* };
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @public
|
|
147
|
+
*/
|
|
148
|
+
interface TelemetryExporter {
|
|
149
|
+
/** Shown in `onExporterError` reports. */
|
|
150
|
+
readonly name: string;
|
|
151
|
+
event?(event: TelemetryEvent): void;
|
|
152
|
+
metric?(record: MetricRecord): void;
|
|
153
|
+
/**
|
|
154
|
+
* Drop a series that no longer exists, such as a closed room's per-room gauge. Optional:
|
|
155
|
+
* an exporter without it keeps the series until it expires on its own.
|
|
156
|
+
*/
|
|
157
|
+
forget?(definition: MetricDefinition, attributes: Attributes): void;
|
|
158
|
+
/**
|
|
159
|
+
* Start a series at zero before its first measurement. A scraper that sees a counter first
|
|
160
|
+
* at 1 cannot count that first increase, so the room server declares its known series when
|
|
161
|
+
* it starts. Optional: an exporter without it creates each series at its first measurement.
|
|
162
|
+
*/
|
|
163
|
+
declare?(definition: MetricDefinition, attributes: Attributes): void;
|
|
164
|
+
/** Send anything buffered. Called by `flush()` and when a page is hidden. */
|
|
165
|
+
flush?(): void | Promise<void>;
|
|
166
|
+
/** Flush and release resources. Called by `shutdown()`. */
|
|
167
|
+
shutdown?(): void | Promise<void>;
|
|
168
|
+
/**
|
|
169
|
+
* Called once when a telemetry client or room server takes the exporter. An exporter that
|
|
170
|
+
* holds events, such as a batching one, reads `consented()` before it sends, so events queued
|
|
171
|
+
* before consent was withdrawn are discarded, not sent.
|
|
172
|
+
*/
|
|
173
|
+
attach?(context: TelemetryExporterContext): void;
|
|
174
|
+
}
|
|
175
|
+
/** What a telemetry client tells an exporter it takes. @public */
|
|
176
|
+
interface TelemetryExporterContext {
|
|
177
|
+
/** Whether the host's consent is given, read now. */
|
|
178
|
+
consented(): boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Whether the page is hidden or closing, read now. While it is, a browser exporter sends with
|
|
181
|
+
* `sendBeacon` or `keepalive`, which still deliver after the page goes away.
|
|
182
|
+
*/
|
|
183
|
+
pageClosing(): boolean;
|
|
184
|
+
}
|
|
185
|
+
/** Options shared by the editor client and the room server. @public */
|
|
186
|
+
interface TelemetryPipelineOptions {
|
|
187
|
+
/** Where telemetry goes. Nothing is recorded or sent without at least one exporter. */
|
|
188
|
+
readonly exporters: readonly TelemetryExporter[];
|
|
189
|
+
/**
|
|
190
|
+
* Whether the viewer agreed to telemetry. Read before each event and metric, so a consent
|
|
191
|
+
* banner can change it at any time. Events before consent are dropped, not buffered.
|
|
192
|
+
* Defaults to `true`: without this option, events are sent. Pass it whenever your app asks
|
|
193
|
+
* for consent.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* consent: () => window.cookieConsent?.analytics === true
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
readonly consent?: boolean | (() => boolean);
|
|
201
|
+
/**
|
|
202
|
+
* Values added to every event, such as `{ 'service.name': 'contracts-app' }`. Metrics do not
|
|
203
|
+
* carry them: set resource attributes on your OpenTelemetry SDK or Prometheus target instead.
|
|
204
|
+
* You own these values: do not put a user, a document, or a room name here.
|
|
205
|
+
*/
|
|
206
|
+
readonly resource?: Attributes;
|
|
207
|
+
/** Told once per exporter when it throws. Defaults to one `console.warn`. */
|
|
208
|
+
readonly onExporterError?: (error: unknown, exporter: string) => void;
|
|
209
|
+
/**
|
|
210
|
+
* Keys of your own attributes that may also label metrics, such as `tenant.tier` or
|
|
211
|
+
* `app.region`. Only for values with few distinct values: each value makes a new series.
|
|
212
|
+
* After {@link MAX_METRIC_LABEL_VALUES} distinct values of one key, later values become
|
|
213
|
+
* `other`. Never list a user, document, or room id here; put those on events instead.
|
|
214
|
+
*/
|
|
215
|
+
readonly metricLabels?: readonly string[];
|
|
216
|
+
}
|
|
217
|
+
/** Distinct values one of your metric labels may take before later values become `other`. @public */
|
|
218
|
+
declare const MAX_METRIC_LABEL_VALUES = 50;
|
|
219
|
+
/** The version of the editor packages, or `'development'` when running from source. @public */
|
|
220
|
+
declare const ENGINE_VERSION: string;
|
|
221
|
+
|
|
222
|
+
export { ENGINE_VERSION as E, MAX_METRIC_LABEL_VALUES as d, METRICS as e, prometheusName as p };
|
|
223
|
+
export type { Attributes as A, MetricRecord as M, TelemetryPipelineOptions as T, TelemetryEvent as a, TelemetryExporter as b, AttributeValue as c, MetricDefinition as f, MetricId as g, MetricKind as h, TelemetryEventContext as i, TelemetryExporterContext as j };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var chunkMJX3ECH3_cjs=require('../chunk-MJX3ECH3.cjs');require('../chunk-LGNT4I5W.cjs');Object.defineProperty(exports,"PROMETHEUS_CONTENT_TYPE",{enumerable:true,get:function(){return chunkMJX3ECH3_cjs.a}});Object.defineProperty(exports,"fetchMetricsHandler",{enumerable:true,get:function(){return chunkMJX3ECH3_cjs.d}});Object.defineProperty(exports,"metricsHandler",{enumerable:true,get:function(){return chunkMJX3ECH3_cjs.c}});Object.defineProperty(exports,"prometheusRegistry",{enumerable:true,get:function(){return chunkMJX3ECH3_cjs.b}});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prometheus: an in-process registry in the text exposition format, and `/metrics` handlers for
|
|
3
|
+
* Node-style servers ({@link metricsHandler}) and Fetch-style servers
|
|
4
|
+
* ({@link fetchMetricsHandler}). No dependency on `prom-client`; a `prom-client` registry
|
|
5
|
+
* merges into the same endpoint.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import { b as TelemetryExporter } from '../pipeline-DbVW7S-K.cjs';
|
|
10
|
+
import '@docx-editor.dev/core';
|
|
11
|
+
|
|
12
|
+
/** The Prometheus text exposition content type. @public */
|
|
13
|
+
declare const PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
14
|
+
/** Anything that renders Prometheus text, such as a `prom-client` `Registry`. @public */
|
|
15
|
+
interface PrometheusTextSource {
|
|
16
|
+
metrics(): string | Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
/** The exporter returned by {@link prometheusRegistry}. @public */
|
|
19
|
+
interface PrometheusRegistry extends TelemetryExporter, PrometheusTextSource {
|
|
20
|
+
/** Every recorded metric in the Prometheus text format. */
|
|
21
|
+
metrics(): string;
|
|
22
|
+
/** Forget every recorded value. */
|
|
23
|
+
reset(): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Collect metrics in memory and render them for Prometheus to scrape.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { prometheusRegistry, metricsHandler } from '@docx-editor.dev/telemetry/prometheus';
|
|
31
|
+
*
|
|
32
|
+
* const metrics = prometheusRegistry();
|
|
33
|
+
* // Pass `metrics` as an exporter, then serve it:
|
|
34
|
+
* app.get('/metrics', metricsHandler(metrics));
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
declare function prometheusRegistry(): PrometheusRegistry;
|
|
40
|
+
/** The part of a Node `ServerResponse` the handler writes to. @public */
|
|
41
|
+
interface NodeResponseLike {
|
|
42
|
+
setHeader(name: string, value: string): unknown;
|
|
43
|
+
end(body: string): unknown;
|
|
44
|
+
statusCode?: number;
|
|
45
|
+
}
|
|
46
|
+
/** Options for {@link metricsHandler} and {@link fetchMetricsHandler}. @public */
|
|
47
|
+
interface MetricsHandlerOptions {
|
|
48
|
+
/**
|
|
49
|
+
* More sources rendered into the same response, such as your `prom-client` registry:
|
|
50
|
+
* `merge: [register]`.
|
|
51
|
+
*/
|
|
52
|
+
readonly merge?: readonly PrometheusTextSource[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A Node-style handler, `(request, response)`, for Node `http`, Express, and Koa
|
|
56
|
+
* (`ctx.res`). For Fastify, hijack the reply and pass `reply.raw`. For Fetch-style servers,
|
|
57
|
+
* use {@link fetchMetricsHandler}. Protect the route as you protect other operational
|
|
58
|
+
* endpoints.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* app.get('/metrics', metricsHandler(metrics, { merge: [promClient.register] })); // Express
|
|
63
|
+
*
|
|
64
|
+
* fastify.get('/metrics', (request, reply) => {
|
|
65
|
+
* reply.hijack();
|
|
66
|
+
* return metricsHandler(metrics)(request.raw, reply.raw);
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @public
|
|
71
|
+
*/
|
|
72
|
+
declare function metricsHandler(registry: PrometheusTextSource, options?: MetricsHandlerOptions): (request: unknown, response: NodeResponseLike) => Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* A Fetch-style handler that returns a `Response`, for Next.js route handlers, Hono, Bun,
|
|
75
|
+
* Deno, and workers. It ignores its argument, so a Hono context works as well as a `Request`.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* // app/metrics/route.ts
|
|
80
|
+
* export const GET = fetchMetricsHandler(metrics);
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @public
|
|
84
|
+
*/
|
|
85
|
+
declare function fetchMetricsHandler(registry: PrometheusTextSource, options?: MetricsHandlerOptions): (request?: unknown) => Promise<Response>;
|
|
86
|
+
|
|
87
|
+
export { PROMETHEUS_CONTENT_TYPE, TelemetryExporter, fetchMetricsHandler, metricsHandler, prometheusRegistry };
|
|
88
|
+
export type { MetricsHandlerOptions, NodeResponseLike, PrometheusRegistry, PrometheusTextSource };
|