@nxgt/telemetry 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/README.md +742 -0
- package/dist/attributes/attributes.d.ts +7 -0
- package/dist/attributes/attributes.d.ts.map +1 -1
- package/dist/context/current.d.ts +43 -0
- package/dist/context/current.d.ts.map +1 -0
- package/dist/export/console.d.ts +20 -0
- package/dist/export/console.d.ts.map +1 -0
- package/dist/export/exporter.d.ts +39 -0
- package/dist/export/exporter.d.ts.map +1 -0
- package/dist/export/file.d.ts +43 -0
- package/dist/export/file.d.ts.map +1 -0
- package/dist/export/json-lines.d.ts +17 -0
- package/dist/export/json-lines.d.ts.map +1 -0
- package/dist/export/pipeline.d.ts +64 -0
- package/dist/export/pipeline.d.ts.map +1 -0
- package/dist/export/rotation.d.ts +46 -0
- package/dist/export/rotation.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +845 -33
- package/dist/index.js.map +18 -6
- package/dist/logger/event.d.ts +15 -0
- package/dist/logger/event.d.ts.map +1 -0
- package/dist/logger/logger.d.ts +46 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/standard-schema.d.ts +32 -0
- package/dist/logger/standard-schema.d.ts.map +1 -0
- package/dist/model/error.d.ts +21 -0
- package/dist/model/error.d.ts.map +1 -0
- package/dist/span/scope.d.ts +71 -0
- package/dist/span/scope.d.ts.map +1 -0
- package/dist/span/span.d.ts +13 -0
- package/dist/span/span.d.ts.map +1 -0
- package/dist/telemetry/telemetry.d.ts +83 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
// src/attributes/attributes.ts
|
|
2
2
|
var EMPTY_ATTRIBUTES = Object.freeze({});
|
|
3
|
+
var UNREADABLE = "[unreadable]";
|
|
3
4
|
function coerceAttribute(value) {
|
|
5
|
+
try {
|
|
6
|
+
return coerce(value);
|
|
7
|
+
} catch {
|
|
8
|
+
return UNREADABLE;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function coerce(value) {
|
|
4
12
|
if (value === null || value === undefined)
|
|
5
13
|
return null;
|
|
6
14
|
switch (typeof value) {
|
|
@@ -24,12 +32,25 @@ function coerceAttribute(value) {
|
|
|
24
32
|
return render(value);
|
|
25
33
|
}
|
|
26
34
|
function attributesOf(input) {
|
|
27
|
-
if (input === undefined)
|
|
35
|
+
if (input === undefined || input === null)
|
|
28
36
|
return EMPTY_ATTRIBUTES;
|
|
37
|
+
let keys;
|
|
38
|
+
try {
|
|
39
|
+
keys = Object.keys(input);
|
|
40
|
+
} catch {
|
|
41
|
+
return EMPTY_ATTRIBUTES;
|
|
42
|
+
}
|
|
29
43
|
const attributes = {};
|
|
30
44
|
let any = false;
|
|
31
|
-
for (const key of
|
|
32
|
-
|
|
45
|
+
for (const key of keys) {
|
|
46
|
+
let value;
|
|
47
|
+
try {
|
|
48
|
+
value = input[key];
|
|
49
|
+
} catch {
|
|
50
|
+
attributes[key] = UNREADABLE;
|
|
51
|
+
any = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
33
54
|
if (value === undefined)
|
|
34
55
|
continue;
|
|
35
56
|
attributes[key] = coerceAttribute(value);
|
|
@@ -67,38 +88,97 @@ function render(value) {
|
|
|
67
88
|
try {
|
|
68
89
|
return String(value);
|
|
69
90
|
} catch {
|
|
70
|
-
return
|
|
91
|
+
return UNREADABLE;
|
|
71
92
|
}
|
|
72
93
|
}
|
|
73
|
-
// src/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
// src/context/current.ts
|
|
95
|
+
import * as asyncHooks from "node:async_hooks";
|
|
96
|
+
|
|
97
|
+
// src/export/pipeline.ts
|
|
98
|
+
class Pipeline {
|
|
99
|
+
constructor(options) {
|
|
100
|
+
this.buffer = [];
|
|
101
|
+
this.draining = Promise.resolve();
|
|
102
|
+
this.closed = false;
|
|
103
|
+
this.options = options;
|
|
104
|
+
}
|
|
105
|
+
post(signal) {
|
|
106
|
+
if (this.closed)
|
|
107
|
+
return false;
|
|
108
|
+
this.buffer.push(signal);
|
|
109
|
+
if (this.buffer.length >= this.options.batch) {
|
|
110
|
+
this.flush();
|
|
111
|
+
} else if (this.timer === undefined) {
|
|
112
|
+
this.timer = setTimeout(() => {
|
|
113
|
+
this.timer = undefined;
|
|
114
|
+
this.flush();
|
|
115
|
+
}, this.options.linger);
|
|
116
|
+
this.timer.unref?.();
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
close() {
|
|
121
|
+
this.closing ??= this.drainAndClose();
|
|
122
|
+
return this.closing;
|
|
123
|
+
}
|
|
124
|
+
flush() {
|
|
125
|
+
this.clearTimer();
|
|
126
|
+
if (this.buffer.length === 0)
|
|
127
|
+
return;
|
|
128
|
+
const batch = this.buffer;
|
|
129
|
+
this.buffer = [];
|
|
130
|
+
this.draining = this.draining.then(() => this.ship(batch));
|
|
131
|
+
}
|
|
132
|
+
clearTimer() {
|
|
133
|
+
if (this.timer === undefined)
|
|
134
|
+
return;
|
|
135
|
+
clearTimeout(this.timer);
|
|
136
|
+
this.timer = undefined;
|
|
137
|
+
}
|
|
138
|
+
async ship(batch) {
|
|
139
|
+
for (const exporter of this.options.exporters) {
|
|
140
|
+
try {
|
|
141
|
+
await exporter.export(this.options.resource, batch);
|
|
142
|
+
} catch (failure) {
|
|
143
|
+
this.report(failure);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async drainAndClose() {
|
|
148
|
+
this.closed = true;
|
|
149
|
+
this.flush();
|
|
150
|
+
const deadline = timeout(this.options.drainTimeout);
|
|
151
|
+
const drained = await Promise.race([
|
|
152
|
+
this.draining.then(() => true),
|
|
153
|
+
deadline.then(() => false)
|
|
154
|
+
]);
|
|
155
|
+
if (!drained) {
|
|
156
|
+
this.report(new Error(`[telemetry] the backlog did not ship within ${this.options.drainTimeout}ms; closing anyway`));
|
|
157
|
+
}
|
|
158
|
+
await Promise.race([this.closeExporters(), deadline]);
|
|
159
|
+
}
|
|
160
|
+
async closeExporters() {
|
|
161
|
+
for (const exporter of this.options.exporters) {
|
|
162
|
+
try {
|
|
163
|
+
await exporter.close?.();
|
|
164
|
+
} catch (failure) {
|
|
165
|
+
this.report(failure);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
report(failure) {
|
|
170
|
+
try {
|
|
171
|
+
this.options.onExportError(failure);
|
|
172
|
+
} catch {}
|
|
173
|
+
}
|
|
98
174
|
}
|
|
99
|
-
function
|
|
100
|
-
return
|
|
175
|
+
function timeout(milliseconds) {
|
|
176
|
+
return new Promise((resolve) => {
|
|
177
|
+
const timer = setTimeout(resolve, milliseconds);
|
|
178
|
+
timer.unref?.();
|
|
179
|
+
});
|
|
101
180
|
}
|
|
181
|
+
|
|
102
182
|
// src/trace/ids.ts
|
|
103
183
|
var INVALID_TRACE_ID = "0".repeat(32);
|
|
104
184
|
var INVALID_SPAN_ID = "0".repeat(16);
|
|
@@ -164,6 +244,7 @@ function randomHex(bytes) {
|
|
|
164
244
|
}
|
|
165
245
|
return hex;
|
|
166
246
|
}
|
|
247
|
+
|
|
167
248
|
// src/trace/sampler.ts
|
|
168
249
|
var alwaysSample = { sample: () => true };
|
|
169
250
|
var neverSample = { sample: () => false };
|
|
@@ -186,32 +267,763 @@ function ratioSampler(ratio) {
|
|
|
186
267
|
}
|
|
187
268
|
};
|
|
188
269
|
}
|
|
270
|
+
|
|
271
|
+
// src/telemetry/telemetry.ts
|
|
272
|
+
var TELEMETRY_DEFAULTS = Object.freeze({
|
|
273
|
+
minimum: "info",
|
|
274
|
+
stackTraces: true,
|
|
275
|
+
batch: 512,
|
|
276
|
+
linger: 1000,
|
|
277
|
+
drainTimeout: 1e4
|
|
278
|
+
});
|
|
279
|
+
var installed;
|
|
280
|
+
|
|
281
|
+
class Telemetry {
|
|
282
|
+
constructor(options) {
|
|
283
|
+
this.resource = {
|
|
284
|
+
service: options.service,
|
|
285
|
+
...options.version === undefined ? {} : { version: options.version },
|
|
286
|
+
...options.environment === undefined ? {} : { environment: options.environment },
|
|
287
|
+
attributes: attributesOf(options.attributes)
|
|
288
|
+
};
|
|
289
|
+
this.sampler = options.sampler ?? alwaysSample;
|
|
290
|
+
this.minimum = options.minimum ?? TELEMETRY_DEFAULTS.minimum;
|
|
291
|
+
this.stackTraces = options.stackTraces ?? TELEMETRY_DEFAULTS.stackTraces;
|
|
292
|
+
this.pipeline = new Pipeline({
|
|
293
|
+
resource: this.resource,
|
|
294
|
+
exporters: options.exporters ?? [],
|
|
295
|
+
batch: options.batch ?? TELEMETRY_DEFAULTS.batch,
|
|
296
|
+
linger: options.linger ?? TELEMETRY_DEFAULTS.linger,
|
|
297
|
+
drainTimeout: options.drainTimeout ?? TELEMETRY_DEFAULTS.drainTimeout,
|
|
298
|
+
onExportError: options.onExportError ?? defaultOnExportError
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
emit(signal) {
|
|
302
|
+
return this.pipeline.post(signal);
|
|
303
|
+
}
|
|
304
|
+
install() {
|
|
305
|
+
installed = this;
|
|
306
|
+
return this;
|
|
307
|
+
}
|
|
308
|
+
async close() {
|
|
309
|
+
if (installed === this)
|
|
310
|
+
installed = undefined;
|
|
311
|
+
await this.pipeline.close();
|
|
312
|
+
}
|
|
313
|
+
async[Symbol.asyncDispose]() {
|
|
314
|
+
await this.close();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function createTelemetry(service, options = {}) {
|
|
318
|
+
return new Telemetry({ ...options, service });
|
|
319
|
+
}
|
|
320
|
+
function installedTelemetry() {
|
|
321
|
+
return installed;
|
|
322
|
+
}
|
|
323
|
+
function uninstallTelemetry(telemetry) {
|
|
324
|
+
if (telemetry === undefined || installed === telemetry)
|
|
325
|
+
installed = undefined;
|
|
326
|
+
}
|
|
327
|
+
function defaultOnExportError(failure) {
|
|
328
|
+
console.error("[telemetry] export failed", failure);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/context/current.ts
|
|
332
|
+
var storage = createStorage();
|
|
333
|
+
function currentContext() {
|
|
334
|
+
return storage.getStore();
|
|
335
|
+
}
|
|
336
|
+
function runWithContext(context, fn) {
|
|
337
|
+
return storage.run(context, fn);
|
|
338
|
+
}
|
|
339
|
+
function resolveTelemetry() {
|
|
340
|
+
return currentContext()?.telemetry ?? installedTelemetry();
|
|
341
|
+
}
|
|
342
|
+
function withTelemetry(telemetry, fn) {
|
|
343
|
+
const context = currentContext();
|
|
344
|
+
return runWithContext({
|
|
345
|
+
telemetry,
|
|
346
|
+
...context?.span === undefined ? {} : { span: context.span },
|
|
347
|
+
attributes: context?.attributes ?? EMPTY_ATTRIBUTES
|
|
348
|
+
}, fn);
|
|
349
|
+
}
|
|
350
|
+
function withAttributes(attributes, fn) {
|
|
351
|
+
const given = attributesOf(attributes);
|
|
352
|
+
const context = currentContext();
|
|
353
|
+
if (context === undefined) {
|
|
354
|
+
const telemetry = installedTelemetry();
|
|
355
|
+
if (telemetry === undefined)
|
|
356
|
+
return fn();
|
|
357
|
+
return runWithContext({ telemetry, attributes: given }, fn);
|
|
358
|
+
}
|
|
359
|
+
return runWithContext({ ...context, attributes: mergeAttributes(context.attributes, given) }, fn);
|
|
360
|
+
}
|
|
361
|
+
function currentSpan() {
|
|
362
|
+
return currentContext()?.span;
|
|
363
|
+
}
|
|
364
|
+
function currentTraceparent() {
|
|
365
|
+
const span = currentSpan();
|
|
366
|
+
return span === undefined ? undefined : renderTraceparent(span);
|
|
367
|
+
}
|
|
368
|
+
function currentAttributes() {
|
|
369
|
+
return currentContext()?.attributes ?? EMPTY_ATTRIBUTES;
|
|
370
|
+
}
|
|
371
|
+
function createStorage() {
|
|
372
|
+
const AsyncLocalStorage2 = asyncHooks.AsyncLocalStorage;
|
|
373
|
+
if (typeof AsyncLocalStorage2 === "function") {
|
|
374
|
+
return new AsyncLocalStorage2;
|
|
375
|
+
}
|
|
376
|
+
let current;
|
|
377
|
+
return {
|
|
378
|
+
getStore: () => current,
|
|
379
|
+
run(context, fn) {
|
|
380
|
+
const previous = current;
|
|
381
|
+
current = context;
|
|
382
|
+
try {
|
|
383
|
+
return fn();
|
|
384
|
+
} finally {
|
|
385
|
+
current = previous;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
// src/export/console.ts
|
|
391
|
+
function consoleExporter(options = {}) {
|
|
392
|
+
const write = options.write ?? ((line) => console.log(line));
|
|
393
|
+
const stackTraces = options.stackTraces ?? true;
|
|
394
|
+
return {
|
|
395
|
+
export(_resource, batch) {
|
|
396
|
+
for (const signal of batch) {
|
|
397
|
+
write(signal.type === "log" ? renderLog(signal) : renderSpan(signal));
|
|
398
|
+
const error = signal.error;
|
|
399
|
+
if (stackTraces && error?.stackTrace)
|
|
400
|
+
write(error.stackTrace);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function renderLog(log) {
|
|
406
|
+
return [
|
|
407
|
+
clock(log.at),
|
|
408
|
+
log.severity.toUpperCase().padEnd(5),
|
|
409
|
+
log.source,
|
|
410
|
+
log.name,
|
|
411
|
+
renderAttributes(log.attributes),
|
|
412
|
+
renderError(log.error),
|
|
413
|
+
renderTrace(log)
|
|
414
|
+
].filter(Boolean).join(" ");
|
|
415
|
+
}
|
|
416
|
+
function renderSpan(span) {
|
|
417
|
+
return [
|
|
418
|
+
clock(span.endedAt),
|
|
419
|
+
"SPAN ",
|
|
420
|
+
span.name,
|
|
421
|
+
`${span.endedAt - span.startedAt}ms`,
|
|
422
|
+
span.status === "ok" ? "" : span.status.toUpperCase(),
|
|
423
|
+
renderAttributes(span.attributes),
|
|
424
|
+
renderError(span.error),
|
|
425
|
+
renderTrace(span)
|
|
426
|
+
].filter(Boolean).join(" ");
|
|
427
|
+
}
|
|
428
|
+
function clock(at) {
|
|
429
|
+
if (!Number.isFinite(at) || Math.abs(at) > 8640000000000000)
|
|
430
|
+
return String(at);
|
|
431
|
+
return new Date(at).toISOString().slice(11, 23);
|
|
432
|
+
}
|
|
433
|
+
function renderAttributes(attributes) {
|
|
434
|
+
return Object.entries(attributes).map(([key, value]) => `${key}=${renderValue(value)}`).join(" ");
|
|
435
|
+
}
|
|
436
|
+
function renderValue(value) {
|
|
437
|
+
return Array.isArray(value) ? `[${value.join(",")}]` : String(value);
|
|
438
|
+
}
|
|
439
|
+
function renderError(error) {
|
|
440
|
+
if (error === undefined)
|
|
441
|
+
return "";
|
|
442
|
+
return error.message ? `${error.type}: ${error.message}` : error.type;
|
|
443
|
+
}
|
|
444
|
+
function renderTrace(signal) {
|
|
445
|
+
const span = signal.type === "log" ? signal.span : signal.context;
|
|
446
|
+
if (span === undefined)
|
|
447
|
+
return "";
|
|
448
|
+
return `[${span.traceId.slice(0, 8)}/${span.spanId.slice(0, 8)}]`;
|
|
449
|
+
}
|
|
450
|
+
// src/export/file.ts
|
|
451
|
+
import {
|
|
452
|
+
appendFile,
|
|
453
|
+
mkdir,
|
|
454
|
+
readdir,
|
|
455
|
+
readFile,
|
|
456
|
+
rename,
|
|
457
|
+
rm,
|
|
458
|
+
stat,
|
|
459
|
+
writeFile
|
|
460
|
+
} from "node:fs/promises";
|
|
461
|
+
import { dirname, join } from "node:path";
|
|
462
|
+
import { promisify } from "node:util";
|
|
463
|
+
import { gzip } from "node:zlib";
|
|
464
|
+
|
|
465
|
+
// src/export/rotation.ts
|
|
466
|
+
import { basename } from "node:path";
|
|
467
|
+
function rotationDue(size, openedAt, now, policy) {
|
|
468
|
+
if (size <= 0)
|
|
469
|
+
return false;
|
|
470
|
+
if (policy.maxSize > 0 && size >= policy.maxSize)
|
|
471
|
+
return true;
|
|
472
|
+
return policy.every > 0 && period(openedAt, policy) !== period(now, policy);
|
|
473
|
+
}
|
|
474
|
+
function rolledName(path, at, taken = 0) {
|
|
475
|
+
const file = basename(path);
|
|
476
|
+
const directory = path.slice(0, path.length - file.length);
|
|
477
|
+
const dot = file.lastIndexOf(".");
|
|
478
|
+
const base = dot <= 0 ? file : file.slice(0, dot);
|
|
479
|
+
const extension = dot <= 0 ? "" : file.slice(dot);
|
|
480
|
+
const suffix = taken === 0 ? "" : `-${taken}`;
|
|
481
|
+
return `${directory}${base}-${stamp(at)}${suffix}${extension}`;
|
|
482
|
+
}
|
|
483
|
+
function rolledOf(path, names) {
|
|
484
|
+
const found = new Map;
|
|
485
|
+
for (const name of names) {
|
|
486
|
+
const rolled = parse(path, name);
|
|
487
|
+
if (rolled === undefined)
|
|
488
|
+
continue;
|
|
489
|
+
const key = `${rolled.at}-${rolled.taken}`;
|
|
490
|
+
const seen = found.get(key);
|
|
491
|
+
if (seen === undefined || name.endsWith(".gz")) {
|
|
492
|
+
found.set(key, { ...rolled, name });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return [...found.values()].sort((one, other) => other.at.localeCompare(one.at) || other.taken - one.taken).map((rolled) => rolled.name);
|
|
496
|
+
}
|
|
497
|
+
function prunable(path, names, keep) {
|
|
498
|
+
const kept = new Set(rolledOf(path, names).slice(0, Math.max(keep, 0)));
|
|
499
|
+
return names.filter((name) => parse(path, name) !== undefined && !kept.has(name));
|
|
500
|
+
}
|
|
501
|
+
function period(at, policy) {
|
|
502
|
+
return Math.floor(at / policy.every);
|
|
503
|
+
}
|
|
504
|
+
function parse(path, name) {
|
|
505
|
+
const file = basename(path);
|
|
506
|
+
const dot = file.lastIndexOf(".");
|
|
507
|
+
const base = dot <= 0 ? file : file.slice(0, dot);
|
|
508
|
+
const extension = dot <= 0 ? "" : file.slice(dot);
|
|
509
|
+
const pattern = new RegExp(`^${escapeRegex(base)}-(\\d{8}-\\d{6})(?:-(\\d+))?${escapeRegex(extension)}(\\.gz)?$`);
|
|
510
|
+
const found = pattern.exec(name);
|
|
511
|
+
if (found === null)
|
|
512
|
+
return;
|
|
513
|
+
return { at: found[1], taken: Number(found[2] ?? 0) };
|
|
514
|
+
}
|
|
515
|
+
function stamp(at) {
|
|
516
|
+
const iso = new Date(at).toISOString();
|
|
517
|
+
return `${iso.slice(0, 10).replaceAll("-", "")}-${iso.slice(11, 19).replaceAll(":", "")}`;
|
|
518
|
+
}
|
|
519
|
+
function escapeRegex(text) {
|
|
520
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/export/file.ts
|
|
524
|
+
var compressed = promisify(gzip);
|
|
525
|
+
var DEFAULT_MAX_SIZE = 64 * 1024 * 1024;
|
|
526
|
+
var DEFAULT_ROTATION_PERIOD = 24 * 60 * 60 * 1000;
|
|
527
|
+
var MAX_COLLISIONS = 1000;
|
|
528
|
+
function fileExporter(options) {
|
|
529
|
+
const policy = {
|
|
530
|
+
maxSize: options.maxSize ?? DEFAULT_MAX_SIZE,
|
|
531
|
+
every: options.every ?? DEFAULT_ROTATION_PERIOD,
|
|
532
|
+
keep: options.keep ?? 7,
|
|
533
|
+
compress: options.compress ?? false
|
|
534
|
+
};
|
|
535
|
+
const now = options.now ?? Date.now;
|
|
536
|
+
const path = options.path;
|
|
537
|
+
let state;
|
|
538
|
+
let queue = Promise.resolve();
|
|
539
|
+
const write = async (batch) => {
|
|
540
|
+
const text = render2(batch);
|
|
541
|
+
if (text.length === 0)
|
|
542
|
+
return;
|
|
543
|
+
try {
|
|
544
|
+
state ??= await currentState(path, now());
|
|
545
|
+
if (rotationDue(state.size, state.openedAt, now(), policy)) {
|
|
546
|
+
await roll(path, now(), policy);
|
|
547
|
+
state = { size: 0, openedAt: now() };
|
|
548
|
+
}
|
|
549
|
+
await appendFile(path, text, "utf8");
|
|
550
|
+
state.size += Buffer.byteLength(text, "utf8");
|
|
551
|
+
} catch (failure) {
|
|
552
|
+
state = undefined;
|
|
553
|
+
throw failure;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
return {
|
|
557
|
+
export(_resource, batch) {
|
|
558
|
+
const next = queue.then(() => write(batch));
|
|
559
|
+
queue = next.catch(() => {
|
|
560
|
+
return;
|
|
561
|
+
});
|
|
562
|
+
return next;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function render2(batch) {
|
|
567
|
+
let text = "";
|
|
568
|
+
for (const signal of batch) {
|
|
569
|
+
try {
|
|
570
|
+
text += `${JSON.stringify(signal)}
|
|
571
|
+
`;
|
|
572
|
+
} catch {}
|
|
573
|
+
}
|
|
574
|
+
return text;
|
|
575
|
+
}
|
|
576
|
+
async function currentState(path, fallback) {
|
|
577
|
+
await mkdir(dirname(path), { recursive: true });
|
|
578
|
+
try {
|
|
579
|
+
const found = await stat(path);
|
|
580
|
+
return { size: found.size, openedAt: found.mtimeMs };
|
|
581
|
+
} catch {
|
|
582
|
+
return { size: 0, openedAt: fallback };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async function roll(path, at, policy) {
|
|
586
|
+
const target = await freeName(path, at);
|
|
587
|
+
await rename(path, target);
|
|
588
|
+
if (policy.compress)
|
|
589
|
+
await compress(target);
|
|
590
|
+
await prune(path, policy.keep);
|
|
591
|
+
}
|
|
592
|
+
async function freeName(path, at) {
|
|
593
|
+
for (let taken = 0;taken < MAX_COLLISIONS; taken++) {
|
|
594
|
+
const candidate = rolledName(path, at, taken);
|
|
595
|
+
if (!await exists(candidate) && !await exists(`${candidate}.gz`)) {
|
|
596
|
+
return candidate;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
throw new Error(`[telemetry] ${MAX_COLLISIONS} archives of ${path} already share this second`);
|
|
600
|
+
}
|
|
601
|
+
async function compress(path) {
|
|
602
|
+
const pending = `${path}.gz.pending`;
|
|
603
|
+
await writeFile(pending, await compressed(await readFile(path)));
|
|
604
|
+
await rename(pending, `${path}.gz`);
|
|
605
|
+
await rm(path, { force: true });
|
|
606
|
+
}
|
|
607
|
+
async function prune(path, keep) {
|
|
608
|
+
const directory = dirname(path);
|
|
609
|
+
const names = await readdir(directory);
|
|
610
|
+
for (const name of prunable(path, names, keep)) {
|
|
611
|
+
await rm(join(directory, name), { force: true });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
async function exists(path) {
|
|
615
|
+
return stat(path).then(() => true, () => false);
|
|
616
|
+
}
|
|
617
|
+
// src/export/json-lines.ts
|
|
618
|
+
function jsonLinesExporter(options = {}) {
|
|
619
|
+
const write = options.write ?? ((line) => console.log(line));
|
|
620
|
+
return {
|
|
621
|
+
export(_resource, batch) {
|
|
622
|
+
for (const signal of batch) {
|
|
623
|
+
const line = render3(signal);
|
|
624
|
+
if (line !== undefined)
|
|
625
|
+
write(line);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function render3(signal) {
|
|
631
|
+
try {
|
|
632
|
+
return JSON.stringify(signal);
|
|
633
|
+
} catch {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// src/logger/event.ts
|
|
638
|
+
var INVALID_EVENT_ATTRIBUTE = "telemetry.event.invalid";
|
|
639
|
+
function event(name, schema) {
|
|
640
|
+
return (input) => {
|
|
641
|
+
if (schema === undefined)
|
|
642
|
+
return { name, attributes: attributesOf(input) };
|
|
643
|
+
let result;
|
|
644
|
+
try {
|
|
645
|
+
result = schema["~standard"].validate(input);
|
|
646
|
+
} catch {
|
|
647
|
+
return invalid(name, input);
|
|
648
|
+
}
|
|
649
|
+
if (isThenable(result)) {
|
|
650
|
+
Promise.resolve(result).catch(() => {
|
|
651
|
+
return;
|
|
652
|
+
});
|
|
653
|
+
return invalid(name, input);
|
|
654
|
+
}
|
|
655
|
+
const checked = result;
|
|
656
|
+
if (checked.issues !== undefined)
|
|
657
|
+
return invalid(name, input);
|
|
658
|
+
return { name, attributes: fields(checked.value) };
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function isTelemetryEvent(value) {
|
|
662
|
+
return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.attributes === "object";
|
|
663
|
+
}
|
|
664
|
+
function invalid(name, input) {
|
|
665
|
+
return {
|
|
666
|
+
name,
|
|
667
|
+
attributes: { ...attributesOf(input), [INVALID_EVENT_ATTRIBUTE]: true },
|
|
668
|
+
invalid: true
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
function fields(value) {
|
|
672
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
673
|
+
return attributesOf(value);
|
|
674
|
+
}
|
|
675
|
+
return { value: coerceAttribute(value) };
|
|
676
|
+
}
|
|
677
|
+
function isThenable(value) {
|
|
678
|
+
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
679
|
+
}
|
|
680
|
+
// src/model/error.ts
|
|
681
|
+
function errorInfo(failure, stackTraces = true) {
|
|
682
|
+
try {
|
|
683
|
+
return flatten(failure, stackTraces);
|
|
684
|
+
} catch {
|
|
685
|
+
return { type: "Error", message: UNREADABLE };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function isAbort(failure) {
|
|
689
|
+
if (typeof failure !== "object" || failure === null)
|
|
690
|
+
return false;
|
|
691
|
+
const name = read(failure, "name");
|
|
692
|
+
return name === "AbortError" || name === "TimeoutError";
|
|
693
|
+
}
|
|
694
|
+
function flatten(failure, stackTraces) {
|
|
695
|
+
if (isError(failure)) {
|
|
696
|
+
const message = read(failure, "message");
|
|
697
|
+
const stack = stackTraces ? read(failure, "stack") : undefined;
|
|
698
|
+
return {
|
|
699
|
+
type: errorType(failure),
|
|
700
|
+
...typeof message === "string" && message ? { message } : {},
|
|
701
|
+
...typeof stack === "string" && stack ? { stackTrace: stack } : {}
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return { type: typeName(failure), message: describe(failure) };
|
|
705
|
+
}
|
|
706
|
+
function isError(value) {
|
|
707
|
+
try {
|
|
708
|
+
if (value instanceof Error)
|
|
709
|
+
return true;
|
|
710
|
+
return typeof value === "object" && value !== null && Object.prototype.toString.call(value) === "[object Error]";
|
|
711
|
+
} catch {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function errorType(failure) {
|
|
716
|
+
const name = read(failure, "name");
|
|
717
|
+
const named = typeof name === "string" && name ? name : undefined;
|
|
718
|
+
if (named !== undefined && named !== "Error")
|
|
719
|
+
return named;
|
|
720
|
+
const constructed = constructorName(failure);
|
|
721
|
+
if (constructed !== undefined && constructed !== "Object")
|
|
722
|
+
return constructed;
|
|
723
|
+
return named ?? "Error";
|
|
724
|
+
}
|
|
725
|
+
function typeName(failure) {
|
|
726
|
+
if (failure === null)
|
|
727
|
+
return "null";
|
|
728
|
+
if (typeof failure !== "object")
|
|
729
|
+
return typeof failure;
|
|
730
|
+
return constructorName(failure) ?? "Object";
|
|
731
|
+
}
|
|
732
|
+
function constructorName(value) {
|
|
733
|
+
const name = read(value, "constructor");
|
|
734
|
+
if (typeof name !== "function" && typeof name !== "object")
|
|
735
|
+
return;
|
|
736
|
+
if (name === null)
|
|
737
|
+
return;
|
|
738
|
+
const own = read(name, "name");
|
|
739
|
+
return typeof own === "string" && own ? own : undefined;
|
|
740
|
+
}
|
|
741
|
+
function read(value, key) {
|
|
742
|
+
try {
|
|
743
|
+
return value[key];
|
|
744
|
+
} catch {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function describe(failure) {
|
|
749
|
+
try {
|
|
750
|
+
return typeof failure === "object" && failure !== null ? JSON.stringify(failure) || String(failure) : String(failure);
|
|
751
|
+
} catch {
|
|
752
|
+
return UNREADABLE;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/model/signal.ts
|
|
757
|
+
var SEVERITY_NUMBER = Object.freeze({
|
|
758
|
+
debug: 5,
|
|
759
|
+
info: 9,
|
|
760
|
+
warn: 13,
|
|
761
|
+
error: 17
|
|
762
|
+
});
|
|
763
|
+
var SEVERITIES = Object.freeze([
|
|
764
|
+
"debug",
|
|
765
|
+
"info",
|
|
766
|
+
"warn",
|
|
767
|
+
"error"
|
|
768
|
+
]);
|
|
769
|
+
function meetsSeverity(severity, minimum) {
|
|
770
|
+
return SEVERITY_NUMBER[severity] >= SEVERITY_NUMBER[minimum];
|
|
771
|
+
}
|
|
772
|
+
var SPAN_KIND_NUMBER = Object.freeze({
|
|
773
|
+
internal: 1,
|
|
774
|
+
server: 2,
|
|
775
|
+
client: 3,
|
|
776
|
+
producer: 4,
|
|
777
|
+
consumer: 5
|
|
778
|
+
});
|
|
779
|
+
function signalAt(signal) {
|
|
780
|
+
return signal.type === "log" ? signal.at : signal.startedAt;
|
|
781
|
+
}
|
|
782
|
+
function signalSpan(signal) {
|
|
783
|
+
return signal.type === "log" ? signal.span : signal.context;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/logger/logger.ts
|
|
787
|
+
function createLogger(source) {
|
|
788
|
+
const write = (severity, input, second, third) => {
|
|
789
|
+
const telemetry = resolveTelemetry();
|
|
790
|
+
if (telemetry === undefined)
|
|
791
|
+
return;
|
|
792
|
+
if (!meetsSeverity(severity, telemetry.minimum))
|
|
793
|
+
return;
|
|
794
|
+
const [failure, fields] = split(second, third);
|
|
795
|
+
const span = currentSpan();
|
|
796
|
+
const named = name(input);
|
|
797
|
+
const record = {
|
|
798
|
+
type: "log",
|
|
799
|
+
at: Date.now(),
|
|
800
|
+
severity,
|
|
801
|
+
name: named.name,
|
|
802
|
+
source,
|
|
803
|
+
attributes: mergeAttributes(mergeAttributes(currentAttributes(), named.attributes), attributesOf(fields)),
|
|
804
|
+
...span === undefined ? {} : { span },
|
|
805
|
+
...failure === undefined ? {} : { error: errorInfo(failure, telemetry.stackTraces) }
|
|
806
|
+
};
|
|
807
|
+
telemetry.emit(record);
|
|
808
|
+
};
|
|
809
|
+
return {
|
|
810
|
+
enabled(severity) {
|
|
811
|
+
const telemetry = resolveTelemetry();
|
|
812
|
+
return telemetry !== undefined && meetsSeverity(severity, telemetry.minimum);
|
|
813
|
+
},
|
|
814
|
+
debug: (input, attributes) => write("debug", input, attributes),
|
|
815
|
+
info: (input, attributes) => write("info", input, attributes),
|
|
816
|
+
warn: (input, second, third) => write("warn", input, second, third),
|
|
817
|
+
error: (input, second, third) => write("error", input, second, third)
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function split(second, third) {
|
|
821
|
+
if (third !== undefined)
|
|
822
|
+
return [second, third];
|
|
823
|
+
if (second === undefined)
|
|
824
|
+
return [undefined, undefined];
|
|
825
|
+
return isFields(second) ? [undefined, second] : [second, undefined];
|
|
826
|
+
}
|
|
827
|
+
function isFields(value) {
|
|
828
|
+
if (typeof value !== "object" || value === null)
|
|
829
|
+
return false;
|
|
830
|
+
try {
|
|
831
|
+
if (value instanceof Error || Array.isArray(value))
|
|
832
|
+
return false;
|
|
833
|
+
const prototype = Object.getPrototypeOf(value);
|
|
834
|
+
if (prototype === null || prototype === Object.prototype)
|
|
835
|
+
return true;
|
|
836
|
+
return Object.getPrototypeOf(prototype) === null && prototype.constructor?.name === "Object";
|
|
837
|
+
} catch {
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function name(input) {
|
|
842
|
+
if (typeof input === "string") {
|
|
843
|
+
return { name: input, attributes: EMPTY_ATTRIBUTES };
|
|
844
|
+
}
|
|
845
|
+
if (isTelemetryEvent(input)) {
|
|
846
|
+
return { name: input.name, attributes: input.attributes };
|
|
847
|
+
}
|
|
848
|
+
try {
|
|
849
|
+
return { name: input(), attributes: EMPTY_ATTRIBUTES };
|
|
850
|
+
} catch (failure) {
|
|
851
|
+
return {
|
|
852
|
+
name: "[message failed to build]",
|
|
853
|
+
attributes: {
|
|
854
|
+
"telemetry.message.invalid": errorInfo(failure, false).type
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
// src/span/scope.ts
|
|
860
|
+
class Span {
|
|
861
|
+
constructor(context, name, kind, stackTraces = true) {
|
|
862
|
+
this.status = "ok";
|
|
863
|
+
this.own = {};
|
|
864
|
+
this.happened = [];
|
|
865
|
+
this.context = context;
|
|
866
|
+
this.name = name;
|
|
867
|
+
this.kind = kind;
|
|
868
|
+
this.stackTraces = stackTraces;
|
|
869
|
+
}
|
|
870
|
+
get traceId() {
|
|
871
|
+
return this.context.traceId;
|
|
872
|
+
}
|
|
873
|
+
get spanId() {
|
|
874
|
+
return this.context.spanId;
|
|
875
|
+
}
|
|
876
|
+
traceparent() {
|
|
877
|
+
return renderTraceparent(this.context);
|
|
878
|
+
}
|
|
879
|
+
attribute(name, value) {
|
|
880
|
+
this.own[name] = coerceAttribute(value);
|
|
881
|
+
}
|
|
882
|
+
attributes(record) {
|
|
883
|
+
Object.assign(this.own, attributesOf(record));
|
|
884
|
+
}
|
|
885
|
+
event(input, attributes) {
|
|
886
|
+
const declared = isTelemetryEvent(input);
|
|
887
|
+
this.happened.push({
|
|
888
|
+
name: declared ? input.name : input,
|
|
889
|
+
at: Date.now(),
|
|
890
|
+
attributes: declared ? input.attributes : attributesOf(attributes)
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
fail(failure) {
|
|
894
|
+
if (this.error !== undefined)
|
|
895
|
+
return;
|
|
896
|
+
const cancelled = isAbort(failure);
|
|
897
|
+
this.status = cancelled ? "cancelled" : "error";
|
|
898
|
+
this.error = errorInfo(failure, !cancelled && this.stackTraces);
|
|
899
|
+
}
|
|
900
|
+
record(startedAt, endedAt, parent, inherited) {
|
|
901
|
+
return {
|
|
902
|
+
type: "span",
|
|
903
|
+
name: this.name,
|
|
904
|
+
context: this.context,
|
|
905
|
+
...parent === undefined ? {} : { parent },
|
|
906
|
+
kind: this.kind,
|
|
907
|
+
startedAt,
|
|
908
|
+
endedAt,
|
|
909
|
+
status: this.status,
|
|
910
|
+
attributes: { ...mergeAttributes(inherited, this.own) },
|
|
911
|
+
events: [...this.happened],
|
|
912
|
+
...this.error === undefined ? {} : { error: this.error }
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
// src/span/span.ts
|
|
917
|
+
function span(name, optionsOrBlock, maybeBlock) {
|
|
918
|
+
const [options, block] = read2(optionsOrBlock, maybeBlock);
|
|
919
|
+
return open(name, options, block, undefined, "internal");
|
|
920
|
+
}
|
|
921
|
+
function continuing(traceparent, name, optionsOrBlock, maybeBlock) {
|
|
922
|
+
const [options, block] = read2(optionsOrBlock, maybeBlock);
|
|
923
|
+
const remote = parseTraceparent(traceparent) ?? undefined;
|
|
924
|
+
return open(name, options, block, remote, "server");
|
|
925
|
+
}
|
|
926
|
+
async function open(name, options, block, remote, fallbackKind) {
|
|
927
|
+
const parent = currentContext();
|
|
928
|
+
const telemetry = parent?.telemetry ?? installedTelemetry();
|
|
929
|
+
const kind = options.kind ?? fallbackKind;
|
|
930
|
+
if (telemetry === undefined) {
|
|
931
|
+
return block(new Span(DETACHED_SPAN_CONTEXT, name, kind));
|
|
932
|
+
}
|
|
933
|
+
const parentSpan = remote ?? parent?.span;
|
|
934
|
+
const traceId = parentSpan?.traceId ?? randomTraceId();
|
|
935
|
+
const context = {
|
|
936
|
+
traceId,
|
|
937
|
+
spanId: randomSpanId(),
|
|
938
|
+
sampled: parentSpan?.sampled ?? decide(telemetry, traceId),
|
|
939
|
+
remote: false
|
|
940
|
+
};
|
|
941
|
+
const inherited = mergeAttributes(parent?.attributes ?? EMPTY_ATTRIBUTES, attributesOf(options.attributes));
|
|
942
|
+
const scope = new Span(context, name, kind, telemetry.stackTraces);
|
|
943
|
+
const startedAt = Date.now();
|
|
944
|
+
try {
|
|
945
|
+
return await runWithContext({ telemetry, span: context, attributes: inherited }, () => block(scope));
|
|
946
|
+
} catch (failure) {
|
|
947
|
+
try {
|
|
948
|
+
scope.fail(failure);
|
|
949
|
+
} catch {
|
|
950
|
+
scope.status = "error";
|
|
951
|
+
}
|
|
952
|
+
throw failure;
|
|
953
|
+
} finally {
|
|
954
|
+
if (context.sampled) {
|
|
955
|
+
telemetry.emit(scope.record(startedAt, Date.now(), parentSpan?.spanId, inherited));
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function read2(optionsOrBlock, maybeBlock) {
|
|
960
|
+
return typeof optionsOrBlock === "function" ? [{}, optionsOrBlock] : [optionsOrBlock, maybeBlock];
|
|
961
|
+
}
|
|
962
|
+
function decide(telemetry, traceId) {
|
|
963
|
+
try {
|
|
964
|
+
return telemetry.sampler.sample(traceId);
|
|
965
|
+
} catch {
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
189
969
|
export {
|
|
970
|
+
DEFAULT_MAX_SIZE,
|
|
971
|
+
DEFAULT_ROTATION_PERIOD,
|
|
190
972
|
DETACHED_SPAN_CONTEXT,
|
|
191
973
|
EMPTY_ATTRIBUTES,
|
|
974
|
+
INVALID_EVENT_ATTRIBUTE,
|
|
192
975
|
INVALID_SPAN_ID,
|
|
193
976
|
INVALID_TRACE_ID,
|
|
194
977
|
SEVERITIES,
|
|
195
978
|
SEVERITY_NUMBER,
|
|
196
979
|
SPAN_KIND_NUMBER,
|
|
980
|
+
Span,
|
|
981
|
+
TELEMETRY_DEFAULTS,
|
|
982
|
+
Telemetry,
|
|
983
|
+
UNREADABLE,
|
|
197
984
|
alwaysSample,
|
|
198
985
|
attributesOf,
|
|
199
986
|
coerceAttribute,
|
|
987
|
+
consoleExporter,
|
|
988
|
+
continuing,
|
|
989
|
+
createLogger,
|
|
990
|
+
createTelemetry,
|
|
991
|
+
currentAttributes,
|
|
992
|
+
currentContext,
|
|
993
|
+
currentSpan,
|
|
994
|
+
currentTraceparent,
|
|
995
|
+
errorInfo,
|
|
996
|
+
event,
|
|
997
|
+
fileExporter,
|
|
998
|
+
installedTelemetry,
|
|
999
|
+
isAbort,
|
|
200
1000
|
isDetached,
|
|
201
1001
|
isEmptyAttributes,
|
|
1002
|
+
isTelemetryEvent,
|
|
202
1003
|
isValidSpanId,
|
|
203
1004
|
isValidTraceId,
|
|
1005
|
+
jsonLinesExporter,
|
|
204
1006
|
meetsSeverity,
|
|
205
1007
|
mergeAttributes,
|
|
206
1008
|
neverSample,
|
|
207
1009
|
parseTraceparent,
|
|
1010
|
+
prunable,
|
|
208
1011
|
randomSpanId,
|
|
209
1012
|
randomTraceId,
|
|
210
1013
|
ratioSampler,
|
|
211
1014
|
renderTraceparent,
|
|
1015
|
+
resolveTelemetry,
|
|
1016
|
+
rolledName,
|
|
1017
|
+
rolledOf,
|
|
1018
|
+
rotationDue,
|
|
1019
|
+
runWithContext,
|
|
212
1020
|
signalAt,
|
|
213
|
-
signalSpan
|
|
1021
|
+
signalSpan,
|
|
1022
|
+
span,
|
|
1023
|
+
uninstallTelemetry,
|
|
1024
|
+
withAttributes,
|
|
1025
|
+
withTelemetry
|
|
214
1026
|
};
|
|
215
1027
|
|
|
216
|
-
//# debugId=
|
|
1028
|
+
//# debugId=48D06AD1E41A02B464756E2164756E21
|
|
217
1029
|
//# sourceMappingURL=index.js.map
|