@aztec/telemetry-client 0.0.0-test.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/dest/attributes.d.ts +99 -0
- package/dest/attributes.d.ts.map +1 -0
- package/dest/attributes.js +67 -0
- package/dest/bench.d.ts +29 -0
- package/dest/bench.d.ts.map +1 -0
- package/dest/bench.js +98 -0
- package/dest/config.d.ts +12 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +39 -0
- package/dest/event_loop_monitor.d.ts +18 -0
- package/dest/event_loop_monitor.d.ts.map +1 -0
- package/dest/event_loop_monitor.js +93 -0
- package/dest/index.d.ts +10 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +9 -0
- package/dest/lmdb_metrics.d.ts +16 -0
- package/dest/lmdb_metrics.d.ts.map +1 -0
- package/dest/lmdb_metrics.js +42 -0
- package/dest/metrics.d.ts +129 -0
- package/dest/metrics.d.ts.map +1 -0
- package/dest/metrics.js +126 -0
- package/dest/noop.d.ts +14 -0
- package/dest/noop.d.ts.map +1 -0
- package/dest/noop.js +71 -0
- package/dest/otel.d.ts +32 -0
- package/dest/otel.d.ts.map +1 -0
- package/dest/otel.js +319 -0
- package/dest/otel_filter_metric_exporter.d.ts +12 -0
- package/dest/otel_filter_metric_exporter.d.ts.map +1 -0
- package/dest/otel_filter_metric_exporter.js +33 -0
- package/dest/otel_logger_provider.d.ts +4 -0
- package/dest/otel_logger_provider.d.ts.map +1 -0
- package/dest/otel_logger_provider.js +25 -0
- package/dest/otel_propagation.d.ts +3 -0
- package/dest/otel_propagation.d.ts.map +1 -0
- package/dest/otel_propagation.js +44 -0
- package/dest/otel_resource.d.ts +3 -0
- package/dest/otel_resource.d.ts.map +1 -0
- package/dest/otel_resource.js +12 -0
- package/dest/prom_otel_adapter.d.ts +127 -0
- package/dest/prom_otel_adapter.d.ts.map +1 -0
- package/dest/prom_otel_adapter.js +183 -0
- package/dest/start.d.ts +6 -0
- package/dest/start.d.ts.map +1 -0
- package/dest/start.js +24 -0
- package/dest/telemetry.d.ts +118 -0
- package/dest/telemetry.d.ts.map +1 -0
- package/dest/telemetry.js +116 -0
- package/dest/vendor/attributes.d.ts +5 -0
- package/dest/vendor/attributes.d.ts.map +1 -0
- package/dest/vendor/attributes.js +5 -0
- package/dest/vendor/otel-pino-stream.d.ts +41 -0
- package/dest/vendor/otel-pino-stream.d.ts.map +1 -0
- package/dest/vendor/otel-pino-stream.js +229 -0
- package/dest/with_tracer.d.ts +33 -0
- package/dest/with_tracer.d.ts.map +1 -0
- package/dest/with_tracer.js +32 -0
- package/dest/wrappers/fetch.d.ts +16 -0
- package/dest/wrappers/fetch.d.ts.map +1 -0
- package/dest/wrappers/fetch.js +39 -0
- package/dest/wrappers/index.d.ts +4 -0
- package/dest/wrappers/index.d.ts.map +1 -0
- package/dest/wrappers/index.js +3 -0
- package/dest/wrappers/json_rpc_server.d.ts +4 -0
- package/dest/wrappers/json_rpc_server.d.ts.map +1 -0
- package/dest/wrappers/json_rpc_server.js +11 -0
- package/dest/wrappers/l2_block_stream.d.ts +15 -0
- package/dest/wrappers/l2_block_stream.d.ts.map +1 -0
- package/dest/wrappers/l2_block_stream.js +26 -0
- package/package.json +89 -0
- package/src/attributes.ts +115 -0
- package/src/bench.ts +147 -0
- package/src/config.ts +56 -0
- package/src/event_loop_monitor.ts +119 -0
- package/src/index.ts +9 -0
- package/src/lmdb_metrics.ts +45 -0
- package/src/metrics.ts +153 -0
- package/src/noop.ts +91 -0
- package/src/otel.ts +286 -0
- package/src/otel_filter_metric_exporter.ts +38 -0
- package/src/otel_logger_provider.ts +31 -0
- package/src/otel_propagation.ts +50 -0
- package/src/otel_resource.ts +16 -0
- package/src/prom_otel_adapter.ts +326 -0
- package/src/start.ts +33 -0
- package/src/telemetry.ts +267 -0
- package/src/vendor/attributes.ts +5 -0
- package/src/vendor/otel-pino-stream.ts +282 -0
- package/src/with_tracer.ts +35 -0
- package/src/wrappers/fetch.ts +52 -0
- package/src/wrappers/index.ts +3 -0
- package/src/wrappers/json_rpc_server.ts +15 -0
- package/src/wrappers/l2_block_stream.ts +37 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
|
+
|
|
3
|
+
import { Registry } from 'prom-client';
|
|
4
|
+
|
|
5
|
+
import type { Meter, MetricsType, ObservableGauge, TelemetryClient } from './telemetry.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Types matching the gossipsub and libp2p services
|
|
9
|
+
*/
|
|
10
|
+
type TopicStr = string;
|
|
11
|
+
export type TopicLabel = string;
|
|
12
|
+
export type TopicStrToLabel = Map<TopicStr, TopicLabel>;
|
|
13
|
+
|
|
14
|
+
export enum MessageSource {
|
|
15
|
+
forward = 'forward',
|
|
16
|
+
publish = 'publish',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type NoLabels = Record<string, never>;
|
|
20
|
+
type LabelsGeneric = Record<string, string | number>;
|
|
21
|
+
type LabelKeys<Labels extends LabelsGeneric> = Extract<keyof Labels, string>;
|
|
22
|
+
interface CollectFn<Labels extends LabelsGeneric> {
|
|
23
|
+
(metric: IGauge<Labels>): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface IGauge<Labels extends LabelsGeneric = NoLabels> {
|
|
27
|
+
inc: NoLabels extends Labels ? (value?: number) => void : (labels: Labels, value?: number) => void;
|
|
28
|
+
set: NoLabels extends Labels ? (value: number) => void : (labels: Labels, value: number) => void;
|
|
29
|
+
|
|
30
|
+
collect?(): void;
|
|
31
|
+
addCollect(collectFn: CollectFn<Labels>): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface IHistogram<Labels extends LabelsGeneric = NoLabels> {
|
|
35
|
+
startTimer(): () => void;
|
|
36
|
+
|
|
37
|
+
observe: NoLabels extends Labels ? (value: number) => void : (labels: Labels, value: number) => void;
|
|
38
|
+
|
|
39
|
+
reset(): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface IAvgMinMax<Labels extends LabelsGeneric = NoLabels> {
|
|
43
|
+
set: NoLabels extends Labels ? (values: number[]) => void : (labels: Labels, values: number[]) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type GaugeConfig<Labels extends LabelsGeneric> = {
|
|
47
|
+
name: string;
|
|
48
|
+
help: string;
|
|
49
|
+
} & (NoLabels extends Labels
|
|
50
|
+
? { labelNames?: never }
|
|
51
|
+
: { labelNames: [LabelKeys<Labels>, ...Array<LabelKeys<Labels>>] });
|
|
52
|
+
|
|
53
|
+
export type HistogramConfig<Labels extends LabelsGeneric> = GaugeConfig<Labels> & {
|
|
54
|
+
buckets?: number[];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type AvgMinMaxConfig<Labels extends LabelsGeneric> = GaugeConfig<Labels>;
|
|
58
|
+
|
|
59
|
+
export interface MetricsRegister {
|
|
60
|
+
gauge<Labels extends LabelsGeneric = NoLabels>(config: GaugeConfig<Labels>): IGauge<Labels>;
|
|
61
|
+
histogram<Labels extends LabelsGeneric = NoLabels>(config: HistogramConfig<Labels>): IHistogram<Labels>;
|
|
62
|
+
avgMinMax<Labels extends LabelsGeneric = NoLabels>(config: AvgMinMaxConfig<Labels>): IAvgMinMax<Labels>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**Otel MetricsType Adapters
|
|
66
|
+
*
|
|
67
|
+
* Some dependencies we use export metrics directly in a Prometheus format
|
|
68
|
+
* This adapter is used to convert those metrics to a format that we can use with OpenTelemetry
|
|
69
|
+
*
|
|
70
|
+
* Affected services include:
|
|
71
|
+
* - chainsafe/gossipsub
|
|
72
|
+
* - libp2p
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
export class OtelGauge<Labels extends LabelsGeneric = NoLabels> implements IGauge<Labels> {
|
|
76
|
+
private gauge: ObservableGauge;
|
|
77
|
+
private currentValue: number = 0;
|
|
78
|
+
private labeledValues: Map<string, number> = new Map();
|
|
79
|
+
private collectFns: CollectFn<Labels>[] = [];
|
|
80
|
+
|
|
81
|
+
private _collect: () => void = () => {};
|
|
82
|
+
get collect(): () => void {
|
|
83
|
+
return this._collect;
|
|
84
|
+
}
|
|
85
|
+
set collect(fn: () => void) {
|
|
86
|
+
this._collect = fn;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
constructor(
|
|
90
|
+
private logger: Logger,
|
|
91
|
+
meter: Meter,
|
|
92
|
+
name: string,
|
|
93
|
+
help: string,
|
|
94
|
+
private labelNames: Array<keyof Labels> = [],
|
|
95
|
+
) {
|
|
96
|
+
this.gauge = meter.createObservableGauge(name as MetricsType, {
|
|
97
|
+
description: help,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Only observe in the callback when collect() is called
|
|
101
|
+
this.gauge.addCallback(this.handleObservation.bind(this));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Add a collect callback
|
|
106
|
+
* @param collectFn - Callback function
|
|
107
|
+
*/
|
|
108
|
+
addCollect(collectFn: CollectFn<Labels>): void {
|
|
109
|
+
this.collectFns.push(collectFn);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
handleObservation(result: any): void {
|
|
113
|
+
// Execute the main collect function if assigned
|
|
114
|
+
this._collect();
|
|
115
|
+
|
|
116
|
+
// Execute all the collect functions
|
|
117
|
+
for (const fn of this.collectFns) {
|
|
118
|
+
fn(this);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Report the current values
|
|
122
|
+
if (this.labelNames.length === 0) {
|
|
123
|
+
result.observe(this.currentValue);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const [labelStr, value] of this.labeledValues.entries()) {
|
|
128
|
+
const labels = this.parseLabelsSafely(labelStr);
|
|
129
|
+
if (labels) {
|
|
130
|
+
result.observe(value, labels);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Increments the gauge value
|
|
137
|
+
* @param labelsOrValue - Labels object or numeric value
|
|
138
|
+
* @param value - Value to increment by (defaults to 1)
|
|
139
|
+
*/
|
|
140
|
+
inc(value?: number): void;
|
|
141
|
+
inc(labels: Labels, value?: number): void;
|
|
142
|
+
inc(labelsOrValue?: Labels | number, value?: number): void {
|
|
143
|
+
if (typeof labelsOrValue === 'number') {
|
|
144
|
+
this.currentValue += labelsOrValue;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (labelsOrValue) {
|
|
149
|
+
this.validateLabels(labelsOrValue);
|
|
150
|
+
const labelKey = JSON.stringify(labelsOrValue);
|
|
151
|
+
const currentValue = this.labeledValues.get(labelKey) ?? 0;
|
|
152
|
+
this.labeledValues.set(labelKey, currentValue + (value ?? 1));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.currentValue += value ?? 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Sets the gauge value
|
|
161
|
+
* @param labelsOrValue - Labels object or numeric value
|
|
162
|
+
* @param value - Value to set
|
|
163
|
+
*/
|
|
164
|
+
set(value: number): void;
|
|
165
|
+
set(labels: Labels, value: number): void;
|
|
166
|
+
set(labelsOrValue: Labels | number, value?: number): void {
|
|
167
|
+
if (typeof labelsOrValue === 'number') {
|
|
168
|
+
this.currentValue = labelsOrValue;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.validateLabels(labelsOrValue);
|
|
173
|
+
const labelKey = JSON.stringify(labelsOrValue);
|
|
174
|
+
this.labeledValues.set(labelKey, value!);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Decrements the gauge value
|
|
179
|
+
* @param labels - Optional labels object
|
|
180
|
+
*/
|
|
181
|
+
dec(labels?: Labels): void {
|
|
182
|
+
if (labels) {
|
|
183
|
+
this.validateLabels(labels);
|
|
184
|
+
const labelKey = JSON.stringify(labels);
|
|
185
|
+
const currentValue = this.labeledValues.get(labelKey) ?? 0;
|
|
186
|
+
this.labeledValues.set(labelKey, currentValue - 1);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.currentValue -= 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Resets the gauge to initial state
|
|
195
|
+
*/
|
|
196
|
+
reset(): void {
|
|
197
|
+
this.currentValue = 0;
|
|
198
|
+
this.labeledValues.clear();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Validates that provided labels match the expected schema
|
|
203
|
+
* @param labels - Labels object to validate
|
|
204
|
+
* @throws Error if invalid labels are provided
|
|
205
|
+
*/
|
|
206
|
+
private validateLabels(labels: Labels): void {
|
|
207
|
+
if (this.labelNames.length === 0) {
|
|
208
|
+
throw new Error('Gauge was initialized without labels support');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const key of Object.keys(labels)) {
|
|
212
|
+
if (!this.labelNames.includes(key as keyof Labels)) {
|
|
213
|
+
throw new Error(`Invalid label key: ${key}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Safely parses label string back to object
|
|
220
|
+
* @param labelStr - Stringified labels object
|
|
221
|
+
* @returns Labels object or null if parsing fails
|
|
222
|
+
*/
|
|
223
|
+
private parseLabelsSafely(labelStr: string): Labels | null {
|
|
224
|
+
try {
|
|
225
|
+
return JSON.parse(labelStr) as Labels;
|
|
226
|
+
} catch {
|
|
227
|
+
this.logger.error(`Failed to parse label string: ${labelStr}`);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Noop implementation of a Historgram collec
|
|
235
|
+
*/
|
|
236
|
+
class NoopOtelHistogram<Labels extends LabelsGeneric = NoLabels> implements IHistogram<Labels> {
|
|
237
|
+
constructor(
|
|
238
|
+
private logger: Logger,
|
|
239
|
+
_meter: Meter,
|
|
240
|
+
_name: string, // MetricsType must be registered in the aztec labels registry
|
|
241
|
+
_help: string,
|
|
242
|
+
_buckets: number[] = [],
|
|
243
|
+
_labelNames: Array<keyof Labels> = [],
|
|
244
|
+
) {}
|
|
245
|
+
|
|
246
|
+
// Overload signatures
|
|
247
|
+
observe(_value: number): void;
|
|
248
|
+
observe(_labels: Labels, _value: number): void;
|
|
249
|
+
observe(_valueOrLabels: number | Labels, _value?: number): void {}
|
|
250
|
+
|
|
251
|
+
startTimer(_labels?: Labels): (_labels?: Labels) => number {
|
|
252
|
+
return () => 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
reset(): void {
|
|
256
|
+
// OpenTelemetry histograms cannot be reset, but we implement the interface
|
|
257
|
+
this.logger.warn('OpenTelemetry histograms cannot be reset');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Noop implementation of an AvgMinMax collector
|
|
263
|
+
*/
|
|
264
|
+
class NoopOtelAvgMinMax<Labels extends LabelsGeneric = NoLabels> implements IAvgMinMax<Labels> {
|
|
265
|
+
constructor(
|
|
266
|
+
private _logger: Logger,
|
|
267
|
+
_meter: Meter,
|
|
268
|
+
_name: string, // MetricsType must be registered in the aztec labels registry
|
|
269
|
+
_help: string,
|
|
270
|
+
_labelNames: Array<keyof Labels> = [],
|
|
271
|
+
) {}
|
|
272
|
+
|
|
273
|
+
set(_values: number[]): void;
|
|
274
|
+
set(_labels: Labels, _values: number[]): void;
|
|
275
|
+
set(_valueOrLabels: number[] | Labels, _values?: number[]): void {}
|
|
276
|
+
|
|
277
|
+
reset(): void {}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Otel metrics Adapter
|
|
282
|
+
*
|
|
283
|
+
* Maps the PromClient based MetricsRegister from gossipsub and discv5 services to the Otel MetricsRegister
|
|
284
|
+
*/
|
|
285
|
+
export class OtelMetricsAdapter extends Registry implements MetricsRegister {
|
|
286
|
+
private readonly meter: Meter;
|
|
287
|
+
|
|
288
|
+
constructor(
|
|
289
|
+
telemetryClient: TelemetryClient,
|
|
290
|
+
private logger: Logger = createLogger('telemetry:otel-metrics-adapter'),
|
|
291
|
+
) {
|
|
292
|
+
super();
|
|
293
|
+
this.meter = telemetryClient.getMeter('metrics-adapter');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
gauge<Labels extends LabelsGeneric = NoLabels>(configuration: GaugeConfig<Labels>): IGauge<Labels> {
|
|
297
|
+
return new OtelGauge<Labels>(
|
|
298
|
+
this.logger,
|
|
299
|
+
this.meter,
|
|
300
|
+
configuration.name as MetricsType,
|
|
301
|
+
configuration.help,
|
|
302
|
+
configuration.labelNames,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
histogram<Labels extends LabelsGeneric = NoLabels>(configuration: HistogramConfig<Labels>): IHistogram<Labels> {
|
|
307
|
+
return new NoopOtelHistogram<Labels>(
|
|
308
|
+
this.logger,
|
|
309
|
+
this.meter,
|
|
310
|
+
configuration.name as MetricsType,
|
|
311
|
+
configuration.help,
|
|
312
|
+
configuration.buckets,
|
|
313
|
+
configuration.labelNames,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
avgMinMax<Labels extends LabelsGeneric = NoLabels>(configuration: AvgMinMaxConfig<Labels>): IAvgMinMax<Labels> {
|
|
318
|
+
return new NoopOtelAvgMinMax<Labels>(
|
|
319
|
+
this.logger,
|
|
320
|
+
this.meter,
|
|
321
|
+
configuration.name as MetricsType,
|
|
322
|
+
configuration.help,
|
|
323
|
+
configuration.labelNames,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
package/src/start.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
2
|
+
|
|
3
|
+
import type { TelemetryClientConfig } from './config.js';
|
|
4
|
+
import { NoopTelemetryClient } from './noop.js';
|
|
5
|
+
import { OpenTelemetryClient } from './otel.js';
|
|
6
|
+
import type { TelemetryClient } from './telemetry.js';
|
|
7
|
+
|
|
8
|
+
export * from './config.js';
|
|
9
|
+
|
|
10
|
+
let initialised = false;
|
|
11
|
+
let telemetry: TelemetryClient = new NoopTelemetryClient();
|
|
12
|
+
|
|
13
|
+
export function initTelemetryClient(config: TelemetryClientConfig): TelemetryClient {
|
|
14
|
+
const log = createLogger('telemetry:client');
|
|
15
|
+
if (initialised) {
|
|
16
|
+
log.warn('Telemetry client has already been initialized once');
|
|
17
|
+
return telemetry;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (config.metricsCollectorUrl) {
|
|
21
|
+
log.info(`Using OpenTelemetry client with custom collector`);
|
|
22
|
+
telemetry = OpenTelemetryClient.createAndStart(config, log);
|
|
23
|
+
} else {
|
|
24
|
+
log.info('Using NoopTelemetryClient');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
initialised = true;
|
|
28
|
+
return telemetry;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getTelemetryClient(): TelemetryClient {
|
|
32
|
+
return telemetry;
|
|
33
|
+
}
|
package/src/telemetry.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AttributeValue,
|
|
3
|
+
type BatchObservableCallback,
|
|
4
|
+
type MetricOptions,
|
|
5
|
+
type Observable,
|
|
6
|
+
type BatchObservableResult as OtelBatchObservableResult,
|
|
7
|
+
type Gauge as OtelGauge,
|
|
8
|
+
type Histogram as OtelHistogram,
|
|
9
|
+
type ObservableGauge as OtelObservableGauge,
|
|
10
|
+
type ObservableResult as OtelObservableResult,
|
|
11
|
+
type ObservableUpDownCounter as OtelObservableUpDownCounter,
|
|
12
|
+
type UpDownCounter as OtelUpDownCounter,
|
|
13
|
+
type Span,
|
|
14
|
+
SpanStatusCode,
|
|
15
|
+
type Tracer,
|
|
16
|
+
} from '@opentelemetry/api';
|
|
17
|
+
import { isPromise } from 'node:util/types';
|
|
18
|
+
|
|
19
|
+
import type * as Attributes from './attributes.js';
|
|
20
|
+
import type * as Metrics from './metrics.js';
|
|
21
|
+
import { getTelemetryClient } from './start.js';
|
|
22
|
+
|
|
23
|
+
export { type Span, SpanStatusCode, ValueType } from '@opentelemetry/api';
|
|
24
|
+
|
|
25
|
+
type ValuesOf<T> = T extends Record<string, infer U> ? U : never;
|
|
26
|
+
|
|
27
|
+
/** Global registry of attributes */
|
|
28
|
+
type AttributesType = Partial<Record<ValuesOf<typeof Attributes>, AttributeValue>>;
|
|
29
|
+
export type { AttributesType };
|
|
30
|
+
|
|
31
|
+
/** Global registry of metrics */
|
|
32
|
+
type MetricsType = (typeof Metrics)[keyof typeof Metrics];
|
|
33
|
+
export type { MetricsType };
|
|
34
|
+
|
|
35
|
+
export type Gauge = OtelGauge<AttributesType>;
|
|
36
|
+
export type Histogram = OtelHistogram<AttributesType>;
|
|
37
|
+
export type UpDownCounter = OtelUpDownCounter<AttributesType>;
|
|
38
|
+
export type ObservableGauge = OtelObservableGauge<AttributesType>;
|
|
39
|
+
export type ObservableUpDownCounter = OtelObservableUpDownCounter<AttributesType>;
|
|
40
|
+
export type ObservableResult = OtelObservableResult<AttributesType>;
|
|
41
|
+
export type BatchObservableResult = OtelBatchObservableResult<AttributesType>;
|
|
42
|
+
|
|
43
|
+
export type { Tracer };
|
|
44
|
+
|
|
45
|
+
// INTERNAL NOTE: this interface is the same as opentelemetry's Meter, but with proper types
|
|
46
|
+
/**
|
|
47
|
+
* A meter that provides instruments for recording metrics.
|
|
48
|
+
*/
|
|
49
|
+
export interface Meter {
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new gauge instrument. A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
|
|
52
|
+
* @param name - The name of the gauge
|
|
53
|
+
* @param options - The options for the gauge
|
|
54
|
+
*/
|
|
55
|
+
createGauge(name: MetricsType, options?: MetricOptions): Gauge;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates a new gauge instrument. A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
|
|
59
|
+
* @param name - The name of the gauge
|
|
60
|
+
* @param options - The options for the gauge
|
|
61
|
+
*/
|
|
62
|
+
createObservableGauge(name: MetricsType, options?: MetricOptions): ObservableGauge;
|
|
63
|
+
|
|
64
|
+
addBatchObservableCallback(
|
|
65
|
+
callback: BatchObservableCallback<AttributesType>,
|
|
66
|
+
observables: Observable<AttributesType>[],
|
|
67
|
+
): void;
|
|
68
|
+
|
|
69
|
+
removeBatchObservableCallback(
|
|
70
|
+
callback: BatchObservableCallback<AttributesType>,
|
|
71
|
+
observables: Observable<AttributesType>[],
|
|
72
|
+
): void;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a new histogram instrument. A histogram is a metric that samples observations (usually things like request durations or response sizes) and counts them in configurable buckets.
|
|
76
|
+
* @param name - The name of the histogram
|
|
77
|
+
* @param options - The options for the histogram
|
|
78
|
+
*/
|
|
79
|
+
createHistogram(name: MetricsType, options?: MetricOptions): Histogram;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Creates a new counter instrument. A counter can go up or down with a delta from the previous value.
|
|
83
|
+
* @param name - The name of the counter
|
|
84
|
+
* @param options - The options for the counter
|
|
85
|
+
*/
|
|
86
|
+
createUpDownCounter(name: MetricsType, options?: MetricOptions): UpDownCounter;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Creates a new gauge instrument. A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
|
|
90
|
+
* @param name - The name of the gauge
|
|
91
|
+
* @param options - The options for the gauge
|
|
92
|
+
*/
|
|
93
|
+
createObservableUpDownCounter(name: MetricsType, options?: MetricOptions): ObservableUpDownCounter;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A telemetry client that provides meters for recording metrics.
|
|
98
|
+
*/
|
|
99
|
+
export interface TelemetryClient {
|
|
100
|
+
/**
|
|
101
|
+
* Whether the client is enabled and reporting metrics.
|
|
102
|
+
**/
|
|
103
|
+
isEnabled(): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Creates a new meter
|
|
106
|
+
* @param name - The name of the meter.
|
|
107
|
+
*/
|
|
108
|
+
getMeter(name: string): Meter;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Creates a new tracer
|
|
112
|
+
* @param name - The name of the tracer.
|
|
113
|
+
*/
|
|
114
|
+
getTracer(name: string): Tracer;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Stops the telemetry client.
|
|
118
|
+
*/
|
|
119
|
+
stop(): Promise<void>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Flushes the telemetry client.
|
|
123
|
+
*/
|
|
124
|
+
flush(): Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Objects that adhere to this interface can use @trackSpan */
|
|
128
|
+
export interface Traceable {
|
|
129
|
+
tracer: Tracer;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
type SpanDecorator<T extends Traceable, F extends (...args: any[]) => any> = (
|
|
133
|
+
originalMethod: F,
|
|
134
|
+
context: ClassMethodDecoratorContext<T>,
|
|
135
|
+
) => F;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Starts a new span whenever the decorated method is called.
|
|
139
|
+
* @param spanName - The name of the span to create. Can be a string or a function that returns a string.
|
|
140
|
+
* @param attributes - Initial attributes to set on the span. If a function is provided, it will be called with the arguments of the method.
|
|
141
|
+
* @param extraAttributes - Extra attributes to set on the span after the method is called. Will be called with the return value of the method. Note: if the function throws then this will not be called.
|
|
142
|
+
* @returns A decorator that wraps the method in a span.
|
|
143
|
+
*
|
|
144
|
+
* @privateRemarks
|
|
145
|
+
* This code looks complex but it's not that difficult:
|
|
146
|
+
* - decorators are functions that _replace_ a method with a different implementation
|
|
147
|
+
* - normal decorators can't take function arguments, but if we write a function that returns a decorator, we can pass arguments to that function
|
|
148
|
+
*
|
|
149
|
+
* The trackSpan function takes a span's name and some attributes and builds a decorator that wraps a method in a span with the given name and props
|
|
150
|
+
* The decorator can currently only be applied to methods on classes that have a `tracer` property. The compiler will enforce this.
|
|
151
|
+
*/
|
|
152
|
+
export function trackSpan<T extends Traceable, F extends (...args: any[]) => any>(
|
|
153
|
+
spanName: string | ((this: T, ...args: Parameters<F>) => string),
|
|
154
|
+
attributes?: AttributesType | ((this: T, ...args: Parameters<F>) => Promise<AttributesType> | AttributesType),
|
|
155
|
+
extraAttributes?: (this: T, returnValue: Awaited<ReturnType<F>>) => AttributesType,
|
|
156
|
+
): SpanDecorator<T, F> {
|
|
157
|
+
// the return value of trackSpan is a decorator
|
|
158
|
+
return (originalMethod: F, _context: ClassMethodDecoratorContext<T>) => {
|
|
159
|
+
// the return value of the decorator replaces the original method
|
|
160
|
+
// in this wrapper method we start a span, call the original method, and then end the span
|
|
161
|
+
return async function replacementMethod(this: T, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> {
|
|
162
|
+
const name = typeof spanName === 'function' ? spanName.call(this, ...args) : spanName;
|
|
163
|
+
const currentAttrs = typeof attributes === 'function' ? await attributes.call(this, ...args) : attributes;
|
|
164
|
+
|
|
165
|
+
// run originalMethod wrapped in an active span
|
|
166
|
+
// "active" means the span will be alive for the duration of the function execution
|
|
167
|
+
// and if any other spans are started during the execution of originalMethod, they will be children of this span
|
|
168
|
+
// behind the scenes this uses AsyncLocalStorage https://nodejs.org/dist/latest-v18.x/docs/api/async_context.html
|
|
169
|
+
return this.tracer.startActiveSpan(name, async (span: Span) => {
|
|
170
|
+
span.setAttributes(currentAttrs ?? {});
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const res = await originalMethod.call(this, ...args);
|
|
174
|
+
const extraAttrs = extraAttributes?.call(this, res);
|
|
175
|
+
span.setAttributes(extraAttrs ?? {});
|
|
176
|
+
return res;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
span.setStatus({
|
|
179
|
+
code: SpanStatusCode.ERROR,
|
|
180
|
+
message: String(err),
|
|
181
|
+
});
|
|
182
|
+
throw err;
|
|
183
|
+
} finally {
|
|
184
|
+
span.end();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
} as F;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Runs an event callback in a span. The span is started immediately and completes once the callback finishes running.
|
|
193
|
+
* The span will have two events added: 'callbackStart' and 'callbackEnd' to mark the start and end of the callback.
|
|
194
|
+
*
|
|
195
|
+
* @param tracer - The tracer instance to use
|
|
196
|
+
* @param spanName - The name of the span to create
|
|
197
|
+
* @param attributes - Initial attributes to set on the span
|
|
198
|
+
* @param callback - The callback to wrap in a span
|
|
199
|
+
*
|
|
200
|
+
* @returns - A new function that wraps the callback in a span
|
|
201
|
+
*/
|
|
202
|
+
export function wrapCallbackInSpan<F extends (...args: any[]) => any>(
|
|
203
|
+
tracer: Tracer,
|
|
204
|
+
spanName: string,
|
|
205
|
+
attributes: AttributesType,
|
|
206
|
+
callback: F,
|
|
207
|
+
): F {
|
|
208
|
+
const span = tracer.startSpan(spanName, { attributes });
|
|
209
|
+
return (async (...args: Parameters<F>) => {
|
|
210
|
+
try {
|
|
211
|
+
span.addEvent('callbackStart');
|
|
212
|
+
const res = await callback(...args);
|
|
213
|
+
return res;
|
|
214
|
+
} catch (err) {
|
|
215
|
+
span.setStatus({
|
|
216
|
+
code: SpanStatusCode.ERROR,
|
|
217
|
+
message: String(err),
|
|
218
|
+
});
|
|
219
|
+
throw err;
|
|
220
|
+
} finally {
|
|
221
|
+
span.addEvent('callbackEnd');
|
|
222
|
+
span.end();
|
|
223
|
+
}
|
|
224
|
+
}) as F;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function runInSpan<A extends any[], R>(
|
|
228
|
+
tracer: Tracer | string,
|
|
229
|
+
spanName: string,
|
|
230
|
+
callback: (span: Span, ...args: A) => R,
|
|
231
|
+
): (...args: A) => R {
|
|
232
|
+
return (...args: A): R => {
|
|
233
|
+
const actualTracer = typeof tracer === 'string' ? getTelemetryClient().getTracer(tracer) : tracer;
|
|
234
|
+
return actualTracer.startActiveSpan(spanName, (span: Span): R => {
|
|
235
|
+
let deferSpanEnd = false;
|
|
236
|
+
try {
|
|
237
|
+
const res = callback(span, ...args);
|
|
238
|
+
if (isPromise(res)) {
|
|
239
|
+
deferSpanEnd = true;
|
|
240
|
+
return res
|
|
241
|
+
.catch(err => {
|
|
242
|
+
span.setStatus({
|
|
243
|
+
code: SpanStatusCode.ERROR,
|
|
244
|
+
message: String(err),
|
|
245
|
+
});
|
|
246
|
+
throw err;
|
|
247
|
+
})
|
|
248
|
+
.finally(() => {
|
|
249
|
+
span.end();
|
|
250
|
+
}) as R;
|
|
251
|
+
} else {
|
|
252
|
+
return res;
|
|
253
|
+
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
span.setStatus({
|
|
256
|
+
code: SpanStatusCode.ERROR,
|
|
257
|
+
message: String(err),
|
|
258
|
+
});
|
|
259
|
+
throw err;
|
|
260
|
+
} finally {
|
|
261
|
+
if (!deferSpanEnd) {
|
|
262
|
+
span.end();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// See https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
|
|
2
|
+
export const ATTR_JSONRPC_METHOD = 'rpc.method';
|
|
3
|
+
export const ATTR_JSONRPC_REQUEST_ID = 'rpc.jsonrpc.request_id';
|
|
4
|
+
export const ATTR_JSONRPC_ERROR_CODE = 'rpc.jsonrpc.error_code';
|
|
5
|
+
export const ATTR_JSONRPC_ERROR_MSG = 'rpc.jsonrpc.error_message';
|