@aztec/telemetry-client 0.75.0-commit.c03ba01a2a4122e43e90d5133ba017e54b90e9d2
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.js +66 -0
- package/dest/aztec_resource_detector.js +18 -0
- package/dest/bench.js +98 -0
- package/dest/config.js +66 -0
- package/dest/event_loop_monitor.js +93 -0
- package/dest/histogram_utils.js +47 -0
- package/dest/index.js +10 -0
- package/dest/lmdb_metrics.js +42 -0
- package/dest/metrics.js +123 -0
- package/dest/noop.js +71 -0
- package/dest/otel.js +237 -0
- package/dest/otel_filter_metric_exporter.js +33 -0
- package/dest/otel_logger_provider.js +25 -0
- package/dest/otel_propagation.js +44 -0
- package/dest/otel_resource.js +16 -0
- package/dest/prom_otel_adapter.js +183 -0
- package/dest/start.js +24 -0
- package/dest/telemetry.js +116 -0
- package/dest/vendor/attributes.js +5 -0
- package/dest/vendor/otel-pino-stream.js +229 -0
- package/dest/with_tracer.js +32 -0
- package/dest/wrappers/fetch.js +39 -0
- package/dest/wrappers/index.js +3 -0
- package/dest/wrappers/json_rpc_server.js +11 -0
- package/dest/wrappers/l2_block_stream.js +26 -0
- package/package.json +92 -0
- package/src/attributes.ts +113 -0
- package/src/aztec_resource_detector.ts +28 -0
- package/src/bench.ts +153 -0
- package/src/config.ts +89 -0
- package/src/event_loop_monitor.ts +119 -0
- package/src/histogram_utils.ts +50 -0
- package/src/index.ts +10 -0
- package/src/lmdb_metrics.ts +45 -0
- package/src/metrics.ts +151 -0
- package/src/noop.ts +91 -0
- package/src/otel.ts +299 -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 +26 -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
package/src/noop.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type Meter, type Span, type SpanContext, type Tracer, createNoopMeter } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
import { type TelemetryClient } from './telemetry.js';
|
|
4
|
+
|
|
5
|
+
export class NoopTelemetryClient implements TelemetryClient {
|
|
6
|
+
getMeter(): Meter {
|
|
7
|
+
return createNoopMeter();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
getTracer(): Tracer {
|
|
11
|
+
return new NoopTracer();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
stop(): Promise<void> {
|
|
15
|
+
return Promise.resolve();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
flush(): Promise<void> {
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
isEnabled() {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// @opentelemetry/api internally uses NoopTracer and NoopSpan but they're not exported
|
|
28
|
+
// make our own versions
|
|
29
|
+
// https://github.com/open-telemetry/opentelemetry-js/issues/4518#issuecomment-2179405444
|
|
30
|
+
export class NoopTracer implements Tracer {
|
|
31
|
+
startSpan(): Span {
|
|
32
|
+
return new NoopSpan();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
startActiveSpan<F extends (...args: any[]) => any>(_name: string, ...args: (unknown | F)[]): ReturnType<F> {
|
|
36
|
+
// there are three different signatures for startActiveSpan, grab the function, we don't care about the rest
|
|
37
|
+
const fn = args.find(arg => typeof arg === 'function') as F;
|
|
38
|
+
return fn(new NoopSpan());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class NoopSpan implements Span {
|
|
43
|
+
private recording: boolean = true;
|
|
44
|
+
addEvent(): this {
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
addLink(): this {
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
addLinks(): this {
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
end(): void {
|
|
57
|
+
this.recording = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
isRecording(): boolean {
|
|
61
|
+
return this.recording;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
recordException(): void {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setAttribute(): this {
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setAttributes(): this {
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setStatus(): this {
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
spanContext(): SpanContext {
|
|
81
|
+
return {
|
|
82
|
+
spanId: '',
|
|
83
|
+
traceId: '',
|
|
84
|
+
traceFlags: 0,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
updateName(): this {
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/otel.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { type LogData, type Logger, addLogDataHandler } from '@aztec/foundation/log';
|
|
2
|
+
|
|
3
|
+
import { MetricExporter as GoogleCloudMetricExporter } from '@google-cloud/opentelemetry-cloud-monitoring-exporter';
|
|
4
|
+
import { TraceExporter as GoogleCloudTraceExporter } from '@google-cloud/opentelemetry-cloud-trace-exporter';
|
|
5
|
+
import {
|
|
6
|
+
DiagConsoleLogger,
|
|
7
|
+
DiagLogLevel,
|
|
8
|
+
type Meter,
|
|
9
|
+
type Tracer,
|
|
10
|
+
type TracerProvider,
|
|
11
|
+
context,
|
|
12
|
+
diag,
|
|
13
|
+
isSpanContextValid,
|
|
14
|
+
trace,
|
|
15
|
+
} from '@opentelemetry/api';
|
|
16
|
+
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
17
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
18
|
+
import { HostMetrics } from '@opentelemetry/host-metrics';
|
|
19
|
+
import { type IResource } from '@opentelemetry/resources';
|
|
20
|
+
import { type LoggerProvider } from '@opentelemetry/sdk-logs';
|
|
21
|
+
import {
|
|
22
|
+
ExplicitBucketHistogramAggregation,
|
|
23
|
+
InstrumentType,
|
|
24
|
+
MeterProvider,
|
|
25
|
+
PeriodicExportingMetricReader,
|
|
26
|
+
type PeriodicExportingMetricReaderOptions,
|
|
27
|
+
View,
|
|
28
|
+
} from '@opentelemetry/sdk-metrics';
|
|
29
|
+
import { BatchSpanProcessor, NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
30
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
|
31
|
+
|
|
32
|
+
import { type TelemetryClientConfig } from './config.js';
|
|
33
|
+
import { EventLoopMonitor } from './event_loop_monitor.js';
|
|
34
|
+
import { linearBuckets } from './histogram_utils.js';
|
|
35
|
+
import { OtelFilterMetricExporter } from './otel_filter_metric_exporter.js';
|
|
36
|
+
import { registerOtelLoggerProvider } from './otel_logger_provider.js';
|
|
37
|
+
import { getOtelResource } from './otel_resource.js';
|
|
38
|
+
import { type Gauge, type TelemetryClient } from './telemetry.js';
|
|
39
|
+
|
|
40
|
+
export type OpenTelemetryClientFactory = (resource: IResource, log: Logger) => OpenTelemetryClient;
|
|
41
|
+
|
|
42
|
+
export class OpenTelemetryClient implements TelemetryClient {
|
|
43
|
+
hostMetrics: HostMetrics | undefined;
|
|
44
|
+
eventLoopMonitor: EventLoopMonitor | undefined;
|
|
45
|
+
targetInfo: Gauge | undefined;
|
|
46
|
+
private meters: Map<string, Meter> = new Map<string, Meter>();
|
|
47
|
+
private tracers: Map<string, Tracer> = new Map<string, Tracer>();
|
|
48
|
+
|
|
49
|
+
protected constructor(
|
|
50
|
+
private resource: IResource,
|
|
51
|
+
private meterProvider: MeterProvider,
|
|
52
|
+
private traceProvider: TracerProvider,
|
|
53
|
+
private loggerProvider: LoggerProvider | undefined,
|
|
54
|
+
private log: Logger,
|
|
55
|
+
) {}
|
|
56
|
+
|
|
57
|
+
getMeter(name: string): Meter {
|
|
58
|
+
let meter = this.meters.get(name);
|
|
59
|
+
if (!meter) {
|
|
60
|
+
meter = this.meterProvider.getMeter(name, this.resource.attributes[ATTR_SERVICE_VERSION] as string);
|
|
61
|
+
this.meters.set(name, meter);
|
|
62
|
+
}
|
|
63
|
+
return meter;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getTracer(name: string): Tracer {
|
|
67
|
+
let tracer = this.tracers.get(name);
|
|
68
|
+
if (!tracer) {
|
|
69
|
+
tracer = this.traceProvider.getTracer(name, this.resource.attributes[ATTR_SERVICE_VERSION] as string);
|
|
70
|
+
this.tracers.set(name, tracer);
|
|
71
|
+
}
|
|
72
|
+
return tracer;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public start() {
|
|
76
|
+
this.log.info('Starting OpenTelemetry client');
|
|
77
|
+
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
|
|
78
|
+
|
|
79
|
+
// Add a callback to the logger to set context data from current trace
|
|
80
|
+
// Adapted from open-telemetry/opentelemetry-js-contrib PinoInstrumentation._getMixinFunction
|
|
81
|
+
addLogDataHandler((data: LogData) => {
|
|
82
|
+
const spanContext = trace.getSpan(context.active())?.spanContext();
|
|
83
|
+
return spanContext && isSpanContextValid(spanContext)
|
|
84
|
+
? {
|
|
85
|
+
...data,
|
|
86
|
+
['trace_id']: spanContext.traceId,
|
|
87
|
+
['span_id']: spanContext.spanId,
|
|
88
|
+
['trace_flags']: `0${spanContext.traceFlags.toString(16)}`,
|
|
89
|
+
}
|
|
90
|
+
: data;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.hostMetrics = new HostMetrics({
|
|
94
|
+
name: this.resource.attributes[ATTR_SERVICE_NAME] as string,
|
|
95
|
+
meterProvider: this.meterProvider,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
this.eventLoopMonitor = new EventLoopMonitor(
|
|
99
|
+
this.meterProvider.getMeter(this.resource.attributes[ATTR_SERVICE_NAME] as string),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// See these two links for more information on providing target information:
|
|
103
|
+
// https://opentelemetry.io/docs/specs/otel/compatibility/prometheus_and_openmetrics/#resource-attributes
|
|
104
|
+
// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#supporting-target-metadata-in-both-push-based-and-pull-based-systems
|
|
105
|
+
this.targetInfo = this.meterProvider.getMeter('target').createGauge('target_info', {
|
|
106
|
+
description: 'Target metadata',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.targetInfo.record(1, this.resource.attributes);
|
|
110
|
+
this.hostMetrics.start();
|
|
111
|
+
this.eventLoopMonitor.start();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public isEnabled() {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public async flush() {
|
|
119
|
+
await Promise.all([
|
|
120
|
+
this.meterProvider.forceFlush(),
|
|
121
|
+
this.loggerProvider?.forceFlush(),
|
|
122
|
+
this.traceProvider instanceof NodeTracerProvider ? this.traceProvider.forceFlush() : Promise.resolve(),
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public async stop() {
|
|
127
|
+
this.eventLoopMonitor?.stop();
|
|
128
|
+
|
|
129
|
+
const flushAndShutdown = async (provider?: { forceFlush: () => Promise<void>; shutdown: () => Promise<void> }) => {
|
|
130
|
+
if (!provider) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
await provider.forceFlush();
|
|
134
|
+
await provider.shutdown();
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
await Promise.all([
|
|
138
|
+
flushAndShutdown(this.meterProvider),
|
|
139
|
+
flushAndShutdown(this.loggerProvider),
|
|
140
|
+
this.traceProvider instanceof NodeTracerProvider ? flushAndShutdown(this.traceProvider) : Promise.resolve(),
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public static createMeterProvider(
|
|
145
|
+
resource: IResource,
|
|
146
|
+
options: Partial<PeriodicExportingMetricReaderOptions>,
|
|
147
|
+
): MeterProvider {
|
|
148
|
+
return new MeterProvider({
|
|
149
|
+
resource,
|
|
150
|
+
readers: options.exporter
|
|
151
|
+
? [new PeriodicExportingMetricReader(options as PeriodicExportingMetricReaderOptions)]
|
|
152
|
+
: [],
|
|
153
|
+
|
|
154
|
+
views: [
|
|
155
|
+
// Every histogram matching the selector (type + unit) gets these custom buckets assigned
|
|
156
|
+
new View({
|
|
157
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
158
|
+
instrumentUnit: 'ms',
|
|
159
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
160
|
+
// 181 buckets between 5ms and 1hr
|
|
161
|
+
[
|
|
162
|
+
...linearBuckets(5, 100, 20), // 20 buckets between 5 and 100ms
|
|
163
|
+
...linearBuckets(100, 1_000, 20).slice(1), // another 20 buckets between 100ms and 1s. slice(1) to remove duplicate 100
|
|
164
|
+
...linearBuckets(1_000, 10_000, 20).slice(1),
|
|
165
|
+
...linearBuckets(10_000, 60_000, 20).slice(1),
|
|
166
|
+
...linearBuckets(60_000, 300_000, 20).slice(1),
|
|
167
|
+
...linearBuckets(300_000, 600_000, 20).slice(1),
|
|
168
|
+
...linearBuckets(600_000, 1_200_000, 20).slice(1),
|
|
169
|
+
...linearBuckets(1_200_000, 1_800_000, 20).slice(1),
|
|
170
|
+
...linearBuckets(1_800_000, 3_600_000, 20).slice(1), // 1hr
|
|
171
|
+
],
|
|
172
|
+
true,
|
|
173
|
+
),
|
|
174
|
+
}),
|
|
175
|
+
new View({
|
|
176
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
177
|
+
instrumentUnit: 'us',
|
|
178
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
179
|
+
[
|
|
180
|
+
...linearBuckets(5, 100, 20), // 20 buckets between 5 and 100us
|
|
181
|
+
...linearBuckets(100, 1_000, 20).slice(1), // another 20 buckets between 100us and 1ms. slice(1) to remove duplicate 100
|
|
182
|
+
...linearBuckets(1_000, 10_000, 90).slice(1), // 90 buckets between 1ms and 10ms
|
|
183
|
+
...linearBuckets(10_000, 100_000, 20).slice(1), // 20 buckets between 10ms and 100ms
|
|
184
|
+
...linearBuckets(100_000, 1_000_000, 20).slice(1), // 20 buckets between 100ms and 1s
|
|
185
|
+
...linearBuckets(1_000_000, 60_000_000, 20).slice(1), // 20 buckets between 1s and 1m
|
|
186
|
+
],
|
|
187
|
+
true,
|
|
188
|
+
),
|
|
189
|
+
}),
|
|
190
|
+
new View({
|
|
191
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
192
|
+
instrumentUnit: 'By',
|
|
193
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
194
|
+
// 143 buckets between 32 bytes and 2MB
|
|
195
|
+
[
|
|
196
|
+
...linearBuckets(2 ** 5, 2 ** 10, 31), // 32 bytes to 1KB at a resolution of 32 bytes
|
|
197
|
+
...linearBuckets(2 ** 10, 2 ** 15, 31).slice(1), // 1KB to 32KB at a resolution of 1KB
|
|
198
|
+
...linearBuckets(2 ** 15, 2 ** 18, 32).slice(1), // 32KB to 256KB at a resolution of 7KB
|
|
199
|
+
...linearBuckets(2 ** 18, 2 ** 20, 32).slice(1), // 256KB to 1MB at a resolution of 24KB
|
|
200
|
+
...linearBuckets(2 ** 20, 2 ** 21, 16).slice(1), // 1MB to 2MB at a resolution of 64KB
|
|
201
|
+
],
|
|
202
|
+
true,
|
|
203
|
+
),
|
|
204
|
+
}),
|
|
205
|
+
new View({
|
|
206
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
207
|
+
instrumentUnit: 'gas/s',
|
|
208
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
209
|
+
[...linearBuckets(100_000, 10_000_000, 100), ...linearBuckets(10_000_000, 100_000_000, 100).slice(1)],
|
|
210
|
+
true,
|
|
211
|
+
),
|
|
212
|
+
}),
|
|
213
|
+
new View({
|
|
214
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
215
|
+
instrumentUnit: 'mana/s',
|
|
216
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
217
|
+
[...linearBuckets(100_000, 10_000_000, 100), ...linearBuckets(10_000_000, 100_000_000, 100).slice(1)],
|
|
218
|
+
true,
|
|
219
|
+
),
|
|
220
|
+
}),
|
|
221
|
+
new View({
|
|
222
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
223
|
+
instrumentUnit: 'gas/block',
|
|
224
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
225
|
+
[...linearBuckets(100_000, 10_000_000, 100), ...linearBuckets(10_000_000, 50_000_000, 50).slice(1)],
|
|
226
|
+
true,
|
|
227
|
+
),
|
|
228
|
+
}),
|
|
229
|
+
new View({
|
|
230
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
231
|
+
instrumentUnit: 'gas/tx',
|
|
232
|
+
aggregation: new ExplicitBucketHistogramAggregation(
|
|
233
|
+
[...linearBuckets(50_000, 1_000_000, 20), ...linearBuckets(1_000_000, 10_000_000, 100).slice(1)],
|
|
234
|
+
true,
|
|
235
|
+
),
|
|
236
|
+
}),
|
|
237
|
+
],
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private static getGcloudClientFactory(config: TelemetryClientConfig): OpenTelemetryClientFactory {
|
|
242
|
+
return (resource: IResource, log: Logger) => {
|
|
243
|
+
const tracerProvider = new NodeTracerProvider({
|
|
244
|
+
resource,
|
|
245
|
+
spanProcessors: [new BatchSpanProcessor(new GoogleCloudTraceExporter({ resourceFilter: /.*/ }))],
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
tracerProvider.register();
|
|
249
|
+
|
|
250
|
+
const meterProvider = OpenTelemetryClient.createMeterProvider(resource, {
|
|
251
|
+
exporter: new OtelFilterMetricExporter(new GoogleCloudMetricExporter(), config.otelExcludeMetrics ?? []),
|
|
252
|
+
exportTimeoutMillis: config.otelExportTimeoutMs,
|
|
253
|
+
exportIntervalMillis: config.otelCollectIntervalMs,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return new OpenTelemetryClient(resource, meterProvider, tracerProvider, undefined, log);
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private static getCustomClientFactory(config: TelemetryClientConfig): OpenTelemetryClientFactory {
|
|
261
|
+
return (resource: IResource, log: Logger) => {
|
|
262
|
+
const tracerProvider = new NodeTracerProvider({
|
|
263
|
+
resource,
|
|
264
|
+
spanProcessors: config.tracesCollectorUrl
|
|
265
|
+
? [new BatchSpanProcessor(new OTLPTraceExporter({ url: config.tracesCollectorUrl.href }))]
|
|
266
|
+
: [],
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
tracerProvider.register();
|
|
270
|
+
|
|
271
|
+
const meterProvider = OpenTelemetryClient.createMeterProvider(resource, {
|
|
272
|
+
exporter: config.metricsCollectorUrl
|
|
273
|
+
? new OtelFilterMetricExporter(
|
|
274
|
+
new OTLPMetricExporter({ url: config.metricsCollectorUrl.href }),
|
|
275
|
+
config.otelExcludeMetrics ?? [],
|
|
276
|
+
)
|
|
277
|
+
: undefined,
|
|
278
|
+
exportTimeoutMillis: config.otelExportTimeoutMs,
|
|
279
|
+
exportIntervalMillis: config.otelCollectIntervalMs,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const loggerProvider = registerOtelLoggerProvider(resource, config.logsCollectorUrl);
|
|
283
|
+
|
|
284
|
+
return new OpenTelemetryClient(resource, meterProvider, tracerProvider, loggerProvider, log);
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public static createAndStart(config: TelemetryClientConfig, log: Logger): OpenTelemetryClient {
|
|
289
|
+
const resource = getOtelResource();
|
|
290
|
+
const factory = config.useGcloudObservability
|
|
291
|
+
? OpenTelemetryClient.getGcloudClientFactory(config)
|
|
292
|
+
: OpenTelemetryClient.getCustomClientFactory(config);
|
|
293
|
+
|
|
294
|
+
const service = factory(resource, log);
|
|
295
|
+
service.start();
|
|
296
|
+
|
|
297
|
+
return service;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ExportResult } from '@opentelemetry/core';
|
|
2
|
+
import { type MetricData, type PushMetricExporter, type ResourceMetrics } from '@opentelemetry/sdk-metrics';
|
|
3
|
+
|
|
4
|
+
export class OtelFilterMetricExporter implements PushMetricExporter {
|
|
5
|
+
constructor(private readonly exporter: PushMetricExporter, private readonly excludeMetricPrefixes: string[]) {
|
|
6
|
+
if (exporter.selectAggregation) {
|
|
7
|
+
(this as PushMetricExporter).selectAggregation = exporter.selectAggregation.bind(exporter);
|
|
8
|
+
}
|
|
9
|
+
if (exporter.selectAggregationTemporality) {
|
|
10
|
+
(this as PushMetricExporter).selectAggregationTemporality = exporter.selectAggregationTemporality.bind(exporter);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void {
|
|
15
|
+
const filteredMetrics: ResourceMetrics = {
|
|
16
|
+
resource: metrics.resource,
|
|
17
|
+
scopeMetrics: metrics.scopeMetrics
|
|
18
|
+
.map(({ scope, metrics }) => ({ scope, metrics: this.filterMetrics(metrics) }))
|
|
19
|
+
.filter(({ metrics }) => metrics.length > 0),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
this.exporter.export(filteredMetrics, resultCallback);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private filterMetrics(metrics: MetricData[]): MetricData[] {
|
|
26
|
+
return metrics.filter(
|
|
27
|
+
metric => !this.excludeMetricPrefixes.some(prefix => metric.descriptor.name.startsWith(prefix)),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public forceFlush(): Promise<void> {
|
|
32
|
+
return this.exporter.forceFlush();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public shutdown(): Promise<void> {
|
|
36
|
+
return this.exporter.shutdown();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { logs as otelLogs } from '@opentelemetry/api-logs';
|
|
2
|
+
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
|
|
3
|
+
import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base';
|
|
4
|
+
import { type IResource } from '@opentelemetry/resources';
|
|
5
|
+
import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';
|
|
6
|
+
|
|
7
|
+
export function registerOtelLoggerProvider(resource: IResource, otelLogsUrl?: URL) {
|
|
8
|
+
const loggerProvider = new LoggerProvider({ resource });
|
|
9
|
+
if (!otelLogsUrl) {
|
|
10
|
+
// If no URL provided, return it disconnected.
|
|
11
|
+
return loggerProvider;
|
|
12
|
+
}
|
|
13
|
+
const logExporter = new OTLPLogExporter({
|
|
14
|
+
compression: CompressionAlgorithm.GZIP,
|
|
15
|
+
url: otelLogsUrl.href,
|
|
16
|
+
});
|
|
17
|
+
// Add a processor to the logger provider
|
|
18
|
+
loggerProvider.addLogRecordProcessor(
|
|
19
|
+
new BatchLogRecordProcessor(logExporter, {
|
|
20
|
+
/** The maximum batch size of every export. It must be smaller or equal to
|
|
21
|
+
* maxQueueSize. */
|
|
22
|
+
maxExportBatchSize: 1024,
|
|
23
|
+
/** The maximum queue size. After the size is reached log records are dropped. */
|
|
24
|
+
maxQueueSize: 4096,
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
otelLogs.setGlobalLoggerProvider(loggerProvider);
|
|
29
|
+
|
|
30
|
+
return loggerProvider;
|
|
31
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ROOT_CONTEXT, type Span, SpanKind, SpanStatusCode, propagation } from '@opentelemetry/api';
|
|
2
|
+
import type Koa from 'koa';
|
|
3
|
+
|
|
4
|
+
import { getTelemetryClient } from './start.js';
|
|
5
|
+
import {
|
|
6
|
+
ATTR_JSONRPC_ERROR_CODE,
|
|
7
|
+
ATTR_JSONRPC_ERROR_MSG,
|
|
8
|
+
ATTR_JSONRPC_METHOD,
|
|
9
|
+
ATTR_JSONRPC_REQUEST_ID,
|
|
10
|
+
} from './vendor/attributes.js';
|
|
11
|
+
|
|
12
|
+
export function getOtelJsonRpcPropagationMiddleware(
|
|
13
|
+
scope = 'JsonRpcServer',
|
|
14
|
+
): (ctx: Koa.Context, next: () => Promise<void>) => Promise<void> {
|
|
15
|
+
return function otelJsonRpcPropagation(ctx: Koa.Context, next: () => Promise<void>) {
|
|
16
|
+
const tracer = getTelemetryClient().getTracer(scope);
|
|
17
|
+
const context = propagation.extract(ROOT_CONTEXT, ctx.request.headers);
|
|
18
|
+
const method = (ctx.request.body as any)?.method;
|
|
19
|
+
return tracer.startActiveSpan(
|
|
20
|
+
`JsonRpcServer.${method ?? 'unknown'}`,
|
|
21
|
+
{ kind: SpanKind.SERVER },
|
|
22
|
+
context,
|
|
23
|
+
async (span: Span): Promise<void> => {
|
|
24
|
+
if (ctx.id) {
|
|
25
|
+
span.setAttribute(ATTR_JSONRPC_REQUEST_ID, ctx.id);
|
|
26
|
+
}
|
|
27
|
+
if (method) {
|
|
28
|
+
span.setAttribute(ATTR_JSONRPC_METHOD, method);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await next();
|
|
33
|
+
const err = (ctx.body as any).error?.message;
|
|
34
|
+
const code = (ctx.body as any).error?.code;
|
|
35
|
+
if (err) {
|
|
36
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err });
|
|
37
|
+
span.setAttribute(ATTR_JSONRPC_ERROR_CODE, code);
|
|
38
|
+
span.setAttribute(ATTR_JSONRPC_ERROR_MSG, err);
|
|
39
|
+
} else {
|
|
40
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
|
|
44
|
+
} finally {
|
|
45
|
+
span.end();
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GcpDetectorSync } from '@google-cloud/opentelemetry-resource-util';
|
|
2
|
+
import {
|
|
3
|
+
type IResource,
|
|
4
|
+
detectResourcesSync,
|
|
5
|
+
envDetectorSync,
|
|
6
|
+
osDetectorSync,
|
|
7
|
+
processDetectorSync,
|
|
8
|
+
serviceInstanceIdDetectorSync,
|
|
9
|
+
} from '@opentelemetry/resources';
|
|
10
|
+
|
|
11
|
+
import { aztecDetector } from './aztec_resource_detector.js';
|
|
12
|
+
|
|
13
|
+
export function getOtelResource(): IResource {
|
|
14
|
+
const resource = detectResourcesSync({
|
|
15
|
+
detectors: [
|
|
16
|
+
osDetectorSync,
|
|
17
|
+
envDetectorSync,
|
|
18
|
+
processDetectorSync,
|
|
19
|
+
serviceInstanceIdDetectorSync,
|
|
20
|
+
aztecDetector,
|
|
21
|
+
new GcpDetectorSync(),
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return resource;
|
|
26
|
+
}
|