@aztec/telemetry-client 0.0.0-test.1 → 0.0.1-commit.0208eb9

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 (90) hide show
  1. package/dest/attributes.d.ts +33 -9
  2. package/dest/attributes.d.ts.map +1 -1
  3. package/dest/attributes.js +22 -8
  4. package/dest/bench.d.ts +6 -1
  5. package/dest/bench.d.ts.map +1 -1
  6. package/dest/bench.js +31 -14
  7. package/dest/config.d.ts +7 -2
  8. package/dest/config.d.ts.map +1 -1
  9. package/dest/config.js +29 -1
  10. package/dest/index.d.ts +2 -1
  11. package/dest/index.d.ts.map +1 -1
  12. package/dest/index.js +1 -0
  13. package/dest/l1_metrics.d.ts +17 -0
  14. package/dest/l1_metrics.d.ts.map +1 -0
  15. package/dest/l1_metrics.js +54 -0
  16. package/dest/lmdb_metrics.d.ts +5 -3
  17. package/dest/lmdb_metrics.d.ts.map +1 -1
  18. package/dest/lmdb_metrics.js +7 -15
  19. package/dest/metric-utils.d.ts +24 -0
  20. package/dest/metric-utils.d.ts.map +1 -0
  21. package/dest/metric-utils.js +59 -0
  22. package/dest/metrics.d.ts +252 -122
  23. package/dest/metrics.d.ts.map +1 -1
  24. package/dest/metrics.js +1265 -120
  25. package/dest/nodejs_metrics_monitor.d.ts +19 -0
  26. package/dest/nodejs_metrics_monitor.d.ts.map +1 -0
  27. package/dest/nodejs_metrics_monitor.js +115 -0
  28. package/dest/noop.d.ts +9 -4
  29. package/dest/noop.d.ts.map +1 -1
  30. package/dest/noop.js +34 -1
  31. package/dest/otel.d.ts +13 -7
  32. package/dest/otel.d.ts.map +1 -1
  33. package/dest/otel.js +142 -21
  34. package/dest/otel_filter_metric_exporter.d.ts +12 -4
  35. package/dest/otel_filter_metric_exporter.d.ts.map +1 -1
  36. package/dest/otel_filter_metric_exporter.js +38 -4
  37. package/dest/otel_logger_provider.d.ts +1 -1
  38. package/dest/otel_propagation.d.ts +1 -1
  39. package/dest/otel_resource.d.ts +1 -1
  40. package/dest/otel_resource.d.ts.map +1 -1
  41. package/dest/otel_resource.js +43 -2
  42. package/dest/prom_otel_adapter.d.ts +61 -12
  43. package/dest/prom_otel_adapter.d.ts.map +1 -1
  44. package/dest/prom_otel_adapter.js +157 -48
  45. package/dest/start.d.ts +3 -2
  46. package/dest/start.d.ts.map +1 -1
  47. package/dest/start.js +8 -7
  48. package/dest/telemetry.d.ts +63 -34
  49. package/dest/telemetry.d.ts.map +1 -1
  50. package/dest/telemetry.js +47 -24
  51. package/dest/vendor/attributes.d.ts +1 -1
  52. package/dest/vendor/otel-pino-stream.d.ts +1 -2
  53. package/dest/vendor/otel-pino-stream.d.ts.map +1 -1
  54. package/dest/vendor/otel-pino-stream.js +2 -2
  55. package/dest/with_tracer.d.ts +1 -1
  56. package/dest/with_tracer.d.ts.map +1 -1
  57. package/dest/wrappers/fetch.d.ts +2 -2
  58. package/dest/wrappers/fetch.d.ts.map +1 -1
  59. package/dest/wrappers/fetch.js +7 -5
  60. package/dest/wrappers/index.d.ts +1 -1
  61. package/dest/wrappers/json_rpc_server.d.ts +2 -2
  62. package/dest/wrappers/json_rpc_server.d.ts.map +1 -1
  63. package/dest/wrappers/l2_block_stream.d.ts +4 -3
  64. package/dest/wrappers/l2_block_stream.d.ts.map +1 -1
  65. package/dest/wrappers/l2_block_stream.js +385 -11
  66. package/package.json +21 -15
  67. package/src/attributes.ts +42 -11
  68. package/src/bench.ts +37 -15
  69. package/src/config.ts +54 -2
  70. package/src/index.ts +1 -0
  71. package/src/l1_metrics.ts +65 -0
  72. package/src/lmdb_metrics.ts +24 -24
  73. package/src/metric-utils.ts +75 -0
  74. package/src/metrics.ts +1340 -145
  75. package/src/nodejs_metrics_monitor.ts +135 -0
  76. package/src/noop.ts +65 -4
  77. package/src/otel.ts +155 -26
  78. package/src/otel_filter_metric_exporter.ts +47 -5
  79. package/src/otel_resource.ts +57 -2
  80. package/src/prom_otel_adapter.ts +195 -70
  81. package/src/start.ts +12 -8
  82. package/src/telemetry.ts +153 -76
  83. package/src/vendor/otel-pino-stream.ts +1 -4
  84. package/src/wrappers/fetch.ts +24 -31
  85. package/src/wrappers/json_rpc_server.ts +1 -1
  86. package/src/wrappers/l2_block_stream.ts +6 -2
  87. package/dest/event_loop_monitor.d.ts +0 -18
  88. package/dest/event_loop_monitor.d.ts.map +0 -1
  89. package/dest/event_loop_monitor.js +0 -93
  90. package/src/event_loop_monitor.ts +0 -119
@@ -0,0 +1,135 @@
1
+ import type { Observable } from '@opentelemetry/api';
2
+ import { type EventLoopUtilization, type IntervalHistogram, monitorEventLoopDelay, performance } from 'node:perf_hooks';
3
+
4
+ import * as Attributes from './attributes.js';
5
+ import { createUpDownCounterWithDefault } from './metric-utils.js';
6
+ import * as Metrics from './metrics.js';
7
+ import type { BatchObservableResult, Meter, ObservableGauge, UpDownCounter } from './telemetry.js';
8
+
9
+ /** Monitors Node.js runtime metrics */
10
+ export class NodejsMetricsMonitor {
11
+ private eventLoopDelayGauges: {
12
+ min: ObservableGauge;
13
+ max: ObservableGauge;
14
+ mean: ObservableGauge;
15
+ stddev: ObservableGauge;
16
+ p50: ObservableGauge;
17
+ p90: ObservableGauge;
18
+ p99: ObservableGauge;
19
+ };
20
+
21
+ // skip `rss` because that's already tracked by @opentelemetry/host-metrics
22
+ // description of each field here https://nodejs.org/api/process.html#processmemoryusage
23
+ private memoryGauges: Record<Exclude<keyof NodeJS.MemoryUsage, 'rss'>, ObservableGauge>;
24
+
25
+ private eventLoopUilization: ObservableGauge;
26
+ private eventLoopTime: UpDownCounter;
27
+
28
+ private started = false;
29
+
30
+ private lastELU: EventLoopUtilization | undefined;
31
+ private eventLoopDelay: IntervalHistogram;
32
+
33
+ constructor(private meter: Meter) {
34
+ this.eventLoopDelayGauges = {
35
+ min: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_MIN),
36
+ mean: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_MEAN),
37
+ max: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_MAX),
38
+ stddev: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_STDDEV),
39
+ p50: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_P50),
40
+ p90: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_P90),
41
+ p99: meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_DELAY_P99),
42
+ };
43
+
44
+ this.eventLoopUilization = meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_UTILIZATION);
45
+ this.eventLoopTime = createUpDownCounterWithDefault(meter, Metrics.NODEJS_EVENT_LOOP_TIME, {
46
+ [Attributes.NODEJS_EVENT_LOOP_STATE]: ['idle', 'active'],
47
+ });
48
+ this.eventLoopDelay = monitorEventLoopDelay();
49
+
50
+ this.memoryGauges = {
51
+ heapUsed: meter.createObservableGauge(Metrics.NODEJS_MEMORY_HEAP_USAGE),
52
+ heapTotal: meter.createObservableGauge(Metrics.NODEJS_MEMORY_HEAP_TOTAL),
53
+ arrayBuffers: meter.createObservableGauge(Metrics.NODEJS_MEMORY_BUFFER_USAGE),
54
+ external: meter.createObservableGauge(Metrics.NODEJS_MEMORY_NATIVE_USAGE),
55
+ };
56
+ }
57
+
58
+ start(): void {
59
+ if (this.started) {
60
+ return;
61
+ }
62
+
63
+ this.lastELU = performance.eventLoopUtilization();
64
+ this.eventLoopDelay.enable();
65
+ this.meter.addBatchObservableCallback(this.measure, [
66
+ this.eventLoopUilization,
67
+ ...Object.values(this.eventLoopDelayGauges),
68
+ ...Object.values(this.memoryGauges),
69
+ ]);
70
+ }
71
+
72
+ stop(): void {
73
+ if (!this.started) {
74
+ return;
75
+ }
76
+ this.meter.removeBatchObservableCallback(this.measure, [
77
+ this.eventLoopUilization,
78
+ ...Object.values(this.eventLoopDelayGauges),
79
+ ...Object.values(this.memoryGauges),
80
+ ]);
81
+ this.eventLoopDelay.disable();
82
+ this.eventLoopDelay.reset();
83
+ this.lastELU = undefined;
84
+ }
85
+
86
+ private measure = (obs: BatchObservableResult): void => {
87
+ this.measureMemoryUsage(obs);
88
+ this.measureEventLoopDelay(obs);
89
+ };
90
+
91
+ private measureMemoryUsage = (observer: BatchObservableResult) => {
92
+ const mem = process.memoryUsage();
93
+
94
+ observer.observe(this.memoryGauges.heapUsed, mem.heapUsed);
95
+ observer.observe(this.memoryGauges.heapTotal, mem.heapTotal);
96
+ observer.observe(this.memoryGauges.arrayBuffers, mem.arrayBuffers);
97
+ observer.observe(this.memoryGauges.external, mem.external);
98
+ };
99
+
100
+ private measureEventLoopDelay = (obs: BatchObservableResult): void => {
101
+ const newELU = performance.eventLoopUtilization();
102
+ const delta = performance.eventLoopUtilization(newELU, this.lastELU);
103
+ this.lastELU = newELU;
104
+
105
+ // `utilization` [0,1] represents how much the event loop is busy vs waiting for new events to come in
106
+ // This should be corelated with CPU usage to gauge the performance characteristics of services
107
+ // 100% utilization leads to high latency because the event loop is _always_ busy, there's no breathing room for events to be processed quickly.
108
+ // Docs and examples:
109
+ // - https://nodesource.com/blog/event-loop-utilization-nodejs
110
+ // - https://youtu.be/WetXnEPraYM
111
+ obs.observe(this.eventLoopUilization, delta.utilization);
112
+
113
+ this.eventLoopTime.add(Math.trunc(delta.idle), { [Attributes.NODEJS_EVENT_LOOP_STATE]: 'idle' });
114
+ this.eventLoopTime.add(Math.trunc(delta.active), { [Attributes.NODEJS_EVENT_LOOP_STATE]: 'active' });
115
+
116
+ safeObserveInt(obs, this.eventLoopDelayGauges.min, this.eventLoopDelay.min);
117
+ safeObserveInt(obs, this.eventLoopDelayGauges.mean, this.eventLoopDelay.mean);
118
+ safeObserveInt(obs, this.eventLoopDelayGauges.max, this.eventLoopDelay.max);
119
+ safeObserveInt(obs, this.eventLoopDelayGauges.stddev, this.eventLoopDelay.stddev);
120
+ safeObserveInt(obs, this.eventLoopDelayGauges.p50, this.eventLoopDelay.percentile(50));
121
+ safeObserveInt(obs, this.eventLoopDelayGauges.p90, this.eventLoopDelay.percentile(90));
122
+ safeObserveInt(obs, this.eventLoopDelayGauges.p99, this.eventLoopDelay.percentile(99));
123
+
124
+ this.eventLoopDelay.reset();
125
+ };
126
+ }
127
+
128
+ function safeObserveInt(observer: BatchObservableResult, metric: Observable, value: number, attrs?: object) {
129
+ // discard NaN, Infinity, -Infinity
130
+ if (!Number.isFinite(value)) {
131
+ return;
132
+ }
133
+
134
+ observer.observe(metric, Math.trunc(value), attrs);
135
+ }
package/src/noop.ts CHANGED
@@ -1,10 +1,63 @@
1
- import { type Meter, type Span, type SpanContext, type Tracer, createNoopMeter } from '@opentelemetry/api';
1
+ import { type Context, type Span, type SpanContext, type Tracer, createNoopMeter } from '@opentelemetry/api';
2
2
 
3
- import type { TelemetryClient } from './telemetry.js';
3
+ import type { MetricDefinition } from './metrics.js';
4
+ import type {
5
+ Gauge,
6
+ Histogram,
7
+ Meter,
8
+ ObservableGauge,
9
+ ObservableUpDownCounter,
10
+ TelemetryClient,
11
+ UpDownCounter,
12
+ } from './telemetry.js';
13
+
14
+ /** A no-op meter that implements our custom Meter interface */
15
+ class NoopMeter implements Meter {
16
+ private otelMeter = createNoopMeter();
17
+
18
+ createGauge(_metric: MetricDefinition): Gauge {
19
+ return this.otelMeter.createGauge('');
20
+ }
21
+
22
+ createObservableGauge(_metric: MetricDefinition): ObservableGauge {
23
+ return this.otelMeter.createObservableGauge('');
24
+ }
25
+
26
+ createHistogram(_metric: MetricDefinition, _extraOptions?: Parameters<Meter['createHistogram']>[1]): Histogram {
27
+ return this.otelMeter.createHistogram('');
28
+ }
29
+
30
+ createUpDownCounter(_metric: MetricDefinition): UpDownCounter {
31
+ return this.otelMeter.createUpDownCounter('');
32
+ }
33
+
34
+ createObservableUpDownCounter(_metric: MetricDefinition): ObservableUpDownCounter {
35
+ return this.otelMeter.createObservableUpDownCounter('');
36
+ }
37
+
38
+ addBatchObservableCallback(
39
+ callback: Parameters<Meter['addBatchObservableCallback']>[0],
40
+ observables: Parameters<Meter['addBatchObservableCallback']>[1],
41
+ ): void {
42
+ this.otelMeter.addBatchObservableCallback(callback, observables);
43
+ }
44
+
45
+ removeBatchObservableCallback(
46
+ callback: Parameters<Meter['removeBatchObservableCallback']>[0],
47
+ observables: Parameters<Meter['removeBatchObservableCallback']>[1],
48
+ ): void {
49
+ this.otelMeter.removeBatchObservableCallback(callback, observables);
50
+ }
51
+ }
4
52
 
5
53
  export class NoopTelemetryClient implements TelemetryClient {
54
+ private meter = new NoopMeter();
55
+
56
+ setExportedPublicTelemetry(_prefixes: string[]): void {}
57
+ setPublicTelemetryCollectFrom(_roles: string[]): void {}
58
+
6
59
  getMeter(): Meter {
7
- return createNoopMeter();
60
+ return this.meter;
8
61
  }
9
62
 
10
63
  getTracer(): Tracer {
@@ -22,6 +75,14 @@ export class NoopTelemetryClient implements TelemetryClient {
22
75
  isEnabled() {
23
76
  return false;
24
77
  }
78
+
79
+ getTraceContext(): string | undefined {
80
+ return undefined;
81
+ }
82
+
83
+ extractPropagatedContext(_traceContext: string): Context | undefined {
84
+ return undefined;
85
+ }
25
86
  }
26
87
 
27
88
  // @opentelemetry/api internally uses NoopTracer and NoopSpan but they're not exported
@@ -32,7 +93,7 @@ export class NoopTracer implements Tracer {
32
93
  return new NoopSpan();
33
94
  }
34
95
 
35
- startActiveSpan<F extends (...args: any[]) => any>(_name: string, ...args: (unknown | F)[]): ReturnType<F> {
96
+ startActiveSpan<F extends (...args: any[]) => any>(_name: string, ...args: unknown[]): ReturnType<F> {
36
97
  // there are three different signatures for startActiveSpan, grab the function, we don't care about the rest
37
98
  const fn = args.find(arg => typeof arg === 'function') as F;
38
99
  return fn(new NoopSpan());
package/src/otel.ts CHANGED
@@ -1,16 +1,20 @@
1
1
  import { type LogData, type Logger, addLogDataHandler } from '@aztec/foundation/log';
2
2
 
3
3
  import {
4
+ type Context,
4
5
  DiagConsoleLogger,
5
6
  DiagLogLevel,
6
- type Meter,
7
+ type Meter as OtelMeter,
8
+ ROOT_CONTEXT,
7
9
  type Tracer,
8
10
  type TracerProvider,
9
11
  context,
10
12
  diag,
11
13
  isSpanContextValid,
14
+ propagation,
12
15
  trace,
13
16
  } from '@opentelemetry/api';
17
+ import { W3CTraceContextPropagator } from '@opentelemetry/core';
14
18
  import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
15
19
  import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
16
20
  import { HostMetrics } from '@opentelemetry/host-metrics';
@@ -28,18 +32,67 @@ import { BatchSpanProcessor, NodeTracerProvider } from '@opentelemetry/sdk-trace
28
32
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
29
33
 
30
34
  import type { TelemetryClientConfig } from './config.js';
31
- import { EventLoopMonitor } from './event_loop_monitor.js';
32
- import { OtelFilterMetricExporter } from './otel_filter_metric_exporter.js';
35
+ import { toMetricOptions } from './metric-utils.js';
36
+ import type { MetricDefinition } from './metrics.js';
37
+ import { NodejsMetricsMonitor } from './nodejs_metrics_monitor.js';
38
+ import { OtelFilterMetricExporter, PublicOtelFilterMetricExporter } from './otel_filter_metric_exporter.js';
33
39
  import { registerOtelLoggerProvider } from './otel_logger_provider.js';
34
40
  import { getOtelResource } from './otel_resource.js';
35
- import type { TelemetryClient } from './telemetry.js';
41
+ import type {
42
+ Gauge,
43
+ Histogram,
44
+ Meter,
45
+ ObservableGauge,
46
+ ObservableUpDownCounter,
47
+ TelemetryClient,
48
+ UpDownCounter,
49
+ } from './telemetry.js';
50
+
51
+ /** Wraps an OpenTelemetry Meter to implement our custom Meter interface */
52
+ class WrappedMeter implements Meter {
53
+ constructor(private otelMeter: OtelMeter) {}
54
+
55
+ createGauge(metric: MetricDefinition): Gauge {
56
+ return this.otelMeter.createGauge(metric.name, toMetricOptions(metric));
57
+ }
58
+
59
+ createObservableGauge(metric: MetricDefinition): ObservableGauge {
60
+ return this.otelMeter.createObservableGauge(metric.name, toMetricOptions(metric));
61
+ }
62
+
63
+ createHistogram(metric: MetricDefinition, extraOptions?: Parameters<Meter['createHistogram']>[1]): Histogram {
64
+ return this.otelMeter.createHistogram(metric.name, { ...toMetricOptions(metric), ...extraOptions });
65
+ }
66
+
67
+ createUpDownCounter(metric: MetricDefinition): UpDownCounter {
68
+ return this.otelMeter.createUpDownCounter(metric.name, toMetricOptions(metric));
69
+ }
70
+
71
+ createObservableUpDownCounter(metric: MetricDefinition): ObservableUpDownCounter {
72
+ return this.otelMeter.createObservableUpDownCounter(metric.name, toMetricOptions(metric));
73
+ }
74
+
75
+ addBatchObservableCallback(
76
+ callback: Parameters<Meter['addBatchObservableCallback']>[0],
77
+ observables: Parameters<Meter['addBatchObservableCallback']>[1],
78
+ ): void {
79
+ this.otelMeter.addBatchObservableCallback(callback, observables);
80
+ }
81
+
82
+ removeBatchObservableCallback(
83
+ callback: Parameters<Meter['removeBatchObservableCallback']>[0],
84
+ observables: Parameters<Meter['removeBatchObservableCallback']>[1],
85
+ ): void {
86
+ this.otelMeter.removeBatchObservableCallback(callback, observables);
87
+ }
88
+ }
36
89
 
37
90
  export type OpenTelemetryClientFactory = (resource: IResource, log: Logger) => OpenTelemetryClient;
38
91
 
39
92
  export class OpenTelemetryClient implements TelemetryClient {
40
93
  hostMetrics: HostMetrics | undefined;
41
- eventLoopMonitor: EventLoopMonitor | undefined;
42
- private meters: Map<string, Meter> = new Map<string, Meter>();
94
+ nodejsMetricsMonitor: NodejsMetricsMonitor | undefined;
95
+ private meters: Map<string, WrappedMeter> = new Map<string, WrappedMeter>();
43
96
  private tracers: Map<string, Tracer> = new Map<string, Tracer>();
44
97
 
45
98
  protected constructor(
@@ -47,13 +100,23 @@ export class OpenTelemetryClient implements TelemetryClient {
47
100
  private meterProvider: MeterProvider,
48
101
  private traceProvider: TracerProvider,
49
102
  private loggerProvider: LoggerProvider | undefined,
103
+ private publicMetricExporter: PublicOtelFilterMetricExporter | undefined,
50
104
  private log: Logger,
51
105
  ) {}
52
106
 
107
+ setExportedPublicTelemetry(metrics: string[]): void {
108
+ this.publicMetricExporter?.setMetricPrefixes(metrics);
109
+ }
110
+
111
+ setPublicTelemetryCollectFrom(roles: string[]): void {
112
+ this.publicMetricExporter?.setAllowedRoles(roles);
113
+ }
114
+
53
115
  getMeter(name: string): Meter {
54
116
  let meter = this.meters.get(name);
55
117
  if (!meter) {
56
- meter = this.meterProvider.getMeter(name, this.resource.attributes[ATTR_SERVICE_VERSION] as string);
118
+ const otelMeter = this.meterProvider.getMeter(name, this.resource.attributes[ATTR_SERVICE_VERSION] as string);
119
+ meter = new WrappedMeter(otelMeter);
57
120
  this.meters.set(name, meter);
58
121
  }
59
122
  return meter;
@@ -91,12 +154,13 @@ export class OpenTelemetryClient implements TelemetryClient {
91
154
  meterProvider: this.meterProvider,
92
155
  });
93
156
 
94
- this.eventLoopMonitor = new EventLoopMonitor(
157
+ const nodejsMeter = new WrappedMeter(
95
158
  this.meterProvider.getMeter(this.resource.attributes[ATTR_SERVICE_NAME] as string),
96
159
  );
160
+ this.nodejsMetricsMonitor = new NodejsMetricsMonitor(nodejsMeter);
97
161
 
98
162
  this.hostMetrics.start();
99
- this.eventLoopMonitor.start();
163
+ this.nodejsMetricsMonitor.start();
100
164
  }
101
165
 
102
166
  public isEnabled() {
@@ -112,7 +176,7 @@ export class OpenTelemetryClient implements TelemetryClient {
112
176
  }
113
177
 
114
178
  public async stop() {
115
- this.eventLoopMonitor?.stop();
179
+ this.nodejsMetricsMonitor?.stop();
116
180
 
117
181
  const flushAndShutdown = async (provider?: { forceFlush: () => Promise<void>; shutdown: () => Promise<void> }) => {
118
182
  if (!provider) {
@@ -129,18 +193,46 @@ export class OpenTelemetryClient implements TelemetryClient {
129
193
  ]);
130
194
  }
131
195
 
196
+ public getTraceContext(): string | undefined {
197
+ const carrier: Record<string, string> = {};
198
+ propagation.inject(context.active(), carrier);
199
+ return carrier['traceparent'];
200
+ }
201
+
202
+ public extractPropagatedContext(traceContext: string): Context {
203
+ const extractedContext = propagation.extract(ROOT_CONTEXT, {
204
+ traceparent: traceContext,
205
+ });
206
+ return extractedContext;
207
+ }
208
+
132
209
  public static createMeterProvider(
133
210
  resource: IResource,
134
- options: Partial<PeriodicExportingMetricReaderOptions>,
211
+ exporters: Array<PeriodicExportingMetricReaderOptions>,
135
212
  ): MeterProvider {
136
213
  return new MeterProvider({
137
214
  resource,
138
- readers: options.exporter
139
- ? [new PeriodicExportingMetricReader(options as PeriodicExportingMetricReaderOptions)]
140
- : [],
215
+ readers: exporters.map(options => new PeriodicExportingMetricReader(options)),
141
216
 
142
217
  views: [
143
218
  // Every histogram matching the selector (type + unit) gets these custom buckets assigned
219
+ new View({
220
+ instrumentType: InstrumentType.HISTOGRAM,
221
+ instrumentUnit: 'Mmana',
222
+ aggregation: new ExplicitBucketHistogramAggregation(
223
+ [0.1, 0.5, 1, 2, 4, 8, 10, 25, 50, 100, 500, 1000, 5000, 10000],
224
+ true,
225
+ ),
226
+ }),
227
+ new View({
228
+ instrumentType: InstrumentType.HISTOGRAM,
229
+ instrumentUnit: 'tx',
230
+ aggregation: new ExplicitBucketHistogramAggregation(
231
+ // TPS
232
+ [0.1 * 36, 0.2 * 36, 0.5 * 36, 1 * 36, 2 * 36, 5 * 36, 10 * 36, 15 * 36].map(Math.ceil),
233
+ true,
234
+ ),
235
+ }),
144
236
  new View({
145
237
  instrumentType: InstrumentType.HISTOGRAM,
146
238
  instrumentUnit: 's',
@@ -255,22 +347,59 @@ export class OpenTelemetryClient implements TelemetryClient {
255
347
  : [],
256
348
  });
257
349
 
258
- tracerProvider.register();
259
-
260
- const meterProvider = OpenTelemetryClient.createMeterProvider(resource, {
261
- exporter: config.metricsCollectorUrl
262
- ? new OtelFilterMetricExporter(
263
- new OTLPMetricExporter({ url: config.metricsCollectorUrl.href }),
264
- config.otelExcludeMetrics ?? [],
265
- )
266
- : undefined,
267
- exportTimeoutMillis: config.otelExportTimeoutMs,
268
- exportIntervalMillis: config.otelCollectIntervalMs,
350
+ tracerProvider.register({
351
+ propagator: new W3CTraceContextPropagator(),
269
352
  });
270
353
 
354
+ const exporters: PeriodicExportingMetricReaderOptions[] = [];
355
+ if (config.metricsCollectorUrl) {
356
+ // Default to a blacklist that is empty (allow all metrics)
357
+ let filter: string[] = [];
358
+ let mode: 'allow' | 'deny' = 'deny';
359
+ if (config.otelExcludeMetrics.length > 0) {
360
+ // Implement a blacklist as specified in config
361
+ log.info(`Excluding metrics from export: ${config.otelExcludeMetrics}`);
362
+ filter = config.otelExcludeMetrics;
363
+ mode = 'deny';
364
+ } else if (config.otelIncludeMetrics.length > 0) {
365
+ // Implement a whitelist as specified in config
366
+ log.info(`Including only specified metrics for export: ${config.otelIncludeMetrics}`);
367
+ filter = config.otelIncludeMetrics;
368
+ mode = 'allow';
369
+ }
370
+ exporters.push({
371
+ exporter: new OtelFilterMetricExporter(
372
+ new OTLPMetricExporter({ url: config.metricsCollectorUrl.href }),
373
+ filter,
374
+ mode,
375
+ ),
376
+ exportTimeoutMillis: config.otelExportTimeoutMs,
377
+ exportIntervalMillis: config.otelCollectIntervalMs,
378
+ });
379
+ }
380
+
381
+ let publicExporter: PublicOtelFilterMetricExporter | undefined;
382
+ if (config.publicMetricsCollectorUrl && !config.publicMetricsOptOut) {
383
+ log.info(`Exporting public metrics: ${config.publicIncludeMetrics}`, {
384
+ publicMetrics: config.publicIncludeMetrics,
385
+ collectorUrl: config.publicMetricsCollectorUrl,
386
+ });
387
+ publicExporter = new PublicOtelFilterMetricExporter(
388
+ config.publicMetricsCollectFrom,
389
+ new OTLPMetricExporter({ url: config.publicMetricsCollectorUrl.href }),
390
+ config.publicIncludeMetrics,
391
+ );
392
+ exporters.push({
393
+ exporter: publicExporter,
394
+ exportTimeoutMillis: config.otelExportTimeoutMs,
395
+ exportIntervalMillis: config.otelCollectIntervalMs,
396
+ });
397
+ }
398
+
399
+ const meterProvider = OpenTelemetryClient.createMeterProvider(resource, exporters);
271
400
  const loggerProvider = registerOtelLoggerProvider(resource, config.logsCollectorUrl);
272
401
 
273
- return new OpenTelemetryClient(resource, meterProvider, tracerProvider, loggerProvider, log);
402
+ return new OpenTelemetryClient(resource, meterProvider, tracerProvider, loggerProvider, publicExporter, log);
274
403
  };
275
404
  }
276
405
 
@@ -1,8 +1,14 @@
1
- import type { ExportResult } from '@opentelemetry/core';
1
+ import { type ExportResult, ExportResultCode } from '@opentelemetry/core';
2
2
  import type { MetricData, PushMetricExporter, ResourceMetrics } from '@opentelemetry/sdk-metrics';
3
3
 
4
+ import { AZTEC_NODE_ROLE } from './attributes.js';
5
+
4
6
  export class OtelFilterMetricExporter implements PushMetricExporter {
5
- constructor(private readonly exporter: PushMetricExporter, private readonly excludeMetricPrefixes: string[]) {
7
+ constructor(
8
+ private readonly exporter: PushMetricExporter,
9
+ private metricPrefix: string[],
10
+ private readonly filter: 'allow' | 'deny' = 'deny',
11
+ ) {
6
12
  if (exporter.selectAggregation) {
7
13
  (this as PushMetricExporter).selectAggregation = exporter.selectAggregation.bind(exporter);
8
14
  }
@@ -23,9 +29,17 @@ export class OtelFilterMetricExporter implements PushMetricExporter {
23
29
  }
24
30
 
25
31
  private filterMetrics(metrics: MetricData[]): MetricData[] {
26
- return metrics.filter(
27
- metric => !this.excludeMetricPrefixes.some(prefix => metric.descriptor.name.startsWith(prefix)),
28
- );
32
+ return metrics.filter(metric => {
33
+ const matched = this.metricPrefix.some(prefix => metric.descriptor.name.startsWith(prefix));
34
+
35
+ if (this.filter === 'deny') {
36
+ return !matched;
37
+ }
38
+
39
+ if (this.filter === 'allow') {
40
+ return matched;
41
+ }
42
+ });
29
43
  }
30
44
 
31
45
  public forceFlush(): Promise<void> {
@@ -35,4 +49,32 @@ export class OtelFilterMetricExporter implements PushMetricExporter {
35
49
  public shutdown(): Promise<void> {
36
50
  return this.exporter.shutdown();
37
51
  }
52
+
53
+ public setMetricPrefixes(metrics: string[]) {
54
+ this.metricPrefix = metrics;
55
+ }
56
+ }
57
+
58
+ export class PublicOtelFilterMetricExporter extends OtelFilterMetricExporter {
59
+ constructor(
60
+ private allowedRoles: string[],
61
+ exporter: PushMetricExporter,
62
+ metricPrefix: string[],
63
+ ) {
64
+ super(exporter, metricPrefix, 'allow');
65
+ }
66
+
67
+ public override export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void {
68
+ const role = String(metrics.resource.attributes[AZTEC_NODE_ROLE] ?? '');
69
+ if (!role || !this.allowedRoles.includes(role)) {
70
+ // noop
71
+ return resultCallback({ code: ExportResultCode.SUCCESS });
72
+ }
73
+
74
+ super.export(metrics, resultCallback);
75
+ }
76
+
77
+ public setAllowedRoles(roles: string[]) {
78
+ this.allowedRoles = roles;
79
+ }
38
80
  }
@@ -1,16 +1,71 @@
1
1
  import {
2
+ type DetectorSync,
2
3
  type IResource,
4
+ Resource,
3
5
  detectResourcesSync,
4
6
  envDetectorSync,
5
7
  osDetectorSync,
6
- processDetectorSync,
7
8
  serviceInstanceIdDetectorSync,
8
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
+ }
9
29
 
10
30
  export function getOtelResource(): IResource {
11
31
  const resource = detectResourcesSync({
12
- detectors: [osDetectorSync, envDetectorSync, processDetectorSync, serviceInstanceIdDetectorSync],
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
+ ],
13
42
  });
14
43
 
15
44
  return resource;
16
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
+ };