@aztec/telemetry-client 0.0.1-commit.b655e406 → 0.0.1-commit.b9865e97
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 +24 -2
- package/dest/attributes.d.ts.map +1 -1
- package/dest/attributes.js +12 -1
- package/dest/bench.d.ts +3 -1
- package/dest/bench.d.ts.map +1 -1
- package/dest/bench.js +24 -14
- package/dest/config.d.ts +4 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +24 -10
- package/dest/index.d.ts +1 -1
- package/dest/l1_metrics.d.ts +2 -2
- package/dest/l1_metrics.d.ts.map +1 -1
- package/dest/l1_metrics.js +4 -20
- package/dest/lmdb_metrics.d.ts +3 -3
- package/dest/lmdb_metrics.d.ts.map +1 -1
- package/dest/lmdb_metrics.js +4 -20
- package/dest/metric-utils.d.ts +24 -0
- package/dest/metric-utils.d.ts.map +1 -0
- package/dest/metric-utils.js +59 -0
- package/dest/metrics.d.ts +312 -196
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +1599 -194
- package/dest/monitored_batch_span_processor.d.ts +29 -0
- package/dest/monitored_batch_span_processor.d.ts.map +1 -0
- package/dest/monitored_batch_span_processor.js +75 -0
- package/dest/nodejs_metrics_monitor.d.ts +3 -5
- package/dest/nodejs_metrics_monitor.d.ts.map +1 -1
- package/dest/nodejs_metrics_monitor.js +19 -40
- package/dest/noop.d.ts +6 -3
- package/dest/noop.d.ts.map +1 -1
- package/dest/noop.js +32 -1
- package/dest/otel.d.ts +10 -3
- package/dest/otel.d.ts.map +1 -1
- package/dest/otel.js +139 -9
- package/dest/otel_filter_metric_exporter.d.ts +1 -1
- package/dest/otel_filter_metric_exporter.d.ts.map +1 -1
- package/dest/otel_logger_provider.d.ts +1 -1
- package/dest/otel_propagation.d.ts +3 -1
- package/dest/otel_propagation.d.ts.map +1 -1
- package/dest/otel_propagation.js +49 -1
- package/dest/otel_resource.d.ts +1 -1
- package/dest/otel_resource.d.ts.map +1 -1
- package/dest/otel_resource.js +14 -2
- package/dest/prom_otel_adapter.d.ts +4 -4
- package/dest/prom_otel_adapter.d.ts.map +1 -1
- package/dest/prom_otel_adapter.js +19 -10
- package/dest/start.d.ts +3 -2
- package/dest/start.d.ts.map +1 -1
- package/dest/start.js +5 -4
- package/dest/telemetry.d.ts +38 -26
- package/dest/telemetry.d.ts.map +1 -1
- package/dest/telemetry.js +46 -23
- package/dest/vendor/attributes.d.ts +1 -1
- package/dest/vendor/otel-pino-stream.d.ts +1 -1
- package/dest/vendor/otel-pino-stream.d.ts.map +1 -1
- package/dest/with_tracer.d.ts +1 -1
- package/dest/with_tracer.d.ts.map +1 -1
- package/dest/wrappers/fetch.d.ts +3 -3
- package/dest/wrappers/fetch.d.ts.map +1 -1
- package/dest/wrappers/fetch.js +3 -2
- package/dest/wrappers/index.d.ts +1 -1
- package/dest/wrappers/json_rpc_server.d.ts +1 -1
- package/dest/wrappers/l2_block_stream.d.ts +4 -3
- package/dest/wrappers/l2_block_stream.d.ts.map +1 -1
- package/dest/wrappers/l2_block_stream.js +385 -11
- package/package.json +9 -6
- package/src/attributes.ts +32 -1
- package/src/bench.ts +27 -15
- package/src/config.ts +38 -10
- package/src/l1_metrics.ts +5 -20
- package/src/lmdb_metrics.ts +5 -26
- package/src/metric-utils.ts +75 -0
- package/src/metrics.ts +1682 -237
- package/src/monitored_batch_span_processor.ts +100 -0
- package/src/nodejs_metrics_monitor.ts +18 -51
- package/src/noop.ts +61 -3
- package/src/otel.ts +144 -11
- package/src/otel_propagation.ts +42 -1
- package/src/otel_resource.ts +19 -2
- package/src/prom_otel_adapter.ts +16 -23
- package/src/start.ts +14 -5
- package/src/telemetry.ts +105 -67
- package/src/wrappers/fetch.ts +9 -3
- package/src/wrappers/l2_block_stream.ts +3 -2
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
2
|
+
|
|
3
|
+
import { type Context, SpanStatusCode } from '@opentelemetry/api';
|
|
4
|
+
import { hrTimeToMilliseconds } from '@opentelemetry/core';
|
|
5
|
+
import type { SpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
6
|
+
import { BatchSpanProcessor, type BufferConfig, type ReadableSpan, type Span } from '@opentelemetry/sdk-trace-node';
|
|
7
|
+
|
|
8
|
+
/** Minimum interval between drop warnings to avoid log spam. */
|
|
9
|
+
const DROP_WARNING_INTERVAL_MS = 30_000;
|
|
10
|
+
|
|
11
|
+
const DEFAULT_MIN_TRACE_DURATION_MS = 10;
|
|
12
|
+
|
|
13
|
+
const DEFAULT_MAX_QUEUE_SIZE = 16384;
|
|
14
|
+
|
|
15
|
+
/** Cap on the per-export batch size, so a large queue can actually be drained instead of dribbling out
|
|
16
|
+
* at the SDK default of 512 spans per scheduled export. Kept <= maxQueueSize per the BatchSpanProcessor contract. */
|
|
17
|
+
const DEFAULT_MAX_EXPORT_BATCH_SIZE = 2048;
|
|
18
|
+
|
|
19
|
+
export type MonitoredBatchSpanProcessorConfig = BufferConfig & {
|
|
20
|
+
minTraceDurationMs?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wraps BatchSpanProcessor to emit warnings when spans are dropped due to a full queue.
|
|
25
|
+
* The standard BatchSpanProcessor silently discards spans when its internal queue reaches
|
|
26
|
+
* maxQueueSize, making telemetry data loss invisible to operators.
|
|
27
|
+
*/
|
|
28
|
+
export class MonitoredBatchSpanProcessor extends BatchSpanProcessor {
|
|
29
|
+
private readonly maxQueueSize: number;
|
|
30
|
+
private readonly minTraceDurationMs: number;
|
|
31
|
+
private readonly log: Logger;
|
|
32
|
+
|
|
33
|
+
private approxQueueSize = 0;
|
|
34
|
+
private droppedSinceLastWarning = 0;
|
|
35
|
+
private totalDropped = 0;
|
|
36
|
+
private lastWarningTime = 0;
|
|
37
|
+
|
|
38
|
+
constructor(exporter: SpanExporter, log: Logger, config?: MonitoredBatchSpanProcessorConfig) {
|
|
39
|
+
const maxQueueSize = config?.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
40
|
+
const maxExportBatchSize = Math.min(config?.maxExportBatchSize ?? DEFAULT_MAX_EXPORT_BATCH_SIZE, maxQueueSize);
|
|
41
|
+
super(exporter, { ...config, maxQueueSize, maxExportBatchSize });
|
|
42
|
+
this.maxQueueSize = maxQueueSize;
|
|
43
|
+
this.minTraceDurationMs = Math.max(0, config?.minTraceDurationMs ?? DEFAULT_MIN_TRACE_DURATION_MS);
|
|
44
|
+
this.log = log;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override onStart(span: Span, parentContext: Context): void {
|
|
48
|
+
super.onStart(span, parentContext);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override onEnd(span: ReadableSpan): void {
|
|
52
|
+
if (this.shouldDropShortSpan(span)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (this.approxQueueSize >= this.maxQueueSize) {
|
|
57
|
+
this.droppedSinceLastWarning++;
|
|
58
|
+
this.totalDropped++;
|
|
59
|
+
this.maybeLogDropWarning();
|
|
60
|
+
} else {
|
|
61
|
+
this.approxQueueSize++;
|
|
62
|
+
}
|
|
63
|
+
super.onEnd(span);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override async forceFlush(): Promise<void> {
|
|
67
|
+
await super.forceFlush();
|
|
68
|
+
this.approxQueueSize = 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override async shutdown(): Promise<void> {
|
|
72
|
+
if (this.totalDropped > 0) {
|
|
73
|
+
this.log.warn(`BatchSpanProcessor shutting down with ${this.totalDropped} total spans dropped`, {
|
|
74
|
+
totalDropped: this.totalDropped,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
await super.shutdown();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private shouldDropShortSpan(span: ReadableSpan): boolean {
|
|
81
|
+
return (
|
|
82
|
+
this.minTraceDurationMs > 0 &&
|
|
83
|
+
span.status.code !== SpanStatusCode.ERROR &&
|
|
84
|
+
hrTimeToMilliseconds(span.duration) < this.minTraceDurationMs
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private maybeLogDropWarning(): void {
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
if (now - this.lastWarningTime >= DROP_WARNING_INTERVAL_MS) {
|
|
91
|
+
this.log.warn(
|
|
92
|
+
`BatchSpanProcessor dropping spans: queue full (maxQueueSize=${this.maxQueueSize}). ` +
|
|
93
|
+
`${this.droppedSinceLastWarning} dropped since last warning, ${this.totalDropped} total.`,
|
|
94
|
+
{ droppedSinceLastWarning: this.droppedSinceLastWarning, totalDropped: this.totalDropped },
|
|
95
|
+
);
|
|
96
|
+
this.droppedSinceLastWarning = 0;
|
|
97
|
+
this.lastWarningTime = now;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -2,18 +2,11 @@ import type { Observable } from '@opentelemetry/api';
|
|
|
2
2
|
import { type EventLoopUtilization, type IntervalHistogram, monitorEventLoopDelay, performance } from 'node:perf_hooks';
|
|
3
3
|
|
|
4
4
|
import * as Attributes from './attributes.js';
|
|
5
|
+
import { createUpDownCounterWithDefault } from './metric-utils.js';
|
|
5
6
|
import * as Metrics from './metrics.js';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type ObservableGauge,
|
|
10
|
-
type UpDownCounter,
|
|
11
|
-
ValueType,
|
|
12
|
-
} from './telemetry.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Detector for custom Aztec attributes
|
|
16
|
-
*/
|
|
7
|
+
import type { BatchObservableResult, Meter, ObservableGauge, UpDownCounter } from './telemetry.js';
|
|
8
|
+
|
|
9
|
+
/** Monitors Node.js runtime metrics */
|
|
17
10
|
export class NodejsMetricsMonitor {
|
|
18
11
|
private eventLoopDelayGauges: {
|
|
19
12
|
min: ObservableGauge;
|
|
@@ -38,53 +31,27 @@ export class NodejsMetricsMonitor {
|
|
|
38
31
|
private eventLoopDelay: IntervalHistogram;
|
|
39
32
|
|
|
40
33
|
constructor(private meter: Meter) {
|
|
41
|
-
const nsObsGauge = (name: (typeof Metrics)[keyof typeof Metrics], description: string) =>
|
|
42
|
-
meter.createObservableGauge(name, {
|
|
43
|
-
unit: 'ns',
|
|
44
|
-
valueType: ValueType.INT,
|
|
45
|
-
description,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
34
|
this.eventLoopDelayGauges = {
|
|
49
|
-
min:
|
|
50
|
-
mean:
|
|
51
|
-
max:
|
|
52
|
-
stddev:
|
|
53
|
-
p50:
|
|
54
|
-
p90:
|
|
55
|
-
p99:
|
|
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),
|
|
56
42
|
};
|
|
57
43
|
|
|
58
|
-
this.eventLoopUilization = meter.createObservableGauge(Metrics.NODEJS_EVENT_LOOP_UTILIZATION
|
|
59
|
-
|
|
60
|
-
|
|
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'],
|
|
61
47
|
});
|
|
62
|
-
|
|
63
|
-
this.eventLoopTime = meter.createUpDownCounter(Metrics.NODEJS_EVENT_LOOP_TIME, {
|
|
64
|
-
unit: 'ms',
|
|
65
|
-
valueType: ValueType.INT,
|
|
66
|
-
description: 'How much time the event loop has spent in a given state',
|
|
67
|
-
});
|
|
68
|
-
|
|
69
48
|
this.eventLoopDelay = monitorEventLoopDelay();
|
|
70
49
|
|
|
71
50
|
this.memoryGauges = {
|
|
72
|
-
heapUsed: meter.createObservableGauge(Metrics.NODEJS_MEMORY_HEAP_USAGE,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
heapTotal: meter.createObservableGauge(Metrics.NODEJS_MEMORY_HEAP_TOTAL, {
|
|
77
|
-
unit: 'By',
|
|
78
|
-
description: 'The max size the V8 heap can grow to',
|
|
79
|
-
}),
|
|
80
|
-
arrayBuffers: meter.createObservableGauge(Metrics.NODEJS_MEMORY_BUFFER_USAGE, {
|
|
81
|
-
unit: 'By',
|
|
82
|
-
description: 'Memory allocated for buffers (includes native memory used)',
|
|
83
|
-
}),
|
|
84
|
-
external: meter.createObservableGauge(Metrics.NODEJS_MEMORY_NATIVE_USAGE, {
|
|
85
|
-
unit: 'By',
|
|
86
|
-
description: 'Memory allocated for native C++ objects',
|
|
87
|
-
}),
|
|
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),
|
|
88
55
|
};
|
|
89
56
|
}
|
|
90
57
|
|
package/src/noop.ts
CHANGED
|
@@ -1,13 +1,63 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Context, type Span, type SpanContext, type Tracer, createNoopMeter } from '@opentelemetry/api';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
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
|
+
|
|
6
56
|
setExportedPublicTelemetry(_prefixes: string[]): void {}
|
|
7
57
|
setPublicTelemetryCollectFrom(_roles: string[]): void {}
|
|
8
58
|
|
|
9
59
|
getMeter(): Meter {
|
|
10
|
-
return
|
|
60
|
+
return this.meter;
|
|
11
61
|
}
|
|
12
62
|
|
|
13
63
|
getTracer(): Tracer {
|
|
@@ -25,6 +75,14 @@ export class NoopTelemetryClient implements TelemetryClient {
|
|
|
25
75
|
isEnabled() {
|
|
26
76
|
return false;
|
|
27
77
|
}
|
|
78
|
+
|
|
79
|
+
getTraceContext(): string | undefined {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
extractPropagatedContext(_traceContext: string): Context | undefined {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
28
86
|
}
|
|
29
87
|
|
|
30
88
|
// @opentelemetry/api internally uses NoopTracer and NoopSpan but they're not exported
|
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';
|
|
@@ -24,24 +28,79 @@ import {
|
|
|
24
28
|
type PeriodicExportingMetricReaderOptions,
|
|
25
29
|
View,
|
|
26
30
|
} from '@opentelemetry/sdk-metrics';
|
|
27
|
-
import {
|
|
31
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
28
32
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
|
29
33
|
|
|
30
34
|
import type { TelemetryClientConfig } from './config.js';
|
|
35
|
+
import { toMetricOptions } from './metric-utils.js';
|
|
36
|
+
import type { MetricDefinition } from './metrics.js';
|
|
37
|
+
import { MonitoredBatchSpanProcessor } from './monitored_batch_span_processor.js';
|
|
31
38
|
import { NodejsMetricsMonitor } from './nodejs_metrics_monitor.js';
|
|
32
39
|
import { OtelFilterMetricExporter, PublicOtelFilterMetricExporter } from './otel_filter_metric_exporter.js';
|
|
33
40
|
import { registerOtelLoggerProvider } from './otel_logger_provider.js';
|
|
34
41
|
import { getOtelResource } from './otel_resource.js';
|
|
35
|
-
import type {
|
|
42
|
+
import type {
|
|
43
|
+
Gauge,
|
|
44
|
+
Histogram,
|
|
45
|
+
Meter,
|
|
46
|
+
ObservableGauge,
|
|
47
|
+
ObservableUpDownCounter,
|
|
48
|
+
TelemetryClient,
|
|
49
|
+
UpDownCounter,
|
|
50
|
+
} from './telemetry.js';
|
|
51
|
+
|
|
52
|
+
/** Wraps an OpenTelemetry Meter to implement our custom Meter interface */
|
|
53
|
+
class WrappedMeter implements Meter {
|
|
54
|
+
constructor(private otelMeter: OtelMeter) {}
|
|
55
|
+
|
|
56
|
+
createGauge(metric: MetricDefinition): Gauge {
|
|
57
|
+
return this.otelMeter.createGauge(metric.name, toMetricOptions(metric));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
createObservableGauge(metric: MetricDefinition): ObservableGauge {
|
|
61
|
+
return this.otelMeter.createObservableGauge(metric.name, toMetricOptions(metric));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
createHistogram(metric: MetricDefinition, extraOptions?: Parameters<Meter['createHistogram']>[1]): Histogram {
|
|
65
|
+
return this.otelMeter.createHistogram(metric.name, { ...toMetricOptions(metric), ...extraOptions });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
createUpDownCounter(metric: MetricDefinition): UpDownCounter {
|
|
69
|
+
return this.otelMeter.createUpDownCounter(metric.name, toMetricOptions(metric));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
createObservableUpDownCounter(metric: MetricDefinition): ObservableUpDownCounter {
|
|
73
|
+
return this.otelMeter.createObservableUpDownCounter(metric.name, toMetricOptions(metric));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
addBatchObservableCallback(
|
|
77
|
+
callback: Parameters<Meter['addBatchObservableCallback']>[0],
|
|
78
|
+
observables: Parameters<Meter['addBatchObservableCallback']>[1],
|
|
79
|
+
): void {
|
|
80
|
+
this.otelMeter.addBatchObservableCallback(callback, observables);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
removeBatchObservableCallback(
|
|
84
|
+
callback: Parameters<Meter['removeBatchObservableCallback']>[0],
|
|
85
|
+
observables: Parameters<Meter['removeBatchObservableCallback']>[1],
|
|
86
|
+
): void {
|
|
87
|
+
this.otelMeter.removeBatchObservableCallback(callback, observables);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
36
90
|
|
|
37
91
|
export type OpenTelemetryClientFactory = (resource: IResource, log: Logger) => OpenTelemetryClient;
|
|
38
92
|
|
|
39
93
|
export class OpenTelemetryClient implements TelemetryClient {
|
|
40
94
|
hostMetrics: HostMetrics | undefined;
|
|
41
95
|
nodejsMetricsMonitor: NodejsMetricsMonitor | undefined;
|
|
42
|
-
private meters: Map<string,
|
|
96
|
+
private meters: Map<string, WrappedMeter> = new Map<string, WrappedMeter>();
|
|
43
97
|
private tracers: Map<string, Tracer> = new Map<string, Tracer>();
|
|
44
98
|
|
|
99
|
+
/** Memoized shutdown promise. The telemetry client is shared between the aztec-node and an embedded prover-node,
|
|
100
|
+
* so stop() can be invoked more than once; the providers throw "shutdown may only be called once" and
|
|
101
|
+
* "invalid attempt to force flush after shutdown" if that happens. Guarding here makes stop()/flush() idempotent. */
|
|
102
|
+
private stopPromise: Promise<void> | undefined;
|
|
103
|
+
|
|
45
104
|
protected constructor(
|
|
46
105
|
private resource: IResource,
|
|
47
106
|
private meterProvider: MeterProvider,
|
|
@@ -62,7 +121,8 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
62
121
|
getMeter(name: string): Meter {
|
|
63
122
|
let meter = this.meters.get(name);
|
|
64
123
|
if (!meter) {
|
|
65
|
-
|
|
124
|
+
const otelMeter = this.meterProvider.getMeter(name, this.resource.attributes[ATTR_SERVICE_VERSION] as string);
|
|
125
|
+
meter = new WrappedMeter(otelMeter);
|
|
66
126
|
this.meters.set(name, meter);
|
|
67
127
|
}
|
|
68
128
|
return meter;
|
|
@@ -100,9 +160,10 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
100
160
|
meterProvider: this.meterProvider,
|
|
101
161
|
});
|
|
102
162
|
|
|
103
|
-
|
|
163
|
+
const nodejsMeter = new WrappedMeter(
|
|
104
164
|
this.meterProvider.getMeter(this.resource.attributes[ATTR_SERVICE_NAME] as string),
|
|
105
165
|
);
|
|
166
|
+
this.nodejsMetricsMonitor = new NodejsMetricsMonitor(nodejsMeter);
|
|
106
167
|
|
|
107
168
|
this.hostMetrics.start();
|
|
108
169
|
this.nodejsMetricsMonitor.start();
|
|
@@ -113,6 +174,10 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
113
174
|
}
|
|
114
175
|
|
|
115
176
|
public async flush() {
|
|
177
|
+
// Flushing after the providers have been shut down throws "invalid attempt to force flush after shutdown".
|
|
178
|
+
if (this.stopPromise) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
116
181
|
await Promise.all([
|
|
117
182
|
this.meterProvider.forceFlush(),
|
|
118
183
|
this.loggerProvider?.forceFlush(),
|
|
@@ -120,7 +185,11 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
120
185
|
]);
|
|
121
186
|
}
|
|
122
187
|
|
|
123
|
-
public
|
|
188
|
+
public stop() {
|
|
189
|
+
return (this.stopPromise ??= this.doStop());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async doStop() {
|
|
124
193
|
this.nodejsMetricsMonitor?.stop();
|
|
125
194
|
|
|
126
195
|
const flushAndShutdown = async (provider?: { forceFlush: () => Promise<void>; shutdown: () => Promise<void> }) => {
|
|
@@ -138,6 +207,19 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
138
207
|
]);
|
|
139
208
|
}
|
|
140
209
|
|
|
210
|
+
public getTraceContext(): string | undefined {
|
|
211
|
+
const carrier: Record<string, string> = {};
|
|
212
|
+
propagation.inject(context.active(), carrier);
|
|
213
|
+
return carrier['traceparent'];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public extractPropagatedContext(traceContext: string): Context {
|
|
217
|
+
const extractedContext = propagation.extract(ROOT_CONTEXT, {
|
|
218
|
+
traceparent: traceContext,
|
|
219
|
+
});
|
|
220
|
+
return extractedContext;
|
|
221
|
+
}
|
|
222
|
+
|
|
141
223
|
public static createMeterProvider(
|
|
142
224
|
resource: IResource,
|
|
143
225
|
exporters: Array<PeriodicExportingMetricReaderOptions>,
|
|
@@ -266,6 +348,36 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
266
348
|
true,
|
|
267
349
|
),
|
|
268
350
|
}),
|
|
351
|
+
// L1 gas prices in gwei: priority fees ~0.01-10, base fees ~1-500, spikes to 1000+
|
|
352
|
+
new View({
|
|
353
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
354
|
+
instrumentUnit: 'gwei',
|
|
355
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
356
|
+
[0.1, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1_000],
|
|
357
|
+
true,
|
|
358
|
+
),
|
|
359
|
+
}),
|
|
360
|
+
// L1 gas consumption: tx gas 100k-30M, calldata/blob gas varies
|
|
361
|
+
new View({
|
|
362
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
363
|
+
instrumentUnit: 'gas',
|
|
364
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
365
|
+
[
|
|
366
|
+
10_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, 2_000_000, 5_000_000, 10_000_000, 15_000_000,
|
|
367
|
+
30_000_000,
|
|
368
|
+
],
|
|
369
|
+
true,
|
|
370
|
+
),
|
|
371
|
+
}),
|
|
372
|
+
// L1 tx total fee in ETH: typically 0.001 - 1 ETH
|
|
373
|
+
new View({
|
|
374
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
375
|
+
instrumentUnit: 'eth',
|
|
376
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
377
|
+
[0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10],
|
|
378
|
+
true,
|
|
379
|
+
),
|
|
380
|
+
}),
|
|
269
381
|
],
|
|
270
382
|
});
|
|
271
383
|
}
|
|
@@ -275,19 +387,40 @@ export class OpenTelemetryClient implements TelemetryClient {
|
|
|
275
387
|
const tracerProvider = new NodeTracerProvider({
|
|
276
388
|
resource,
|
|
277
389
|
spanProcessors: config.tracesCollectorUrl
|
|
278
|
-
? [
|
|
390
|
+
? [
|
|
391
|
+
new MonitoredBatchSpanProcessor(new OTLPTraceExporter({ url: config.tracesCollectorUrl.href }), log, {
|
|
392
|
+
maxQueueSize: config.otelBspMaxQueueSize,
|
|
393
|
+
minTraceDurationMs: config.otelMinTraceDurationMs,
|
|
394
|
+
}),
|
|
395
|
+
]
|
|
279
396
|
: [],
|
|
280
397
|
});
|
|
281
398
|
|
|
282
|
-
tracerProvider.register(
|
|
399
|
+
tracerProvider.register({
|
|
400
|
+
propagator: new W3CTraceContextPropagator(),
|
|
401
|
+
});
|
|
283
402
|
|
|
284
403
|
const exporters: PeriodicExportingMetricReaderOptions[] = [];
|
|
285
404
|
if (config.metricsCollectorUrl) {
|
|
405
|
+
// Default to a blacklist that is empty (allow all metrics)
|
|
406
|
+
let filter: string[] = [];
|
|
407
|
+
let mode: 'allow' | 'deny' = 'deny';
|
|
408
|
+
if (config.otelExcludeMetrics.length > 0) {
|
|
409
|
+
// Implement a blacklist as specified in config
|
|
410
|
+
log.info(`Excluding metrics from export: ${config.otelExcludeMetrics}`);
|
|
411
|
+
filter = config.otelExcludeMetrics;
|
|
412
|
+
mode = 'deny';
|
|
413
|
+
} else if (config.otelIncludeMetrics.length > 0) {
|
|
414
|
+
// Implement a whitelist as specified in config
|
|
415
|
+
log.info(`Including only specified metrics for export: ${config.otelIncludeMetrics}`);
|
|
416
|
+
filter = config.otelIncludeMetrics;
|
|
417
|
+
mode = 'allow';
|
|
418
|
+
}
|
|
286
419
|
exporters.push({
|
|
287
420
|
exporter: new OtelFilterMetricExporter(
|
|
288
421
|
new OTLPMetricExporter({ url: config.metricsCollectorUrl.href }),
|
|
289
|
-
|
|
290
|
-
|
|
422
|
+
filter,
|
|
423
|
+
mode,
|
|
291
424
|
),
|
|
292
425
|
exportTimeoutMillis: config.otelExportTimeoutMs,
|
|
293
426
|
exportIntervalMillis: config.otelCollectIntervalMs,
|
package/src/otel_propagation.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { DiagnosticsMiddleware } from '@aztec/foundation/json-rpc/server';
|
|
2
|
+
|
|
1
3
|
import { ROOT_CONTEXT, type Span, SpanKind, SpanStatusCode, propagation } from '@opentelemetry/api';
|
|
2
4
|
import type Koa from 'koa';
|
|
3
5
|
|
|
@@ -17,7 +19,7 @@ export function getOtelJsonRpcPropagationMiddleware(
|
|
|
17
19
|
const context = propagation.extract(ROOT_CONTEXT, ctx.request.headers);
|
|
18
20
|
const method = (ctx.request.body as any)?.method;
|
|
19
21
|
return tracer.startActiveSpan(
|
|
20
|
-
`JsonRpcServer.${method ?? '
|
|
22
|
+
`JsonRpcServer.${method ?? 'batch'}`,
|
|
21
23
|
{ kind: SpanKind.SERVER },
|
|
22
24
|
context,
|
|
23
25
|
async (span: Span): Promise<void> => {
|
|
@@ -48,3 +50,42 @@ export function getOtelJsonRpcPropagationMiddleware(
|
|
|
48
50
|
);
|
|
49
51
|
};
|
|
50
52
|
}
|
|
53
|
+
|
|
54
|
+
export function getOtelJsonRpcDiagnosticsMiddleware(): DiagnosticsMiddleware {
|
|
55
|
+
return function otelJsonRpcDiagnostics(ctx, next) {
|
|
56
|
+
const [namespace, method] = splitNamespace(ctx.method);
|
|
57
|
+
const scope = namespace ?? 'UnknownHandler';
|
|
58
|
+
const tracer = getTelemetryClient().getTracer(scope);
|
|
59
|
+
return tracer.startActiveSpan(
|
|
60
|
+
`${scope}.${method}`,
|
|
61
|
+
{ kind: SpanKind.INTERNAL, attributes: { [ATTR_JSONRPC_METHOD]: ctx.method } },
|
|
62
|
+
async span => {
|
|
63
|
+
if (ctx.id !== null) {
|
|
64
|
+
span.setAttribute(ATTR_JSONRPC_REQUEST_ID, ctx.id);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await next();
|
|
69
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
70
|
+
} catch (err) {
|
|
71
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err instanceof Error ? err.message : String(err) });
|
|
72
|
+
if (typeof err === 'string' || err instanceof Error) {
|
|
73
|
+
span.recordException(err);
|
|
74
|
+
}
|
|
75
|
+
throw err;
|
|
76
|
+
} finally {
|
|
77
|
+
span.end();
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function splitNamespace(fullMethod: string): [namespace: string | undefined, method: string] {
|
|
85
|
+
const idx = fullMethod.indexOf('_');
|
|
86
|
+
if (idx > -1) {
|
|
87
|
+
return [fullMethod.slice(0, idx), fullMethod.slice(idx + 1)];
|
|
88
|
+
} else {
|
|
89
|
+
return [undefined, fullMethod];
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/otel_resource.ts
CHANGED
|
@@ -7,10 +7,26 @@ import {
|
|
|
7
7
|
osDetectorSync,
|
|
8
8
|
serviceInstanceIdDetectorSync,
|
|
9
9
|
} from '@opentelemetry/resources';
|
|
10
|
-
import {
|
|
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';
|
|
11
14
|
|
|
12
15
|
import { AZTEC_NODE_ROLE, AZTEC_REGISTRY_ADDRESS, AZTEC_ROLLUP_ADDRESS, AZTEC_ROLLUP_VERSION } from './attributes.js';
|
|
13
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
|
+
|
|
14
30
|
export function getOtelResource(): IResource {
|
|
15
31
|
const resource = detectResourcesSync({
|
|
16
32
|
detectors: [
|
|
@@ -42,7 +58,8 @@ const aztecNetworkDetectorSync: DetectorSync = {
|
|
|
42
58
|
}
|
|
43
59
|
const aztecAttributes = {
|
|
44
60
|
// this gets overwritten by OTEL_RESOURCE_ATTRIBUTES (if set)
|
|
45
|
-
[
|
|
61
|
+
[ATTR_SERVICE_NAME]: role ? `aztec-${role}` : undefined,
|
|
62
|
+
[ATTR_SERVICE_VERSION]: getAztecVersion(),
|
|
46
63
|
[AZTEC_NODE_ROLE]: role,
|
|
47
64
|
[AZTEC_ROLLUP_VERSION]: process.env.ROLLUP_VERSION ?? 'canonical',
|
|
48
65
|
[AZTEC_ROLLUP_ADDRESS]: process.env.ROLLUP_CONTRACT_ADDRESS,
|