@dxos/tracing 0.8.4-main.ae835ea → 0.8.4-main.bc2380dfbc
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 +102 -5
- package/dist/lib/browser/index.mjs +331 -393
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +331 -393
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/api.d.ts +36 -17
- package/dist/types/src/api.d.ts.map +1 -1
- package/dist/types/src/buffering-backend.d.ts +24 -0
- package/dist/types/src/buffering-backend.d.ts.map +1 -0
- package/dist/types/src/diagnostic.d.ts +2 -2
- package/dist/types/src/diagnostic.d.ts.map +1 -1
- package/dist/types/src/diagnostics-channel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/metrics/base.d.ts.map +1 -1
- package/dist/types/src/metrics/custom-counter.d.ts.map +1 -1
- package/dist/types/src/metrics/map-counter.d.ts.map +1 -1
- package/dist/types/src/metrics/time-series-counter.d.ts.map +1 -1
- package/dist/types/src/metrics/time-usage-counter.d.ts.map +1 -1
- package/dist/types/src/metrics/unary-counter.d.ts.map +1 -1
- package/dist/types/src/remote/index.d.ts +0 -1
- package/dist/types/src/remote/index.d.ts.map +1 -1
- package/dist/types/src/remote/metrics.d.ts.map +1 -1
- package/dist/types/src/symbols.d.ts +0 -1
- package/dist/types/src/symbols.d.ts.map +1 -1
- package/dist/types/src/trace-processor.d.ts +16 -52
- package/dist/types/src/trace-processor.d.ts.map +1 -1
- package/dist/types/src/tracing-types.d.ts +67 -0
- package/dist/types/src/tracing-types.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -13
- package/src/api.ts +237 -35
- package/src/buffering-backend.ts +112 -0
- package/src/diagnostic.ts +2 -2
- package/src/index.ts +1 -2
- package/src/remote/index.ts +0 -1
- package/src/symbols.ts +0 -2
- package/src/trace-processor.ts +58 -258
- package/src/tracing-types.ts +77 -0
- package/src/tracing.test.ts +513 -4
- package/dist/types/src/remote/tracing.d.ts +0 -23
- package/dist/types/src/remote/tracing.d.ts.map +0 -1
- package/dist/types/src/trace-sender.d.ts +0 -9
- package/dist/types/src/trace-sender.d.ts.map +0 -1
- package/src/remote/tracing.ts +0 -53
- package/src/trace-sender.ts +0 -88
package/src/trace-processor.ts
CHANGED
|
@@ -2,45 +2,32 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { type Context } from '@dxos/context';
|
|
7
|
-
import { LogLevel, type LogProcessor, getContextFromEntry, log } from '@dxos/log';
|
|
5
|
+
import { LogLevel, type LogProcessor, log } from '@dxos/log';
|
|
8
6
|
import { type LogEntry } from '@dxos/protocols/proto/dxos/client/services';
|
|
9
|
-
import { type
|
|
10
|
-
import { type Metric, type Resource, type Span } from '@dxos/protocols/proto/dxos/tracing';
|
|
7
|
+
import { type Metric, type Resource } from '@dxos/protocols/proto/dxos/tracing';
|
|
11
8
|
import { getPrototypeSpecificInstanceId } from '@dxos/util';
|
|
12
9
|
|
|
13
10
|
import type { AddLinkOptions, TimeAware } from './api';
|
|
11
|
+
import { BUFFERED_PREFIX, BufferingTracingBackend } from './buffering-backend';
|
|
14
12
|
import { DiagnosticsManager } from './diagnostic';
|
|
15
13
|
import { DiagnosticsChannel } from './diagnostics-channel';
|
|
16
14
|
import { type BaseCounter } from './metrics';
|
|
17
|
-
import { RemoteMetrics
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
15
|
+
import { RemoteMetrics } from './remote/metrics';
|
|
16
|
+
import { getTracingContext } from './symbols';
|
|
17
|
+
import type { RemoteSpan, StartSpanOptions, TracingBackend } from './tracing-types';
|
|
20
18
|
import { WeakRef } from './weak-ref';
|
|
21
19
|
|
|
22
20
|
export type Diagnostics = {
|
|
23
21
|
resources: Record<string, Resource>;
|
|
24
|
-
spans: Span[];
|
|
25
22
|
logs: LogEntry[];
|
|
26
23
|
};
|
|
27
24
|
|
|
28
|
-
export type
|
|
25
|
+
export type TraceResourceConstructorProps = {
|
|
29
26
|
constructor: { new (...args: any[]): {} };
|
|
30
27
|
instance: any;
|
|
31
28
|
annotation?: symbol;
|
|
32
29
|
};
|
|
33
30
|
|
|
34
|
-
export type TraceSpanParams = {
|
|
35
|
-
instance: any;
|
|
36
|
-
// TODO(wittjosiah): Rename to `name`.
|
|
37
|
-
methodName: string;
|
|
38
|
-
parentCtx: Context | null;
|
|
39
|
-
showInBrowserTimeline: boolean;
|
|
40
|
-
op?: string;
|
|
41
|
-
attributes?: Record<string, any>;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
31
|
export class ResourceEntry {
|
|
45
32
|
/**
|
|
46
33
|
* Sometimes bundlers mangle class names: WebFile -> WebFile2.
|
|
@@ -62,39 +49,57 @@ export class ResourceEntry {
|
|
|
62
49
|
}
|
|
63
50
|
}
|
|
64
51
|
|
|
65
|
-
export type TraceSubscription = {
|
|
66
|
-
flush: () => void;
|
|
67
|
-
|
|
68
|
-
dirtyResources: Set<number>;
|
|
69
|
-
dirtySpans: Set<number>;
|
|
70
|
-
newLogs: LogEntry[];
|
|
71
|
-
};
|
|
72
|
-
|
|
73
52
|
const MAX_RESOURCE_RECORDS = 2_000;
|
|
74
|
-
const MAX_SPAN_RECORDS = 1_000;
|
|
75
53
|
const MAX_LOG_RECORDS = 1_000;
|
|
76
54
|
|
|
77
|
-
const REFRESH_INTERVAL = 1_000;
|
|
78
|
-
|
|
79
55
|
const MAX_INFO_OBJECT_DEPTH = 8;
|
|
80
56
|
|
|
81
|
-
const IS_CLOUDFLARE_WORKERS = !!globalThis?.navigator?.userAgent?.includes('Cloudflare-Workers');
|
|
82
|
-
|
|
83
57
|
export class TraceProcessor {
|
|
84
58
|
public readonly diagnostics = new DiagnosticsManager();
|
|
85
59
|
public readonly diagnosticsChannel = new DiagnosticsChannel();
|
|
86
60
|
public readonly remoteMetrics = new RemoteMetrics();
|
|
87
|
-
public readonly remoteTracing = new RemoteTracing();
|
|
88
61
|
|
|
89
|
-
readonly
|
|
62
|
+
readonly #bufferingBackend = new BufferingTracingBackend();
|
|
63
|
+
#activeBackend: TracingBackend = this.#bufferingBackend;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Tracing backend. Initially a buffering backend that records spans;
|
|
67
|
+
* once the observability package sets a real backend, the buffer is drained
|
|
68
|
+
* and a thin translating wrapper is installed that resolves stale buffered
|
|
69
|
+
* parent IDs still held by in-flight {@link Context} objects.
|
|
70
|
+
*
|
|
71
|
+
* The wrapper only allocates when a `buffered-*` parent is actually encountered;
|
|
72
|
+
* the common path is a single `startsWith` check and direct passthrough.
|
|
73
|
+
*/
|
|
74
|
+
get tracingBackend(): TracingBackend {
|
|
75
|
+
return this.#activeBackend;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
set tracingBackend(backend: TracingBackend | undefined) {
|
|
79
|
+
if (!backend || backend === this.#bufferingBackend) {
|
|
80
|
+
this.#bufferingBackend.clear();
|
|
81
|
+
this.#activeBackend = this.#bufferingBackend;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const idMap = this.#bufferingBackend.drain(backend);
|
|
85
|
+
this.#activeBackend = {
|
|
86
|
+
startSpan: (options: StartSpanOptions): RemoteSpan => {
|
|
87
|
+
const parent = options.parentContext;
|
|
88
|
+
if (parent?.traceparent.startsWith(BUFFERED_PREFIX)) {
|
|
89
|
+
const translated = idMap.get(parent.traceparent);
|
|
90
|
+
if (translated) {
|
|
91
|
+
return backend.startSpan({ ...options, parentContext: translated });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return backend.startSpan(options);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
90
98
|
|
|
91
99
|
readonly resources = new Map<number, ResourceEntry>();
|
|
92
100
|
readonly resourceInstanceIndex = new WeakMap<any, ResourceEntry>();
|
|
93
101
|
readonly resourceIdList: number[] = [];
|
|
94
102
|
|
|
95
|
-
readonly spans = new Map<number, Span>();
|
|
96
|
-
readonly spanIdList: number[] = [];
|
|
97
|
-
|
|
98
103
|
readonly logs: LogEntry[] = [];
|
|
99
104
|
|
|
100
105
|
private _instanceTag: string | null = null;
|
|
@@ -102,11 +107,6 @@ export class TraceProcessor {
|
|
|
102
107
|
constructor() {
|
|
103
108
|
log.addProcessor(this._logProcessor.bind(this));
|
|
104
109
|
|
|
105
|
-
if (!IS_CLOUDFLARE_WORKERS) {
|
|
106
|
-
const refreshInterval = setInterval(this.refresh.bind(this), REFRESH_INTERVAL);
|
|
107
|
-
unrefTimeout(refreshInterval);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
110
|
if (DiagnosticsChannel.supported) {
|
|
111
111
|
this.diagnosticsChannel.serve(this.diagnostics);
|
|
112
112
|
}
|
|
@@ -118,14 +118,10 @@ export class TraceProcessor {
|
|
|
118
118
|
this.diagnostics.setInstanceTag(tag);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/**
|
|
122
|
-
|
|
123
|
-
*/
|
|
124
|
-
// TODO(burdon): Comment.
|
|
125
|
-
createTraceResource(params: TraceResourceConstructorParams): void {
|
|
121
|
+
/** @internal */
|
|
122
|
+
createTraceResource(params: TraceResourceConstructorProps): void {
|
|
126
123
|
const id = this.resources.size;
|
|
127
124
|
|
|
128
|
-
// Init metrics counters.
|
|
129
125
|
const tracingContext = getTracingContext(Object.getPrototypeOf(params.instance));
|
|
130
126
|
for (const key of Object.keys(tracingContext.metricsProperties)) {
|
|
131
127
|
(params.instance[key] as BaseCounter)._assign(params.instance, key);
|
|
@@ -150,29 +146,11 @@ export class TraceProcessor {
|
|
|
150
146
|
if (this.resourceIdList.length > MAX_RESOURCE_RECORDS) {
|
|
151
147
|
this._clearResources();
|
|
152
148
|
}
|
|
153
|
-
|
|
154
|
-
this._markResourceDirty(id);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
createTraceSender(): TraceSender {
|
|
158
|
-
return new TraceSender(this);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
traceSpan(params: TraceSpanParams): TracingSpan {
|
|
162
|
-
const span = new TracingSpan(this, params);
|
|
163
|
-
this._flushSpan(span);
|
|
164
|
-
return span;
|
|
165
149
|
}
|
|
166
150
|
|
|
167
151
|
// TODO(burdon): Not implemented.
|
|
168
152
|
addLink(parent: any, child: any, opts: AddLinkOptions): void {}
|
|
169
153
|
|
|
170
|
-
//
|
|
171
|
-
// Getters
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
// TODO(burdon): Define type.
|
|
175
|
-
// TODO(burdon): Reconcile with system service.
|
|
176
154
|
getDiagnostics(): Diagnostics {
|
|
177
155
|
this.refresh();
|
|
178
156
|
|
|
@@ -183,7 +161,6 @@ export class TraceProcessor {
|
|
|
183
161
|
entry.data,
|
|
184
162
|
]),
|
|
185
163
|
),
|
|
186
|
-
spans: Array.from(this.spans.values()),
|
|
187
164
|
logs: this.logs.filter((log) => log.level >= LogLevel.INFO),
|
|
188
165
|
};
|
|
189
166
|
}
|
|
@@ -250,81 +227,23 @@ export class TraceProcessor {
|
|
|
250
227
|
(instance[key] as BaseCounter)._tick?.(time);
|
|
251
228
|
}
|
|
252
229
|
|
|
253
|
-
let _changed = false;
|
|
254
|
-
|
|
255
|
-
const oldInfo = resource.data.info;
|
|
256
230
|
resource.data.info = this.getResourceInfo(instance);
|
|
257
|
-
_changed ||= !areEqualShallow(oldInfo, resource.data.info);
|
|
258
|
-
|
|
259
|
-
const oldMetrics = resource.data.metrics;
|
|
260
231
|
resource.data.metrics = this.getResourceMetrics(instance);
|
|
261
|
-
_changed ||= !areEqualShallow(oldMetrics, resource.data.metrics);
|
|
262
|
-
|
|
263
|
-
// TODO(dmaretskyi): Test if works and enable.
|
|
264
|
-
// if (changed) {
|
|
265
|
-
this._markResourceDirty(resource.data.id);
|
|
266
|
-
// }
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
for (const subscription of this.subscriptions) {
|
|
270
|
-
subscription.flush();
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
// Implementation
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* @internal
|
|
280
|
-
*/
|
|
281
|
-
_flushSpan(runtimeSpan: TracingSpan): void {
|
|
282
|
-
const span = runtimeSpan.serialize();
|
|
283
|
-
this.spans.set(span.id, span);
|
|
284
|
-
this.spanIdList.push(span.id);
|
|
285
|
-
if (this.spanIdList.length > MAX_SPAN_RECORDS) {
|
|
286
|
-
this._clearSpans();
|
|
287
|
-
}
|
|
288
|
-
this._markSpanDirty(span.id);
|
|
289
|
-
this.remoteTracing.flushSpan(runtimeSpan);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
private _markResourceDirty(id: number): void {
|
|
293
|
-
for (const subscription of this.subscriptions) {
|
|
294
|
-
subscription.dirtyResources.add(id);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private _markSpanDirty(id: number): void {
|
|
299
|
-
for (const subscription of this.subscriptions) {
|
|
300
|
-
subscription.dirtySpans.add(id);
|
|
301
232
|
}
|
|
302
233
|
}
|
|
303
234
|
|
|
304
235
|
private _clearResources(): void {
|
|
305
|
-
// TODO(dmaretskyi): Use FinalizationRegistry to delete finalized resources first.
|
|
306
236
|
while (this.resourceIdList.length > MAX_RESOURCE_RECORDS) {
|
|
307
237
|
const id = this.resourceIdList.shift()!;
|
|
308
238
|
this.resources.delete(id);
|
|
309
239
|
}
|
|
310
240
|
}
|
|
311
241
|
|
|
312
|
-
private _clearSpans(): void {
|
|
313
|
-
while (this.spanIdList.length > MAX_SPAN_RECORDS) {
|
|
314
|
-
const id = this.spanIdList.shift()!;
|
|
315
|
-
this.spans.delete(id);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
242
|
private _pushLog(log: LogEntry): void {
|
|
320
243
|
this.logs.push(log);
|
|
321
244
|
if (this.logs.length > MAX_LOG_RECORDS) {
|
|
322
245
|
this.logs.shift();
|
|
323
246
|
}
|
|
324
|
-
|
|
325
|
-
for (const subscription of this.subscriptions) {
|
|
326
|
-
subscription.newLogs.push(log);
|
|
327
|
-
}
|
|
328
247
|
}
|
|
329
248
|
|
|
330
249
|
private _logProcessor: LogProcessor = (config, entry) => {
|
|
@@ -338,19 +257,20 @@ export class TraceProcessor {
|
|
|
338
257
|
return;
|
|
339
258
|
}
|
|
340
259
|
|
|
341
|
-
const context =
|
|
342
|
-
|
|
343
|
-
context
|
|
260
|
+
const context: Record<string, any> = { ...entry.computedContext };
|
|
261
|
+
if (entry.computedError !== undefined) {
|
|
262
|
+
context.error = entry.computedError;
|
|
344
263
|
}
|
|
345
264
|
|
|
265
|
+
const { filename, line } = entry.computedMeta;
|
|
346
266
|
const entryToPush: LogEntry = {
|
|
347
267
|
level: entry.level,
|
|
348
|
-
message: entry.message ??
|
|
268
|
+
message: entry.message ?? entry.computedError ?? '',
|
|
349
269
|
context,
|
|
350
|
-
timestamp: new Date(),
|
|
270
|
+
timestamp: new Date(entry.timestamp),
|
|
351
271
|
meta: {
|
|
352
|
-
file:
|
|
353
|
-
line:
|
|
272
|
+
file: filename ?? '',
|
|
273
|
+
line: line ?? 0,
|
|
354
274
|
resourceId: resource.data.id,
|
|
355
275
|
},
|
|
356
276
|
};
|
|
@@ -362,110 +282,6 @@ export class TraceProcessor {
|
|
|
362
282
|
};
|
|
363
283
|
}
|
|
364
284
|
|
|
365
|
-
// TODO(burdon): Comment.
|
|
366
|
-
export class TracingSpan {
|
|
367
|
-
static nextId = 0;
|
|
368
|
-
|
|
369
|
-
readonly id: number;
|
|
370
|
-
readonly parentId: number | null = null;
|
|
371
|
-
readonly methodName: string;
|
|
372
|
-
readonly resourceId: number | null = null;
|
|
373
|
-
readonly op: string | undefined;
|
|
374
|
-
readonly attributes: Record<string, any>;
|
|
375
|
-
startTs: number;
|
|
376
|
-
endTs: number | null = null;
|
|
377
|
-
error: SerializedError | null = null;
|
|
378
|
-
|
|
379
|
-
private _showInBrowserTimeline: boolean;
|
|
380
|
-
private readonly _ctx: Context | null = null;
|
|
381
|
-
|
|
382
|
-
constructor(
|
|
383
|
-
private _traceProcessor: TraceProcessor,
|
|
384
|
-
params: TraceSpanParams,
|
|
385
|
-
) {
|
|
386
|
-
this.id = TracingSpan.nextId++;
|
|
387
|
-
this.methodName = params.methodName;
|
|
388
|
-
this.resourceId = _traceProcessor.getResourceId(params.instance);
|
|
389
|
-
this.startTs = performance.now();
|
|
390
|
-
this._showInBrowserTimeline = params.showInBrowserTimeline;
|
|
391
|
-
this.op = params.op;
|
|
392
|
-
this.attributes = params.attributes ?? {};
|
|
393
|
-
|
|
394
|
-
if (params.parentCtx) {
|
|
395
|
-
this._ctx = params.parentCtx.derive({
|
|
396
|
-
attributes: {
|
|
397
|
-
[TRACE_SPAN_ATTRIBUTE]: this.id,
|
|
398
|
-
},
|
|
399
|
-
});
|
|
400
|
-
const parentId = params.parentCtx.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
401
|
-
if (typeof parentId === 'number') {
|
|
402
|
-
this.parentId = parentId;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
get name() {
|
|
408
|
-
const resource = this._traceProcessor.resources.get(this.resourceId!);
|
|
409
|
-
return resource ? `${resource.sanitizedClassName}#${resource.data.instanceId}.${this.methodName}` : this.methodName;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
get ctx(): Context | null {
|
|
413
|
-
return this._ctx;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
markSuccess(): void {
|
|
417
|
-
this.endTs = performance.now();
|
|
418
|
-
this._traceProcessor._flushSpan(this);
|
|
419
|
-
|
|
420
|
-
if (this._showInBrowserTimeline) {
|
|
421
|
-
this._markInBrowserTimeline();
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
markError(err: unknown): void {
|
|
426
|
-
this.endTs = performance.now();
|
|
427
|
-
this.error = serializeError(err);
|
|
428
|
-
this._traceProcessor._flushSpan(this);
|
|
429
|
-
|
|
430
|
-
if (this._showInBrowserTimeline) {
|
|
431
|
-
this._markInBrowserTimeline();
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
serialize(): Span {
|
|
436
|
-
return {
|
|
437
|
-
id: this.id,
|
|
438
|
-
resourceId: this.resourceId ?? undefined,
|
|
439
|
-
methodName: this.methodName,
|
|
440
|
-
parentId: this.parentId ?? undefined,
|
|
441
|
-
startTs: this.startTs.toFixed(3),
|
|
442
|
-
endTs: this.endTs?.toFixed(3) ?? undefined,
|
|
443
|
-
error: this.error ?? undefined,
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
private _markInBrowserTimeline(): void {
|
|
448
|
-
if (typeof globalThis?.performance?.measure === 'function') {
|
|
449
|
-
performance.measure(this.name, { start: this.startTs, end: this.endTs! });
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// TODO(burdon): Log cause.
|
|
455
|
-
const serializeError = (err: unknown): SerializedError => {
|
|
456
|
-
if (err instanceof Error) {
|
|
457
|
-
return {
|
|
458
|
-
name: err.name,
|
|
459
|
-
message: err.message,
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
return {
|
|
464
|
-
message: String(err),
|
|
465
|
-
};
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
// TODO(burdon): Rename singleton and move out of package.
|
|
469
285
|
export const TRACE_PROCESSOR: TraceProcessor = ((globalThis as any).TRACE_PROCESSOR ??= new TraceProcessor());
|
|
470
286
|
|
|
471
287
|
const sanitizeValue = (value: any, depth: number, traceProcessor: TraceProcessor): any => {
|
|
@@ -489,7 +305,6 @@ const sanitizeValue = (value: any, depth: number, traceProcessor: TraceProcessor
|
|
|
489
305
|
}
|
|
490
306
|
|
|
491
307
|
if (typeof value.toJSON === 'function') {
|
|
492
|
-
// TODO(dmaretskyi): This has potential to cause infinite recursion.
|
|
493
308
|
return sanitizeValue(value.toJSON(), depth, traceProcessor);
|
|
494
309
|
}
|
|
495
310
|
|
|
@@ -513,7 +328,6 @@ const sanitizeValue = (value: any, depth: number, traceProcessor: TraceProcessor
|
|
|
513
328
|
}
|
|
514
329
|
}
|
|
515
330
|
|
|
516
|
-
// TODO(dmaretskyi): Expose trait.
|
|
517
331
|
if (typeof value.truncate === 'function') {
|
|
518
332
|
return value.truncate();
|
|
519
333
|
}
|
|
@@ -522,28 +336,14 @@ const sanitizeValue = (value: any, depth: number, traceProcessor: TraceProcessor
|
|
|
522
336
|
}
|
|
523
337
|
};
|
|
524
338
|
|
|
525
|
-
const areEqualShallow = (a: any, b: any) => {
|
|
526
|
-
for (const key in a) {
|
|
527
|
-
if (!(key in b) || a[key] !== b[key]) {
|
|
528
|
-
return false;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
for (const key in b) {
|
|
532
|
-
if (!(key in a) || a[key] !== b[key]) {
|
|
533
|
-
return false;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return true;
|
|
537
|
-
};
|
|
538
|
-
|
|
539
339
|
export const sanitizeClassName = (className: string) => {
|
|
340
|
+
let name = className.replace(/^_+/, '');
|
|
540
341
|
const SANITIZE_REGEX = /[^_](\d+)$/;
|
|
541
|
-
const m =
|
|
542
|
-
if (
|
|
543
|
-
|
|
544
|
-
} else {
|
|
545
|
-
return className.slice(0, -m[1].length);
|
|
342
|
+
const m = name.match(SANITIZE_REGEX);
|
|
343
|
+
if (m) {
|
|
344
|
+
name = name.slice(0, -m[1].length);
|
|
546
345
|
}
|
|
346
|
+
return name;
|
|
547
347
|
};
|
|
548
348
|
|
|
549
349
|
const isSetLike = (value: any): value is Set<any> =>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type TraceContextData } from '@dxos/context';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Opaque span handle returned by {@link TracingBackend.startSpan}.
|
|
9
|
+
*
|
|
10
|
+
* The `spanContext` field carries W3C trace context strings that are stored
|
|
11
|
+
* on the DXOS {@link Context} via `TRACE_SPAN_ATTRIBUTE`. Because these are
|
|
12
|
+
* plain strings (not live runtime objects), they survive after the span ends
|
|
13
|
+
* and across serialization boundaries.
|
|
14
|
+
*/
|
|
15
|
+
export type RemoteSpan = {
|
|
16
|
+
/** Signal that the span has ended. Must be called exactly once. */
|
|
17
|
+
end: (endTime?: number) => void;
|
|
18
|
+
|
|
19
|
+
/** Record an error on the span (e.g., OTEL `span.recordException` + `setStatus`). */
|
|
20
|
+
setError?: (err: unknown) => void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* W3C trace context identifying this span.
|
|
24
|
+
*
|
|
25
|
+
* Stored on the DXOS `Context` attribute (`TRACE_SPAN_ATTRIBUTE`) so that
|
|
26
|
+
* child `@trace.span()` methods can read it and pass it as
|
|
27
|
+
* {@link StartSpanOptions.parentContext} to create properly-parented spans.
|
|
28
|
+
*/
|
|
29
|
+
spanContext?: TraceContextData;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Options passed to {@link TracingBackend.startSpan}.
|
|
34
|
+
*/
|
|
35
|
+
export type StartSpanOptions = {
|
|
36
|
+
/** Human-readable span name, typically `ClassName.methodName`. */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Span category (e.g., `'function'`, `'rpc'`). */
|
|
39
|
+
op?: string;
|
|
40
|
+
/** Key-value attributes attached to the span. */
|
|
41
|
+
attributes?: Record<string, any>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* W3C trace context of the parent span.
|
|
45
|
+
*
|
|
46
|
+
* The backend extracts the trace/span IDs from these strings to establish
|
|
47
|
+
* parent-child relationships. When `undefined`, the backend creates a root span.
|
|
48
|
+
*/
|
|
49
|
+
parentContext?: TraceContextData;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Epoch-millisecond timestamp for the span start.
|
|
53
|
+
* Used by the buffering backend to preserve original timing when replaying spans.
|
|
54
|
+
* When `undefined`, the backend uses the current time.
|
|
55
|
+
*/
|
|
56
|
+
startTime?: number;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Backend-agnostic tracing interface implemented by the observability package
|
|
61
|
+
* and registered on `TRACE_PROCESSOR.tracingBackend`.
|
|
62
|
+
*
|
|
63
|
+
* The backend receives and returns {@link TraceContextData} (W3C strings) —
|
|
64
|
+
* no opaque runtime objects cross the interface boundary. The OTEL backend
|
|
65
|
+
* performs `propagation.extract/inject` internally in {@link startSpan}.
|
|
66
|
+
*/
|
|
67
|
+
export interface TracingBackend {
|
|
68
|
+
/**
|
|
69
|
+
* Create a new span.
|
|
70
|
+
*
|
|
71
|
+
* The backend should:
|
|
72
|
+
* 1. Extract the parent from `options.parentContext` (if present).
|
|
73
|
+
* 2. Create a span as a child of that parent.
|
|
74
|
+
* 3. Inject the new span's identity into the returned `spanContext`.
|
|
75
|
+
*/
|
|
76
|
+
startSpan: (options: StartSpanOptions) => RemoteSpan;
|
|
77
|
+
}
|