@aztec-labs/telemetry-client 6.0.0-nightly.20260829

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.
Files changed (105) hide show
  1. package/dest/attributes.d.ts +143 -0
  2. package/dest/attributes.d.ts.map +1 -0
  3. package/dest/attributes.js +91 -0
  4. package/dest/bench.d.ts +34 -0
  5. package/dest/bench.d.ts.map +1 -0
  6. package/dest/bench.js +115 -0
  7. package/dest/config.d.ts +21 -0
  8. package/dest/config.d.ts.map +1 -0
  9. package/dest/config.js +85 -0
  10. package/dest/index.d.ts +12 -0
  11. package/dest/index.d.ts.map +1 -0
  12. package/dest/index.js +11 -0
  13. package/dest/json_rpc_server_metrics.d.ts +27 -0
  14. package/dest/json_rpc_server_metrics.d.ts.map +1 -0
  15. package/dest/json_rpc_server_metrics.js +154 -0
  16. package/dest/l1_metrics.d.ts +17 -0
  17. package/dest/l1_metrics.d.ts.map +1 -0
  18. package/dest/l1_metrics.js +54 -0
  19. package/dest/lmdb_metrics.d.ts +18 -0
  20. package/dest/lmdb_metrics.d.ts.map +1 -0
  21. package/dest/lmdb_metrics.js +34 -0
  22. package/dest/metric-utils.d.ts +24 -0
  23. package/dest/metric-utils.d.ts.map +1 -0
  24. package/dest/metric-utils.js +59 -0
  25. package/dest/metrics.d.ts +338 -0
  26. package/dest/metrics.d.ts.map +1 -0
  27. package/dest/metrics.js +1702 -0
  28. package/dest/monitored_batch_span_processor.d.ts +33 -0
  29. package/dest/monitored_batch_span_processor.d.ts.map +1 -0
  30. package/dest/monitored_batch_span_processor.js +111 -0
  31. package/dest/nodejs_metrics_monitor.d.ts +19 -0
  32. package/dest/nodejs_metrics_monitor.d.ts.map +1 -0
  33. package/dest/nodejs_metrics_monitor.js +115 -0
  34. package/dest/noop.d.ts +19 -0
  35. package/dest/noop.d.ts.map +1 -0
  36. package/dest/noop.js +104 -0
  37. package/dest/otel.d.ts +43 -0
  38. package/dest/otel.d.ts.map +1 -0
  39. package/dest/otel.js +537 -0
  40. package/dest/otel_filter_metric_exporter.d.ts +20 -0
  41. package/dest/otel_filter_metric_exporter.d.ts.map +1 -0
  42. package/dest/otel_filter_metric_exporter.js +67 -0
  43. package/dest/otel_logger_provider.d.ts +4 -0
  44. package/dest/otel_logger_provider.d.ts.map +1 -0
  45. package/dest/otel_logger_provider.js +25 -0
  46. package/dest/otel_propagation.d.ts +6 -0
  47. package/dest/otel_propagation.d.ts.map +1 -0
  48. package/dest/otel_propagation.js +102 -0
  49. package/dest/otel_resource.d.ts +3 -0
  50. package/dest/otel_resource.d.ts.map +1 -0
  51. package/dest/otel_resource.js +53 -0
  52. package/dest/prom_otel_adapter.d.ts +176 -0
  53. package/dest/prom_otel_adapter.d.ts.map +1 -0
  54. package/dest/prom_otel_adapter.js +292 -0
  55. package/dest/start.d.ts +7 -0
  56. package/dest/start.d.ts.map +1 -0
  57. package/dest/start.js +25 -0
  58. package/dest/telemetry.d.ts +149 -0
  59. package/dest/telemetry.d.ts.map +1 -0
  60. package/dest/telemetry.js +139 -0
  61. package/dest/vendor/attributes.d.ts +6 -0
  62. package/dest/vendor/attributes.d.ts.map +1 -0
  63. package/dest/vendor/attributes.js +6 -0
  64. package/dest/vendor/otel-pino-stream.d.ts +40 -0
  65. package/dest/vendor/otel-pino-stream.d.ts.map +1 -0
  66. package/dest/vendor/otel-pino-stream.js +229 -0
  67. package/dest/with_tracer.d.ts +33 -0
  68. package/dest/with_tracer.d.ts.map +1 -0
  69. package/dest/with_tracer.js +32 -0
  70. package/dest/wrappers/fetch.d.ts +16 -0
  71. package/dest/wrappers/fetch.d.ts.map +1 -0
  72. package/dest/wrappers/fetch.js +42 -0
  73. package/dest/wrappers/index.d.ts +3 -0
  74. package/dest/wrappers/index.d.ts.map +1 -0
  75. package/dest/wrappers/index.js +2 -0
  76. package/dest/wrappers/json_rpc_server.d.ts +4 -0
  77. package/dest/wrappers/json_rpc_server.d.ts.map +1 -0
  78. package/dest/wrappers/json_rpc_server.js +16 -0
  79. package/package.json +95 -0
  80. package/src/attributes.ts +174 -0
  81. package/src/bench.ts +168 -0
  82. package/src/config.ts +135 -0
  83. package/src/index.ts +11 -0
  84. package/src/json_rpc_server_metrics.ts +150 -0
  85. package/src/l1_metrics.ts +64 -0
  86. package/src/lmdb_metrics.ts +45 -0
  87. package/src/metric-utils.ts +75 -0
  88. package/src/metrics.ts +1802 -0
  89. package/src/monitored_batch_span_processor.ts +147 -0
  90. package/src/nodejs_metrics_monitor.ts +135 -0
  91. package/src/noop.ts +152 -0
  92. package/src/otel.ts +482 -0
  93. package/src/otel_filter_metric_exporter.ts +80 -0
  94. package/src/otel_logger_provider.ts +31 -0
  95. package/src/otel_propagation.ts +107 -0
  96. package/src/otel_resource.ts +71 -0
  97. package/src/prom_otel_adapter.ts +450 -0
  98. package/src/start.ts +42 -0
  99. package/src/telemetry.ts +348 -0
  100. package/src/vendor/attributes.ts +6 -0
  101. package/src/vendor/otel-pino-stream.ts +279 -0
  102. package/src/with_tracer.ts +35 -0
  103. package/src/wrappers/fetch.ts +50 -0
  104. package/src/wrappers/index.ts +2 -0
  105. package/src/wrappers/json_rpc_server.ts +26 -0
@@ -0,0 +1,71 @@
1
+ import {
2
+ type DetectorSync,
3
+ type IResource,
4
+ Resource,
5
+ detectResourcesSync,
6
+ envDetectorSync,
7
+ osDetectorSync,
8
+ serviceInstanceIdDetectorSync,
9
+ } from '@opentelemetry/resources';
10
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
11
+ import { readFileSync } from 'fs';
12
+ import { dirname, resolve } from 'path';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ import { AZTEC_NODE_ROLE, AZTEC_REGISTRY_ADDRESS, AZTEC_ROLLUP_ADDRESS, AZTEC_ROLLUP_VERSION } from './attributes.js';
16
+
17
+ /** Reads the Aztec client version from the release manifest. */
18
+ function getAztecVersion(): string | undefined {
19
+ try {
20
+ const releasePleasePath = resolve(
21
+ dirname(fileURLToPath(import.meta.url)),
22
+ '../../../.release-please-manifest.json',
23
+ );
24
+ return JSON.parse(readFileSync(releasePleasePath, 'utf-8'))['.'];
25
+ } catch {
26
+ return undefined;
27
+ }
28
+ }
29
+
30
+ export function getOtelResource(): IResource {
31
+ const resource = detectResourcesSync({
32
+ detectors: [
33
+ aztecNetworkDetectorSync,
34
+ osDetectorSync,
35
+ envDetectorSync,
36
+ // this detector is disabled because:
37
+ // 1. our software runs in a docker container, a lot of the attributes detected would be identical across different machines (e.g. all run node v22, executing the same script, running PID 1, etc)
38
+ // 2. it catures process.argv which could contain sensitive values in plain text (e.g. validator private keys)
39
+ // processDetectorSync,
40
+ serviceInstanceIdDetectorSync,
41
+ ],
42
+ });
43
+
44
+ return resource;
45
+ }
46
+
47
+ const aztecNetworkDetectorSync: DetectorSync = {
48
+ detect(): IResource {
49
+ let role: string | undefined;
50
+ if (process.argv.includes('--sequencer')) {
51
+ role = 'sequencer';
52
+ } else if (process.argv.includes('--prover-node')) {
53
+ role = 'prover-node';
54
+ } else if (process.argv.includes('--node')) {
55
+ role = 'node';
56
+ } else if (process.argv.includes('--p2p-bootstrap')) {
57
+ role = 'bootnode';
58
+ }
59
+ const aztecAttributes = {
60
+ // this gets overwritten by OTEL_RESOURCE_ATTRIBUTES (if set)
61
+ [ATTR_SERVICE_NAME]: role ? `aztec-${role}` : undefined,
62
+ [ATTR_SERVICE_VERSION]: getAztecVersion(),
63
+ [AZTEC_NODE_ROLE]: role,
64
+ [AZTEC_ROLLUP_VERSION]: process.env.ROLLUP_VERSION ?? 'canonical',
65
+ [AZTEC_ROLLUP_ADDRESS]: process.env.ROLLUP_CONTRACT_ADDRESS,
66
+ [AZTEC_REGISTRY_ADDRESS]: process.env.REGISTRY_CONTRACT_ADDRESS,
67
+ };
68
+
69
+ return new Resource(aztecAttributes);
70
+ },
71
+ };
@@ -0,0 +1,450 @@
1
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec-labs/foundation/log';
2
+ import { Timer } from '@aztec-labs/foundation/timer';
3
+ import { Registry } from 'prom-client';
4
+
5
+ import type { MetricDefinition } from './metrics.js';
6
+ import type { Histogram, Meter, ObservableGauge, TelemetryClient } from './telemetry.js';
7
+
8
+ /**
9
+ * Types matching the gossipsub and libp2p services
10
+ */
11
+ type TopicStr = string;
12
+ export type TopicLabel = string;
13
+ export type TopicStrToLabel = Map<TopicStr, TopicLabel>;
14
+
15
+ export enum MessageSource {
16
+ forward = 'forward',
17
+ publish = 'publish',
18
+ }
19
+
20
+ type NoLabels = Record<string, never>;
21
+ type LabelsGeneric = Record<string, string | number>;
22
+ type LabelKeys<Labels extends LabelsGeneric> = Extract<keyof Labels, string>;
23
+ interface CollectFn<Labels extends LabelsGeneric> {
24
+ (metric: IGauge<Labels>): void;
25
+ }
26
+
27
+ interface IGauge<Labels extends LabelsGeneric = NoLabels> {
28
+ inc: NoLabels extends Labels ? (value?: number) => void : (labels: Labels, value?: number) => void;
29
+ set: NoLabels extends Labels ? (value: number) => void : (labels: Labels, value: number) => void;
30
+
31
+ collect?(): void;
32
+ addCollect(collectFn: CollectFn<Labels>): void;
33
+ }
34
+
35
+ interface IHistogram<Labels extends LabelsGeneric = NoLabels> {
36
+ startTimer(): () => void;
37
+
38
+ observe: NoLabels extends Labels ? (value: number) => void : (labels: Labels, value: number) => void;
39
+
40
+ reset(): void;
41
+ }
42
+
43
+ interface IAvgMinMax<Labels extends LabelsGeneric = NoLabels> {
44
+ set: NoLabels extends Labels ? (values: number[]) => void : (labels: Labels, values: number[]) => void;
45
+ }
46
+
47
+ export type GaugeConfig<Labels extends LabelsGeneric> = {
48
+ name: string;
49
+ help: string;
50
+ } & (NoLabels extends Labels
51
+ ? { labelNames?: never }
52
+ : { labelNames: [LabelKeys<Labels>, ...Array<LabelKeys<Labels>>] });
53
+
54
+ export type HistogramConfig<Labels extends LabelsGeneric> = GaugeConfig<Labels> & {
55
+ buckets?: number[];
56
+ };
57
+
58
+ export type AvgMinMaxConfig<Labels extends LabelsGeneric> = GaugeConfig<Labels>;
59
+
60
+ export interface MetricsRegister {
61
+ gauge<Labels extends LabelsGeneric = NoLabels>(config: GaugeConfig<Labels>): IGauge<Labels>;
62
+ histogram<Labels extends LabelsGeneric = NoLabels>(config: HistogramConfig<Labels>): IHistogram<Labels>;
63
+ avgMinMax<Labels extends LabelsGeneric = NoLabels>(config: AvgMinMaxConfig<Labels>): IAvgMinMax<Labels>;
64
+ }
65
+
66
+ /**Otel MetricsType Adapters
67
+ *
68
+ * Some dependencies we use export metrics directly in a Prometheus format
69
+ * This adapter is used to convert those metrics to a format that we can use with OpenTelemetry
70
+ *
71
+ * Affected services include:
72
+ * - chainsafe/gossipsub
73
+ * - libp2p
74
+ */
75
+
76
+ export class OtelGauge<Labels extends LabelsGeneric = NoLabels> implements IGauge<Labels> {
77
+ private gauge: ObservableGauge;
78
+ private currentValue: number = 0;
79
+ private labeledValues: Map<string, number> = new Map();
80
+ private collectFns: CollectFn<Labels>[] = [];
81
+
82
+ private _collect: () => void = () => {};
83
+ get collect(): () => void {
84
+ return this._collect;
85
+ }
86
+ set collect(fn: () => void) {
87
+ this._collect = fn;
88
+ }
89
+
90
+ constructor(
91
+ private logger: Logger,
92
+ meter: Meter,
93
+ name: string,
94
+ help: string,
95
+ private labelNames: Array<keyof Labels> = [],
96
+ ) {
97
+ const metricDef: MetricDefinition = { name, description: help };
98
+ this.gauge = meter.createObservableGauge(metricDef);
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 = parseLabelsSafely(labelStr, this.logger);
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
+ validateLabels(labelsOrValue, this.labelNames, 'Gauge');
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
+ validateLabels(labelsOrValue, this.labelNames, 'Gauge');
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
+ validateLabels(labels, this.labelNames, 'Gauge');
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
+ /**
203
+ * Implementation of a Histogram collector
204
+ */
205
+ export class OtelHistogram<Labels extends LabelsGeneric = NoLabels> implements IHistogram<Labels> {
206
+ private histogram: Histogram;
207
+
208
+ constructor(
209
+ private logger: Logger,
210
+ meter: Meter,
211
+ name: string,
212
+ help: string,
213
+ buckets: number[] = [],
214
+ private labelNames: Array<keyof Labels> = [],
215
+ ) {
216
+ const metricDef: MetricDefinition = { name, description: help };
217
+ this.histogram = meter.createHistogram(metricDef, {
218
+ advice: buckets.length ? { explicitBucketBoundaries: buckets } : undefined,
219
+ });
220
+ }
221
+
222
+ /**
223
+ * Starts a timer and returns a function that when called will record the time elapsed
224
+ * @param labels - Optional labels for the observation
225
+ */
226
+ startTimer(labels?: Labels): () => void {
227
+ if (labels) {
228
+ validateLabels(labels, this.labelNames, 'Histogram');
229
+ }
230
+
231
+ const timer = new Timer();
232
+ return () => {
233
+ // Use timer.s() here to get the duration in seconds since this is only currently used by gossipsub_heartbeat_duration_seconds
234
+ const duration = timer.s();
235
+ if (labels) {
236
+ this.observe(labels, duration);
237
+ } else {
238
+ this.observe(duration);
239
+ }
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Observes a value
245
+ * @param value - Value to observe
246
+ */
247
+ observe(value: number): void;
248
+ /**
249
+ * Observes a value with labels
250
+ * @param labels - Labels object
251
+ * @param value - Value to observe
252
+ */
253
+ observe(labels: Labels, value: number): void;
254
+ observe(labelsOrValue: Labels | number, value?: number): void {
255
+ if (typeof labelsOrValue === 'number') {
256
+ this.histogram.record(labelsOrValue);
257
+ } else {
258
+ validateLabels(labelsOrValue, this.labelNames, 'Histogram');
259
+ this.histogram.record(value!, labelsOrValue);
260
+ }
261
+ }
262
+
263
+ reset(): void {
264
+ // OpenTelemetry histograms cannot be reset, but we implement the interface
265
+ this.logger.silent('OpenTelemetry histograms cannot be fully reset');
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Implementation of an AvgMinMax collector
271
+ */
272
+ export class OtelAvgMinMax<Labels extends LabelsGeneric = NoLabels> implements IAvgMinMax<Labels> {
273
+ private gauges: {
274
+ avg: ObservableGauge;
275
+ min: ObservableGauge;
276
+ max: ObservableGauge;
277
+ };
278
+
279
+ private currentValues: number[] = [];
280
+ private labeledValues: Map<string, number[]> = new Map();
281
+
282
+ constructor(
283
+ private logger: Logger,
284
+ meter: Meter,
285
+ name: string,
286
+ help: string,
287
+ private labelNames: Array<keyof Labels> = [],
288
+ ) {
289
+ // Create three separate gauges for avg, min, and max
290
+ this.gauges = {
291
+ avg: meter.createObservableGauge({ name: `${name}_avg`, description: `${help} (average)` }),
292
+ min: meter.createObservableGauge({ name: `${name}_min`, description: `${help} (minimum)` }),
293
+ max: meter.createObservableGauge({ name: `${name}_max`, description: `${help} (maximum)` }),
294
+ };
295
+
296
+ // Register callbacks for each gauge
297
+ this.gauges.avg.addCallback(this.observeAvg.bind(this));
298
+ this.gauges.min.addCallback(this.observeMin.bind(this));
299
+ this.gauges.max.addCallback(this.observeMax.bind(this));
300
+ }
301
+
302
+ /**
303
+ * Sets the values for calculating avg, min, max
304
+ * @param values - Array of values
305
+ */
306
+ set(values: number[]): void;
307
+ /**
308
+ * Sets the values for calculating avg, min, max with labels
309
+ * @param labels - Labels object
310
+ * @param values - Array of values
311
+ */
312
+ set(labels: Labels, values: number[]): void;
313
+ set(labelsOrValues: number[] | Labels, values?: number[]): void {
314
+ if (Array.isArray(labelsOrValues)) {
315
+ this.currentValues = labelsOrValues;
316
+ return;
317
+ } else {
318
+ validateLabels(labelsOrValues, this.labelNames, 'AvgMinMax');
319
+ const labelKey = JSON.stringify(labelsOrValues);
320
+ this.labeledValues.set(labelKey, values || []);
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Resets all stored values
326
+ */
327
+ reset(): void {
328
+ this.currentValues = [];
329
+ this.labeledValues.clear();
330
+ }
331
+
332
+ /**
333
+ * General function to observe an aggregation
334
+ * @param result - Observer result
335
+ * @param aggregateFn - Function that calculates the aggregation
336
+ */
337
+ private observeAggregation(result: any, aggregateFn: (arr: number[]) => number): void {
338
+ // Observe unlabeled values
339
+ if (this.currentValues.length > 0) {
340
+ result.observe(aggregateFn(this.currentValues));
341
+ }
342
+
343
+ // Observe labeled values
344
+ for (const [labelStr, values] of this.labeledValues.entries()) {
345
+ if (values.length > 0) {
346
+ const labels = parseLabelsSafely(labelStr, this.logger);
347
+ if (labels) {
348
+ result.observe(aggregateFn(values), labels);
349
+ }
350
+ }
351
+ }
352
+ }
353
+
354
+ private observeAvg(result: any): void {
355
+ this.observeAggregation(result, arr => arr.reduce((sum, val) => sum + val, 0) / arr.length);
356
+ }
357
+
358
+ private observeMin(result: any): void {
359
+ this.observeAggregation(result, arr => Math.min.apply(null, arr));
360
+ }
361
+
362
+ private observeMax(result: any): void {
363
+ this.observeAggregation(result, arr => Math.max.apply(null, arr));
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Validates that provided labels match the expected schema
369
+ * @param labels - Labels object to validate
370
+ * @param labelNames - Array of allowed label names
371
+ * @param metricType - Type of metric for error message ('Gauge', 'Histogram', 'AvgMinMax')
372
+ * @throws Error if invalid labels are provided
373
+ */
374
+ function validateLabels<Labels extends LabelsGeneric>(
375
+ labels: Labels,
376
+ labelNames: Array<keyof Labels>,
377
+ metricType: string,
378
+ ): void {
379
+ if (labelNames.length === 0) {
380
+ throw new Error(`${metricType} was initialized without labels support`);
381
+ }
382
+
383
+ for (const key of Object.keys(labels)) {
384
+ if (!labelNames.includes(key as keyof Labels)) {
385
+ throw new Error(`Invalid label key: ${key}`);
386
+ }
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Safely parses label string back to object
392
+ * @param labelStr - Stringified labels object
393
+ * @param logger - Logger instance for error reporting
394
+ * @returns Labels object or null if parsing fails
395
+ */
396
+ function parseLabelsSafely<Labels extends LabelsGeneric>(labelStr: string, logger: Logger): Labels | null {
397
+ try {
398
+ return JSON.parse(labelStr) as Labels;
399
+ } catch {
400
+ logger.error(`Failed to parse label string: ${labelStr}`);
401
+ return null;
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Otel metrics Adapter
407
+ *
408
+ * Maps the PromClient based MetricsRegister from gossipsub and discv5 services to the Otel MetricsRegister
409
+ */
410
+ export class OtelMetricsAdapter extends Registry implements MetricsRegister {
411
+ private readonly meter: Meter;
412
+ private logger: Logger;
413
+
414
+ constructor(telemetryClient: TelemetryClient, bindings?: LoggerBindings) {
415
+ super();
416
+ this.logger = createLogger('telemetry:otel-metrics-adapter', bindings);
417
+ this.meter = telemetryClient.getMeter('metrics-adapter');
418
+ }
419
+
420
+ gauge<Labels extends LabelsGeneric = NoLabels>(configuration: GaugeConfig<Labels>): IGauge<Labels> {
421
+ return new OtelGauge<Labels>(
422
+ this.logger,
423
+ this.meter,
424
+ configuration.name,
425
+ configuration.help,
426
+ configuration.labelNames,
427
+ );
428
+ }
429
+
430
+ histogram<Labels extends LabelsGeneric = NoLabels>(configuration: HistogramConfig<Labels>): IHistogram<Labels> {
431
+ return new OtelHistogram<Labels>(
432
+ this.logger,
433
+ this.meter,
434
+ configuration.name,
435
+ configuration.help,
436
+ configuration.buckets,
437
+ configuration.labelNames,
438
+ );
439
+ }
440
+
441
+ avgMinMax<Labels extends LabelsGeneric = NoLabels>(configuration: AvgMinMaxConfig<Labels>): IAvgMinMax<Labels> {
442
+ return new OtelAvgMinMax<Labels>(
443
+ this.logger,
444
+ this.meter,
445
+ configuration.name,
446
+ configuration.help,
447
+ configuration.labelNames,
448
+ );
449
+ }
450
+ }
package/src/start.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { type LoggerBindings, createLogger } from '@aztec-labs/foundation/log';
2
+
3
+ import type { TelemetryClientConfig } from './config.js';
4
+ import { NoopTelemetryClient } from './noop.js';
5
+ import type { TelemetryClient } from './telemetry.js';
6
+
7
+ export * from './config.js';
8
+
9
+ let initialized = false;
10
+ let telemetry: TelemetryClient = new NoopTelemetryClient();
11
+
12
+ export async function initTelemetryClient(
13
+ config: TelemetryClientConfig,
14
+ bindings?: LoggerBindings,
15
+ ): Promise<TelemetryClient> {
16
+ const log = createLogger('telemetry:client', bindings);
17
+ if (initialized) {
18
+ log.warn('Telemetry client has already been initialized once');
19
+ return telemetry;
20
+ }
21
+
22
+ if (
23
+ config.metricsCollectorUrl ||
24
+ config.publicMetricsCollectorUrl ||
25
+ config.tracesCollectorUrl ||
26
+ config.logsCollectorUrl
27
+ ) {
28
+ log.info(`Using OpenTelemetry client with custom collector`);
29
+ // Lazy load OpenTelemetry to avoid loading heavy deps at startup
30
+ const { OpenTelemetryClient } = await import('./otel.js');
31
+ telemetry = OpenTelemetryClient.createAndStart(config, log);
32
+ } else {
33
+ log.info('Using NoopTelemetryClient');
34
+ }
35
+
36
+ initialized = true;
37
+ return telemetry;
38
+ }
39
+
40
+ export function getTelemetryClient(): TelemetryClient {
41
+ return telemetry;
42
+ }