@equipe-tech/observability 0.1.0 → 0.2.1
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/LICENSE +202 -0
- package/README.md +27 -0
- package/dist/Metrics.d.ts +74 -0
- package/dist/Metrics.js +20 -0
- package/dist/MetricsRuntime.d.ts +11 -0
- package/dist/MetricsRuntime.js +1286 -0
- package/dist/RedactionPolicy.d.ts +9 -0
- package/dist/RedactionPolicy.js +246 -0
- package/dist/Telemetry.d.ts +8 -4
- package/dist/Telemetry.js +25 -10
- package/dist/TelemetryConfig.d.ts +1 -0
- package/dist/TelemetryConfig.js +1 -1
- package/dist/browser/BrowserClient.d.ts +74 -0
- package/dist/browser/BrowserClient.js +189 -0
- package/dist/browser/client.d.ts +2 -0
- package/dist/browser/client.js +1 -0
- package/dist/browser/index.d.ts +4 -2
- package/dist/browser/index.js +40 -50
- package/dist/nestjs/BrowserEventsController.d.ts +1 -1
- package/dist/nestjs/BrowserEventsController.js +9 -1
- package/dist/nestjs/HttpRoutePolicy.d.ts +23 -0
- package/dist/nestjs/HttpRoutePolicy.js +179 -0
- package/dist/nestjs/HttpServerOtlpTracer.d.ts +15 -0
- package/dist/nestjs/HttpServerOtlpTracer.js +154 -0
- package/dist/nestjs/RequestWideEventTraceCorrelation.d.ts +15 -0
- package/dist/nestjs/RequestWideEventTraceCorrelation.js +13 -0
- package/dist/nestjs/TelemetryInterceptor.d.ts +23 -4
- package/dist/nestjs/TelemetryInterceptor.js +205 -39
- package/dist/nestjs/TelemetryModule.d.ts +53 -0
- package/dist/nestjs/TelemetryModule.js +428 -0
- package/dist/nestjs/index.d.ts +4 -1
- package/dist/nestjs/index.js +3 -1
- package/dist/node/BrowserEventIngest.d.ts +2 -2
- package/dist/node/BrowserEventIngest.js +3 -3
- package/dist/testing/index.d.ts +34 -2
- package/dist/testing/index.js +92 -10
- package/package.json +23 -1
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { Module } from "@nestjs/common";
|
|
2
|
+
import { APP_INTERCEPTOR, HttpAdapterHost } from "@nestjs/core";
|
|
3
|
+
import { Duration, Effect, Layer, ManagedRuntime, Option, Schema } from "effect";
|
|
4
|
+
import { OtlpExporter as Otlp } from "effect/unstable/observability";
|
|
5
|
+
import { layer } from "../Telemetry.js";
|
|
6
|
+
import { OtlpEndpoint, TelemetryConfig } from "../TelemetryConfig.js";
|
|
7
|
+
import { telemetryRoutePolicy } from "./HttpRoutePolicy.js";
|
|
8
|
+
import { RequestWideEventTraceCorrelation } from "./RequestWideEventTraceCorrelation.js";
|
|
9
|
+
import { TelemetryInterceptor, TelemetryRequestTracker } from "./TelemetryInterceptor.js";
|
|
10
|
+
export class InvalidTelemetryModuleOptions extends Error {
|
|
11
|
+
_tag = "InvalidTelemetryModuleOptions";
|
|
12
|
+
code = "OBS_TELEMETRY_INVALID_MODULE_OPTIONS";
|
|
13
|
+
cause;
|
|
14
|
+
constructor(cause) {
|
|
15
|
+
super("Telemetry module options are invalid. Provide valid service identity, HTTP OTLP endpoint, route templates, proxy policy, and shutdown timeout.", { cause });
|
|
16
|
+
this.name = "InvalidTelemetryModuleOptions";
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class TelemetryStartupError extends Error {
|
|
21
|
+
_tag = "TelemetryStartupError";
|
|
22
|
+
code = "OBS_TELEMETRY_STARTUP_FAILED";
|
|
23
|
+
cause;
|
|
24
|
+
constructor(cause) {
|
|
25
|
+
super("Telemetry startup failed. Verify the OTLP transport and runtime configuration before restarting the application.", { cause });
|
|
26
|
+
this.name = "TelemetryStartupError";
|
|
27
|
+
this.cause = cause;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class TelemetryShutdownError extends Error {
|
|
31
|
+
_tag = "TelemetryShutdownError";
|
|
32
|
+
code = "OBS_TELEMETRY_SHUTDOWN_FAILED";
|
|
33
|
+
cause;
|
|
34
|
+
constructor(cause) {
|
|
35
|
+
super("Telemetry shutdown failed. Telemetry resources were disposed, but the final drain or flush did not complete.", { cause });
|
|
36
|
+
this.name = "TelemetryShutdownError";
|
|
37
|
+
this.cause = cause;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const Identity = Schema.NonEmptyString.check(Schema.makeFilter((value) => value.trim() === value, { expected: "a nonempty trimmed string" }));
|
|
41
|
+
const ShutdownTimeout = Schema.Number.check(Schema.isInt(), Schema.makeFilter((value) => Number.isSafeInteger(value) && value > 0, {
|
|
42
|
+
expected: "a positive safe integer",
|
|
43
|
+
}));
|
|
44
|
+
const EnabledOptions = Schema.Struct({
|
|
45
|
+
enabled: Schema.Literal(true),
|
|
46
|
+
serviceName: Identity,
|
|
47
|
+
serviceVersion: Identity,
|
|
48
|
+
environment: Identity,
|
|
49
|
+
otlpEndpoint: OtlpEndpoint,
|
|
50
|
+
healthRouteTemplates: Schema.Union([Schema.Array(Schema.String), Schema.Undefined]).pipe(Schema.optionalKey),
|
|
51
|
+
proxyPolicy: Schema.Union([Schema.Literals(["direct", "framework"]), Schema.Undefined]).pipe(Schema.optionalKey),
|
|
52
|
+
requestWideEventTraceCorrelation: Schema.Union([
|
|
53
|
+
Schema.instanceOf(RequestWideEventTraceCorrelation),
|
|
54
|
+
Schema.Undefined,
|
|
55
|
+
]).pipe(Schema.optionalKey),
|
|
56
|
+
shutdownTimeoutMilliseconds: Schema.Union([ShutdownTimeout, Schema.Undefined]).pipe(Schema.optionalKey),
|
|
57
|
+
});
|
|
58
|
+
const DisabledOptions = Schema.Struct({ enabled: Schema.Literal(false) });
|
|
59
|
+
const ModuleOptions = Schema.Union([DisabledOptions, EnabledOptions]);
|
|
60
|
+
const decodeModuleOptions = Schema.decodeUnknownSync(ModuleOptions);
|
|
61
|
+
const parseModuleOptions = (input) => {
|
|
62
|
+
try {
|
|
63
|
+
const options = decodeModuleOptions(input);
|
|
64
|
+
if (!options.enabled) {
|
|
65
|
+
return { enabled: false };
|
|
66
|
+
}
|
|
67
|
+
telemetryRoutePolicy({
|
|
68
|
+
healthRouteTemplates: options.healthRouteTemplates,
|
|
69
|
+
proxyPolicy: options.proxyPolicy,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
enabled: true,
|
|
73
|
+
config: new TelemetryConfig({
|
|
74
|
+
serviceName: options.serviceName,
|
|
75
|
+
serviceVersion: options.serviceVersion,
|
|
76
|
+
environment: options.environment,
|
|
77
|
+
otlpEndpoint: options.otlpEndpoint,
|
|
78
|
+
}),
|
|
79
|
+
healthRouteTemplates: options.healthRouteTemplates,
|
|
80
|
+
proxyPolicy: options.proxyPolicy ?? "direct",
|
|
81
|
+
requestWideEventTraceCorrelation: options.requestWideEventTraceCorrelation,
|
|
82
|
+
shutdownTimeoutMilliseconds: options.shutdownTimeoutMilliseconds ?? 5_000,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (cause) {
|
|
86
|
+
throw new InvalidTelemetryModuleOptions(cause);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
class DisabledTelemetryInterceptor {
|
|
90
|
+
intercept(_context, next) {
|
|
91
|
+
return next.handle();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
class DisabledTelemetryIntegration {
|
|
95
|
+
interceptor = new DisabledTelemetryInterceptor();
|
|
96
|
+
beforeApplicationShutdown() { }
|
|
97
|
+
onApplicationShutdown() { }
|
|
98
|
+
}
|
|
99
|
+
class EnabledTelemetryIntegration {
|
|
100
|
+
interceptor;
|
|
101
|
+
#runtime;
|
|
102
|
+
#flusher;
|
|
103
|
+
#requestTracker;
|
|
104
|
+
#shutdownTimeoutMilliseconds;
|
|
105
|
+
#releaseRuntime;
|
|
106
|
+
#beforeRequestDrain;
|
|
107
|
+
#shutdownPromise;
|
|
108
|
+
#disposePromise;
|
|
109
|
+
#shutdownError;
|
|
110
|
+
constructor(runtime, flusher, options, releaseRuntime, beforeRequestDrain) {
|
|
111
|
+
this.#runtime = runtime;
|
|
112
|
+
this.#flusher = flusher;
|
|
113
|
+
this.#releaseRuntime = releaseRuntime;
|
|
114
|
+
this.#beforeRequestDrain = beforeRequestDrain;
|
|
115
|
+
this.#requestTracker = new TelemetryRequestTracker();
|
|
116
|
+
this.#shutdownTimeoutMilliseconds = options.shutdownTimeoutMilliseconds;
|
|
117
|
+
this.interceptor = new TelemetryInterceptor(runtime, {
|
|
118
|
+
healthRouteTemplates: options.healthRouteTemplates,
|
|
119
|
+
proxyPolicy: options.proxyPolicy,
|
|
120
|
+
requestTracker: this.#requestTracker,
|
|
121
|
+
requestWideEventTraceCorrelation: options.requestWideEventTraceCorrelation,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
beforeApplicationShutdown() {
|
|
125
|
+
this.#shutdownPromise ??= this.#shutdown();
|
|
126
|
+
return this.#shutdownPromise;
|
|
127
|
+
}
|
|
128
|
+
async onApplicationShutdown() {
|
|
129
|
+
try {
|
|
130
|
+
await this.beforeApplicationShutdown();
|
|
131
|
+
}
|
|
132
|
+
catch (cause) {
|
|
133
|
+
this.#recordShutdownError(cause);
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
this.#disposePromise ??= this.#releaseRuntime();
|
|
137
|
+
try {
|
|
138
|
+
await this.#disposePromise;
|
|
139
|
+
}
|
|
140
|
+
catch (cause) {
|
|
141
|
+
this.#recordShutdownError(cause);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (this.#shutdownError !== undefined) {
|
|
145
|
+
throw this.#shutdownError;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async #shutdown() {
|
|
149
|
+
const deadline = Date.now() + this.#shutdownTimeoutMilliseconds;
|
|
150
|
+
try {
|
|
151
|
+
this.#requestTracker.closeAdmission();
|
|
152
|
+
await this.#beforeRequestDrain();
|
|
153
|
+
const idle = await this.#withinDeadline(this.#requestTracker.waitForIdle(), deadline);
|
|
154
|
+
if (!idle) {
|
|
155
|
+
this.#requestTracker.interruptActive();
|
|
156
|
+
const interrupted = await this.#withinDeadline(this.#requestTracker.waitForIdle(), deadline);
|
|
157
|
+
if (!interrupted) {
|
|
158
|
+
this.#recordShutdownError(new Error("The telemetry request interruption exhausted the shutdown deadline."));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (cause) {
|
|
163
|
+
this.#recordShutdownError(cause);
|
|
164
|
+
}
|
|
165
|
+
const remaining = Math.max(0, deadline - Date.now());
|
|
166
|
+
if (remaining === 0) {
|
|
167
|
+
this.#recordShutdownError(new Error("The telemetry request drain exhausted the shutdown deadline."));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const flushed = await this.#runtime.runPromise(this.#flusher.flush.pipe(Effect.timeoutOption(Duration.millis(remaining))));
|
|
172
|
+
if (Option.isNone(flushed)) {
|
|
173
|
+
this.#recordShutdownError(new Error("The telemetry flush exceeded the shutdown deadline."));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (cause) {
|
|
177
|
+
this.#recordShutdownError(cause);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
#recordShutdownError(cause) {
|
|
181
|
+
if (this.#shutdownError === undefined) {
|
|
182
|
+
this.#shutdownError =
|
|
183
|
+
cause instanceof TelemetryShutdownError ? cause : new TelemetryShutdownError(cause);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.#shutdownError = new TelemetryShutdownError(new AggregateError([this.#shutdownError.cause, cause]));
|
|
187
|
+
}
|
|
188
|
+
async #withinDeadline(operation, deadline) {
|
|
189
|
+
const remaining = Math.max(0, deadline - Date.now());
|
|
190
|
+
if (remaining === 0) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
let timer;
|
|
194
|
+
const timeout = new Promise((resolve) => {
|
|
195
|
+
timer = setTimeout(() => resolve(false), remaining);
|
|
196
|
+
});
|
|
197
|
+
const completed = operation.then(() => true);
|
|
198
|
+
try {
|
|
199
|
+
return await Promise.race([completed, timeout]);
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
if (timer !== undefined) {
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
class DeferredTelemetryInterceptor {
|
|
209
|
+
#target;
|
|
210
|
+
setTarget(target) {
|
|
211
|
+
this.#target = target;
|
|
212
|
+
}
|
|
213
|
+
intercept(context, next) {
|
|
214
|
+
return this.#target?.intercept(context, next) ?? next.handle();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
class PendingTelemetryIntegration {
|
|
218
|
+
interceptor = new DeferredTelemetryInterceptor();
|
|
219
|
+
#application;
|
|
220
|
+
#options;
|
|
221
|
+
#state;
|
|
222
|
+
#overrides;
|
|
223
|
+
#integration;
|
|
224
|
+
constructor(application, options, state, overrides) {
|
|
225
|
+
this.#application = application;
|
|
226
|
+
this.#options = options;
|
|
227
|
+
this.#state = state;
|
|
228
|
+
this.#overrides = overrides;
|
|
229
|
+
}
|
|
230
|
+
async onModuleInit() {
|
|
231
|
+
const lease = await acquireApplicationRuntime(this.#application, this.#state, this.#options, this.#overrides);
|
|
232
|
+
this.#integration = new EnabledTelemetryIntegration(lease.runtime, lease.flusher, this.#options, lease.release, this.#overrides.beforeRequestDrain ?? (() => undefined));
|
|
233
|
+
this.interceptor.setTarget(this.#integration.interceptor);
|
|
234
|
+
}
|
|
235
|
+
beforeApplicationShutdown() {
|
|
236
|
+
return this.#integration?.beforeApplicationShutdown();
|
|
237
|
+
}
|
|
238
|
+
onApplicationShutdown() {
|
|
239
|
+
return this.#integration?.onApplicationShutdown();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const ASYNC_OPTIONS = Symbol("TelemetryModuleAsyncOptions");
|
|
243
|
+
const NORMALIZED_OPTIONS = Symbol("TelemetryModuleNormalizedOptions");
|
|
244
|
+
const TELEMETRY_INTEGRATION = Symbol("TelemetryModuleIntegration");
|
|
245
|
+
const runtimePool = new Map();
|
|
246
|
+
const applicationRuntimes = new WeakMap();
|
|
247
|
+
const runtimeKey = (options) => JSON.stringify([
|
|
248
|
+
options.config.otlpEndpoint.toString(),
|
|
249
|
+
options.config.serviceName,
|
|
250
|
+
options.config.serviceVersion,
|
|
251
|
+
options.config.environment,
|
|
252
|
+
options.shutdownTimeoutMilliseconds,
|
|
253
|
+
]);
|
|
254
|
+
const createSharedRuntime = async (options, overrides) => {
|
|
255
|
+
let runtime;
|
|
256
|
+
try {
|
|
257
|
+
let runtimeLayer = layer(options.config, {
|
|
258
|
+
shutdownTimeout: Duration.millis(options.shutdownTimeoutMilliseconds),
|
|
259
|
+
});
|
|
260
|
+
if (overrides.scopedResource !== undefined) {
|
|
261
|
+
const resource = overrides.scopedResource;
|
|
262
|
+
const resourceLayer = Layer.effectDiscard(Effect.acquireRelease(Effect.promise(() => Promise.resolve(resource.acquire())), () => Effect.promise(() => Promise.resolve(resource.release()))));
|
|
263
|
+
runtimeLayer = Layer.merge(runtimeLayer, resourceLayer);
|
|
264
|
+
}
|
|
265
|
+
runtime = ManagedRuntime.make(runtimeLayer);
|
|
266
|
+
await runtime.context();
|
|
267
|
+
await (overrides.startupProbe ?? (() => undefined))();
|
|
268
|
+
const flusher = await runtime.runPromise(Otlp.Flusher);
|
|
269
|
+
return { runtime, flusher };
|
|
270
|
+
}
|
|
271
|
+
catch (cause) {
|
|
272
|
+
if (runtime !== undefined) {
|
|
273
|
+
try {
|
|
274
|
+
await runtime.dispose();
|
|
275
|
+
overrides.onRuntimeDisposed?.();
|
|
276
|
+
}
|
|
277
|
+
catch (disposeCause) {
|
|
278
|
+
throw new TelemetryStartupError(new AggregateError([cause, disposeCause]));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
throw new TelemetryStartupError(cause);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const acquireRuntime = async (options, overrides) => {
|
|
285
|
+
const key = runtimeKey(options);
|
|
286
|
+
let entry = runtimePool.get(key);
|
|
287
|
+
if (entry?.closing !== undefined) {
|
|
288
|
+
try {
|
|
289
|
+
await entry.closing;
|
|
290
|
+
}
|
|
291
|
+
catch (cause) {
|
|
292
|
+
throw new TelemetryStartupError(cause);
|
|
293
|
+
}
|
|
294
|
+
entry = runtimePool.get(key);
|
|
295
|
+
}
|
|
296
|
+
if (entry === undefined) {
|
|
297
|
+
const shared = createSharedRuntime(options, overrides);
|
|
298
|
+
entry = { shared, references: 0, closing: undefined };
|
|
299
|
+
runtimePool.set(key, entry);
|
|
300
|
+
try {
|
|
301
|
+
await shared;
|
|
302
|
+
}
|
|
303
|
+
catch (cause) {
|
|
304
|
+
if (runtimePool.get(key) === entry) {
|
|
305
|
+
runtimePool.delete(key);
|
|
306
|
+
}
|
|
307
|
+
throw cause;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const shared = await entry.shared;
|
|
311
|
+
entry.references++;
|
|
312
|
+
let releasePromise;
|
|
313
|
+
const release = () => {
|
|
314
|
+
if (releasePromise !== undefined) {
|
|
315
|
+
return releasePromise;
|
|
316
|
+
}
|
|
317
|
+
entry.references--;
|
|
318
|
+
if (entry.references > 0) {
|
|
319
|
+
releasePromise = Promise.resolve();
|
|
320
|
+
return releasePromise;
|
|
321
|
+
}
|
|
322
|
+
entry.closing = (async () => {
|
|
323
|
+
let failure;
|
|
324
|
+
try {
|
|
325
|
+
await overrides.beforeRuntimeDispose?.();
|
|
326
|
+
}
|
|
327
|
+
catch (cause) {
|
|
328
|
+
failure = new Error("Telemetry runtime disposal preparation failed.", { cause });
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
await shared.runtime.dispose();
|
|
332
|
+
overrides.onRuntimeDisposed?.();
|
|
333
|
+
}
|
|
334
|
+
catch (cause) {
|
|
335
|
+
failure =
|
|
336
|
+
failure === undefined
|
|
337
|
+
? new Error("Telemetry runtime disposal failed.", { cause })
|
|
338
|
+
: new AggregateError([failure, cause]);
|
|
339
|
+
}
|
|
340
|
+
if (failure !== undefined) {
|
|
341
|
+
throw failure;
|
|
342
|
+
}
|
|
343
|
+
})().finally(() => {
|
|
344
|
+
if (runtimePool.get(key) === entry) {
|
|
345
|
+
runtimePool.delete(key);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
releasePromise = entry.closing;
|
|
349
|
+
return releasePromise;
|
|
350
|
+
};
|
|
351
|
+
return { runtime: shared.runtime, flusher: shared.flusher, release };
|
|
352
|
+
};
|
|
353
|
+
const registerApplicationRuntime = (application, options) => {
|
|
354
|
+
const key = runtimeKey(options);
|
|
355
|
+
const state = applicationRuntimes.get(application);
|
|
356
|
+
if (state === undefined) {
|
|
357
|
+
const registered = {
|
|
358
|
+
key,
|
|
359
|
+
requestWideEventTraceCorrelation: options.requestWideEventTraceCorrelation,
|
|
360
|
+
releases: new Set(),
|
|
361
|
+
failed: false,
|
|
362
|
+
};
|
|
363
|
+
applicationRuntimes.set(application, registered);
|
|
364
|
+
return registered;
|
|
365
|
+
}
|
|
366
|
+
if (state.key !== key ||
|
|
367
|
+
state.requestWideEventTraceCorrelation !== options.requestWideEventTraceCorrelation) {
|
|
368
|
+
state.failed = true;
|
|
369
|
+
}
|
|
370
|
+
return state;
|
|
371
|
+
};
|
|
372
|
+
const acquireApplicationRuntime = async (application, state, options, overrides) => {
|
|
373
|
+
if (state.failed) {
|
|
374
|
+
applicationRuntimes.delete(application);
|
|
375
|
+
throw new InvalidTelemetryModuleOptions(new Error("TelemetryModule imports in one application must use one telemetry configuration."));
|
|
376
|
+
}
|
|
377
|
+
const lease = await acquireRuntime(options, overrides);
|
|
378
|
+
const release = () => {
|
|
379
|
+
state.releases.delete(release);
|
|
380
|
+
if (state.releases.size === 0) {
|
|
381
|
+
applicationRuntimes.delete(application);
|
|
382
|
+
}
|
|
383
|
+
return lease.release();
|
|
384
|
+
};
|
|
385
|
+
state.releases.add(release);
|
|
386
|
+
return { runtime: lease.runtime, flusher: lease.flusher, release };
|
|
387
|
+
};
|
|
388
|
+
const makeIntegration = (options, application, overrides) => {
|
|
389
|
+
if (!options.enabled) {
|
|
390
|
+
return new DisabledTelemetryIntegration();
|
|
391
|
+
}
|
|
392
|
+
return new PendingTelemetryIntegration(application, options, registerApplicationRuntime(application, options), overrides);
|
|
393
|
+
};
|
|
394
|
+
const makeTelemetryModule = (options, overrides = {}) => {
|
|
395
|
+
const providers = [
|
|
396
|
+
{
|
|
397
|
+
provide: ASYNC_OPTIONS,
|
|
398
|
+
inject: options.inject ?? [],
|
|
399
|
+
useFactory: options.useFactory,
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
provide: NORMALIZED_OPTIONS,
|
|
403
|
+
inject: [ASYNC_OPTIONS],
|
|
404
|
+
useFactory: parseModuleOptions,
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
provide: TELEMETRY_INTEGRATION,
|
|
408
|
+
inject: [NORMALIZED_OPTIONS, HttpAdapterHost],
|
|
409
|
+
useFactory: (normalized, application) => makeIntegration(normalized, application, overrides),
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
provide: APP_INTERCEPTOR,
|
|
413
|
+
inject: [TELEMETRY_INTEGRATION],
|
|
414
|
+
useFactory: (integration) => integration.interceptor,
|
|
415
|
+
},
|
|
416
|
+
];
|
|
417
|
+
if (options.imports === undefined) {
|
|
418
|
+
return { module: TelemetryModule, providers };
|
|
419
|
+
}
|
|
420
|
+
return { module: TelemetryModule, imports: options.imports, providers };
|
|
421
|
+
};
|
|
422
|
+
export const telemetryModuleForTesting = (options, overrides) => makeTelemetryModule(options, overrides);
|
|
423
|
+
export class TelemetryModule {
|
|
424
|
+
static forRootAsync(options) {
|
|
425
|
+
return makeTelemetryModule(options);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
Module({})(TelemetryModule);
|
package/dist/nestjs/index.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { BrowserEventsRejection, createBrowserEventsController, defaultBrowserEventsPath, type BrowserEventsControllerOptions, } from "./BrowserEventsController.js";
|
|
2
|
-
export { requestSpan, TelemetryInterceptor, withRequestSpan, type
|
|
2
|
+
export { requestSpan, TelemetryInterceptor, TelemetryRequestTracker, withRequestSpan, type TelemetryInterceptorOptions, } from "./TelemetryInterceptor.js";
|
|
3
|
+
export { createRequestWideEventTraceCorrelation, RequestWideEventTraceCorrelation, type RequestReference, type RequestWideEventLogger, type RequestWideEventLoggerResolver, type ServerSpanCorrelation, } from "./RequestWideEventTraceCorrelation.js";
|
|
4
|
+
export type { ProxyPolicy, TelemetryRoutePolicyOptions } from "./HttpRoutePolicy.js";
|
|
5
|
+
export { InvalidTelemetryModuleOptions, TelemetryModule, TelemetryShutdownError, TelemetryStartupError, type TelemetryModuleAsyncOptions, type TelemetryModuleOptions, } from "./TelemetryModule.js";
|
package/dist/nestjs/index.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { BrowserEventsRejection, createBrowserEventsController, defaultBrowserEventsPath, } from "./BrowserEventsController.js";
|
|
2
|
-
export { requestSpan, TelemetryInterceptor, withRequestSpan, } from "./TelemetryInterceptor.js";
|
|
2
|
+
export { requestSpan, TelemetryInterceptor, TelemetryRequestTracker, withRequestSpan, } from "./TelemetryInterceptor.js";
|
|
3
|
+
export { createRequestWideEventTraceCorrelation, RequestWideEventTraceCorrelation, } from "./RequestWideEventTraceCorrelation.js";
|
|
4
|
+
export { InvalidTelemetryModuleOptions, TelemetryModule, TelemetryShutdownError, TelemetryStartupError, } from "./TelemetryModule.js";
|
|
@@ -7,10 +7,10 @@ declare const InvalidBrowserEventBatch_base: Schema.Class<InvalidBrowserEventBat
|
|
|
7
7
|
}>, import("effect/Cause").YieldableError>;
|
|
8
8
|
export declare class InvalidBrowserEventBatch extends InvalidBrowserEventBatch_base {
|
|
9
9
|
}
|
|
10
|
-
export declare const parseBrowserEventBatch: (input: unknown) => Effect.Effect<BrowserEventBatch, InvalidBrowserEventBatch>;
|
|
10
|
+
export declare const parseBrowserEventBatch: (input: unknown, options?: import("effect/SchemaAST").ParseOptions | undefined) => Effect.Effect<BrowserEventBatch, InvalidBrowserEventBatch, never>;
|
|
11
11
|
export type BrowserEventIngestReceipt = {
|
|
12
12
|
readonly accepted: number;
|
|
13
13
|
};
|
|
14
14
|
export declare const ingestBrowserEventBatch: (batch: BrowserEventBatch) => Effect.Effect<BrowserEventIngestReceipt, never, never>;
|
|
15
|
-
export declare const ingestBrowserEvents: (input: unknown) => Effect.Effect<BrowserEventIngestReceipt, InvalidBrowserEventBatch>;
|
|
15
|
+
export declare const ingestBrowserEvents: (input: unknown, options?: import("effect/SchemaAST").ParseOptions | undefined) => Effect.Effect<BrowserEventIngestReceipt, InvalidBrowserEventBatch, never>;
|
|
16
16
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect";
|
|
1
|
+
import { Effect, flow, Schema } from "effect";
|
|
2
2
|
import { BrowserEventBatch } from "../BrowserEvents.js";
|
|
3
3
|
import * as WideEvent from "../WideEvent.js";
|
|
4
4
|
export class InvalidBrowserEventBatch extends Schema.TaggedError()("InvalidBrowserEventBatch", {
|
|
@@ -8,7 +8,7 @@ export class InvalidBrowserEventBatch extends Schema.TaggedError()("InvalidBrows
|
|
|
8
8
|
}) {
|
|
9
9
|
}
|
|
10
10
|
const decodeBrowserEventBatch = Schema.decodeUnknownEffect(BrowserEventBatch);
|
|
11
|
-
export const parseBrowserEventBatch = (
|
|
11
|
+
export const parseBrowserEventBatch = flow(decodeBrowserEventBatch, Effect.mapError((cause) => new InvalidBrowserEventBatch({
|
|
12
12
|
code: "OBS_BROWSER_EVENTS_INVALID_BATCH",
|
|
13
13
|
message: "The browser event batch is invalid. Send a version 1 batch with bounded events and scalar fields.",
|
|
14
14
|
cause,
|
|
@@ -35,4 +35,4 @@ export const ingestBrowserEventBatch = Effect.fn("ingestBrowserEventBatch")(func
|
|
|
35
35
|
}
|
|
36
36
|
return { accepted: batch.events.length };
|
|
37
37
|
});
|
|
38
|
-
export const ingestBrowserEvents = (
|
|
38
|
+
export const ingestBrowserEvents = flow(parseBrowserEventBatch, Effect.flatMap(ingestBrowserEventBatch));
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect, Layer, Option, type Exit } from "effect";
|
|
2
|
+
import { OtlpExporter } from "effect/unstable/observability";
|
|
2
3
|
import { TelemetryConfig } from "../TelemetryConfig.js";
|
|
3
4
|
export type CapturedAttributeValue = string | number | boolean;
|
|
4
5
|
export type CapturedAttributes = ReadonlyMap<string, CapturedAttributeValue>;
|
|
@@ -7,9 +8,12 @@ export type CapturedSpan = {
|
|
|
7
8
|
readonly spanId: string;
|
|
8
9
|
readonly parentSpanId: Option.Option<string>;
|
|
9
10
|
readonly name: string;
|
|
11
|
+
readonly kind: number;
|
|
10
12
|
readonly statusCode: number;
|
|
11
13
|
readonly statusMessage: Option.Option<string>;
|
|
12
14
|
readonly attributes: CapturedAttributes;
|
|
15
|
+
readonly eventNames: ReadonlyArray<string>;
|
|
16
|
+
readonly linkedSpanIds: ReadonlyArray<string>;
|
|
13
17
|
readonly resourceAttributes: CapturedAttributes;
|
|
14
18
|
};
|
|
15
19
|
export type CapturedLog = {
|
|
@@ -24,11 +28,38 @@ export type CapturedMetricPoint = {
|
|
|
24
28
|
readonly value: Option.Option<number>;
|
|
25
29
|
readonly attributes: CapturedAttributes;
|
|
26
30
|
};
|
|
27
|
-
export type
|
|
31
|
+
export type CapturedHistogramPoint = {
|
|
32
|
+
readonly attributes: CapturedAttributes;
|
|
33
|
+
readonly count: number;
|
|
34
|
+
readonly sum: number;
|
|
35
|
+
readonly min: number;
|
|
36
|
+
readonly max: number;
|
|
37
|
+
readonly explicitBounds: ReadonlyArray<number>;
|
|
38
|
+
readonly bucketCounts: ReadonlyArray<number>;
|
|
39
|
+
};
|
|
40
|
+
type CapturedMetricCommon = {
|
|
28
41
|
readonly name: string;
|
|
42
|
+
readonly description: string;
|
|
43
|
+
readonly unit: string;
|
|
29
44
|
readonly points: ReadonlyArray<CapturedMetricPoint>;
|
|
30
45
|
readonly resourceAttributes: CapturedAttributes;
|
|
31
46
|
};
|
|
47
|
+
export type CapturedMetric = (CapturedMetricCommon & {
|
|
48
|
+
readonly kind: "sum";
|
|
49
|
+
readonly isMonotonic: boolean;
|
|
50
|
+
readonly aggregationTemporality: number;
|
|
51
|
+
}) | (CapturedMetricCommon & {
|
|
52
|
+
readonly kind: "gauge";
|
|
53
|
+
}) | (CapturedMetricCommon & {
|
|
54
|
+
readonly kind: "histogram";
|
|
55
|
+
readonly aggregationTemporality: number;
|
|
56
|
+
readonly histogramPoints: ReadonlyArray<CapturedHistogramPoint>;
|
|
57
|
+
}) | (CapturedMetricCommon & {
|
|
58
|
+
readonly kind: "exponentialHistogram";
|
|
59
|
+
readonly aggregationTemporality: number;
|
|
60
|
+
}) | (CapturedMetricCommon & {
|
|
61
|
+
readonly kind: "summary";
|
|
62
|
+
});
|
|
32
63
|
export type CapturedTelemetry = {
|
|
33
64
|
readonly spans: ReadonlyArray<CapturedSpan>;
|
|
34
65
|
readonly logs: ReadonlyArray<CapturedLog>;
|
|
@@ -47,8 +78,9 @@ export type RunOptions = {
|
|
|
47
78
|
readonly config?: TelemetryConfig;
|
|
48
79
|
};
|
|
49
80
|
export type TelemetryCapture = {
|
|
50
|
-
readonly layer: Layer.Layer<
|
|
81
|
+
readonly layer: Layer.Layer<OtlpExporter.Flusher>;
|
|
51
82
|
readonly telemetry: Effect.Effect<CapturedTelemetry>;
|
|
52
83
|
};
|
|
53
84
|
export declare const makeCapture: (options?: RunOptions | undefined) => Effect.Effect<TelemetryCapture, never, never>;
|
|
54
85
|
export declare const run: <A, E, R>(program: Effect.Effect<A, E, R>, options?: RunOptions) => Effect.Effect<TelemetryRun<A, E>, never, R>;
|
|
86
|
+
export {};
|