@dxos/tracing 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef
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/dist/lib/browser/index.mjs +330 -513
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +330 -513
- 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 +31 -14
- 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.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 +15 -56
- 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 +9 -12
- package/src/api.ts +235 -40
- package/src/buffering-backend.ts +112 -0
- package/src/index.ts +1 -2
- package/src/remote/index.ts +0 -1
- package/src/symbols.ts +0 -2
- package/src/trace-processor.ts +51 -263
- package/src/tracing-types.ts +77 -0
- package/src/tracing.test.ts +513 -4
- package/dist/types/src/remote/tracing.d.ts +0 -57
- 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 -175
- package/src/trace-sender.ts +0 -88
package/src/trace-processor.ts
CHANGED
|
@@ -2,26 +2,23 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { 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
|
|
|
@@ -31,17 +28,6 @@ export type TraceResourceConstructorProps = {
|
|
|
31
28
|
annotation?: symbol;
|
|
32
29
|
};
|
|
33
30
|
|
|
34
|
-
export type TraceSpanProps = {
|
|
35
|
-
instance: any;
|
|
36
|
-
// TODO(wittjosiah): Rename to `name`.
|
|
37
|
-
methodName: string;
|
|
38
|
-
parentCtx: Context | null;
|
|
39
|
-
showInBrowserTimeline: boolean;
|
|
40
|
-
showInRemoteTracing?: boolean;
|
|
41
|
-
op?: string;
|
|
42
|
-
attributes?: Record<string, any>;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
31
|
export class ResourceEntry {
|
|
46
32
|
/**
|
|
47
33
|
* Sometimes bundlers mangle class names: WebFile -> WebFile2.
|
|
@@ -63,39 +49,57 @@ export class ResourceEntry {
|
|
|
63
49
|
}
|
|
64
50
|
}
|
|
65
51
|
|
|
66
|
-
export type TraceSubscription = {
|
|
67
|
-
flush: () => void;
|
|
68
|
-
|
|
69
|
-
dirtyResources: Set<number>;
|
|
70
|
-
dirtySpans: Set<number>;
|
|
71
|
-
newLogs: LogEntry[];
|
|
72
|
-
};
|
|
73
|
-
|
|
74
52
|
const MAX_RESOURCE_RECORDS = 2_000;
|
|
75
|
-
const MAX_SPAN_RECORDS = 1_000;
|
|
76
53
|
const MAX_LOG_RECORDS = 1_000;
|
|
77
54
|
|
|
78
|
-
const REFRESH_INTERVAL = 1_000;
|
|
79
|
-
|
|
80
55
|
const MAX_INFO_OBJECT_DEPTH = 8;
|
|
81
56
|
|
|
82
|
-
const IS_CLOUDFLARE_WORKERS = !!globalThis?.navigator?.userAgent?.includes('Cloudflare-Workers');
|
|
83
|
-
|
|
84
57
|
export class TraceProcessor {
|
|
85
58
|
public readonly diagnostics = new DiagnosticsManager();
|
|
86
59
|
public readonly diagnosticsChannel = new DiagnosticsChannel();
|
|
87
60
|
public readonly remoteMetrics = new RemoteMetrics();
|
|
88
|
-
public readonly remoteTracing = new RemoteTracing();
|
|
89
61
|
|
|
90
|
-
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
|
+
}
|
|
91
98
|
|
|
92
99
|
readonly resources = new Map<number, ResourceEntry>();
|
|
93
100
|
readonly resourceInstanceIndex = new WeakMap<any, ResourceEntry>();
|
|
94
101
|
readonly resourceIdList: number[] = [];
|
|
95
102
|
|
|
96
|
-
readonly spans = new Map<number, Span>();
|
|
97
|
-
readonly spanIdList: number[] = [];
|
|
98
|
-
|
|
99
103
|
readonly logs: LogEntry[] = [];
|
|
100
104
|
|
|
101
105
|
private _instanceTag: string | null = null;
|
|
@@ -103,11 +107,6 @@ export class TraceProcessor {
|
|
|
103
107
|
constructor() {
|
|
104
108
|
log.addProcessor(this._logProcessor.bind(this));
|
|
105
109
|
|
|
106
|
-
if (!IS_CLOUDFLARE_WORKERS) {
|
|
107
|
-
const refreshInterval = setInterval(this.refresh.bind(this), REFRESH_INTERVAL);
|
|
108
|
-
unrefTimeout(refreshInterval);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
110
|
if (DiagnosticsChannel.supported) {
|
|
112
111
|
this.diagnosticsChannel.serve(this.diagnostics);
|
|
113
112
|
}
|
|
@@ -119,14 +118,10 @@ export class TraceProcessor {
|
|
|
119
118
|
this.diagnostics.setInstanceTag(tag);
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
/**
|
|
123
|
-
* @internal
|
|
124
|
-
*/
|
|
125
|
-
// TODO(burdon): Comment.
|
|
121
|
+
/** @internal */
|
|
126
122
|
createTraceResource(params: TraceResourceConstructorProps): void {
|
|
127
123
|
const id = this.resources.size;
|
|
128
124
|
|
|
129
|
-
// Init metrics counters.
|
|
130
125
|
const tracingContext = getTracingContext(Object.getPrototypeOf(params.instance));
|
|
131
126
|
for (const key of Object.keys(tracingContext.metricsProperties)) {
|
|
132
127
|
(params.instance[key] as BaseCounter)._assign(params.instance, key);
|
|
@@ -151,29 +146,11 @@ export class TraceProcessor {
|
|
|
151
146
|
if (this.resourceIdList.length > MAX_RESOURCE_RECORDS) {
|
|
152
147
|
this._clearResources();
|
|
153
148
|
}
|
|
154
|
-
|
|
155
|
-
this._markResourceDirty(id);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
createTraceSender(): TraceSender {
|
|
159
|
-
return new TraceSender(this);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
traceSpan(params: TraceSpanProps): TracingSpan {
|
|
163
|
-
const span = new TracingSpan(this, params);
|
|
164
|
-
this._flushSpan(span);
|
|
165
|
-
return span;
|
|
166
149
|
}
|
|
167
150
|
|
|
168
151
|
// TODO(burdon): Not implemented.
|
|
169
152
|
addLink(parent: any, child: any, opts: AddLinkOptions): void {}
|
|
170
153
|
|
|
171
|
-
//
|
|
172
|
-
// Getters
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
// TODO(burdon): Define type.
|
|
176
|
-
// TODO(burdon): Reconcile with system service.
|
|
177
154
|
getDiagnostics(): Diagnostics {
|
|
178
155
|
this.refresh();
|
|
179
156
|
|
|
@@ -184,7 +161,6 @@ export class TraceProcessor {
|
|
|
184
161
|
entry.data,
|
|
185
162
|
]),
|
|
186
163
|
),
|
|
187
|
-
spans: Array.from(this.spans.values()),
|
|
188
164
|
logs: this.logs.filter((log) => log.level >= LogLevel.INFO),
|
|
189
165
|
};
|
|
190
166
|
}
|
|
@@ -251,81 +227,23 @@ export class TraceProcessor {
|
|
|
251
227
|
(instance[key] as BaseCounter)._tick?.(time);
|
|
252
228
|
}
|
|
253
229
|
|
|
254
|
-
let _changed = false;
|
|
255
|
-
|
|
256
|
-
const oldInfo = resource.data.info;
|
|
257
230
|
resource.data.info = this.getResourceInfo(instance);
|
|
258
|
-
_changed ||= !areEqualShallow(oldInfo, resource.data.info);
|
|
259
|
-
|
|
260
|
-
const oldMetrics = resource.data.metrics;
|
|
261
231
|
resource.data.metrics = this.getResourceMetrics(instance);
|
|
262
|
-
_changed ||= !areEqualShallow(oldMetrics, resource.data.metrics);
|
|
263
|
-
|
|
264
|
-
// TODO(dmaretskyi): Test if works and enable.
|
|
265
|
-
// if (changed) {
|
|
266
|
-
this._markResourceDirty(resource.data.id);
|
|
267
|
-
// }
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
for (const subscription of this.subscriptions) {
|
|
271
|
-
subscription.flush();
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
//
|
|
276
|
-
// Implementation
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* @internal
|
|
281
|
-
*/
|
|
282
|
-
_flushSpan(runtimeSpan: TracingSpan): void {
|
|
283
|
-
const span = runtimeSpan.serialize();
|
|
284
|
-
this.spans.set(span.id, span);
|
|
285
|
-
this.spanIdList.push(span.id);
|
|
286
|
-
if (this.spanIdList.length > MAX_SPAN_RECORDS) {
|
|
287
|
-
this._clearSpans();
|
|
288
|
-
}
|
|
289
|
-
this._markSpanDirty(span.id);
|
|
290
|
-
this.remoteTracing.flushSpan(runtimeSpan);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private _markResourceDirty(id: number): void {
|
|
294
|
-
for (const subscription of this.subscriptions) {
|
|
295
|
-
subscription.dirtyResources.add(id);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
private _markSpanDirty(id: number): void {
|
|
300
|
-
for (const subscription of this.subscriptions) {
|
|
301
|
-
subscription.dirtySpans.add(id);
|
|
302
232
|
}
|
|
303
233
|
}
|
|
304
234
|
|
|
305
235
|
private _clearResources(): void {
|
|
306
|
-
// TODO(dmaretskyi): Use FinalizationRegistry to delete finalized resources first.
|
|
307
236
|
while (this.resourceIdList.length > MAX_RESOURCE_RECORDS) {
|
|
308
237
|
const id = this.resourceIdList.shift()!;
|
|
309
238
|
this.resources.delete(id);
|
|
310
239
|
}
|
|
311
240
|
}
|
|
312
241
|
|
|
313
|
-
private _clearSpans(): void {
|
|
314
|
-
while (this.spanIdList.length > MAX_SPAN_RECORDS) {
|
|
315
|
-
const id = this.spanIdList.shift()!;
|
|
316
|
-
this.spans.delete(id);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
242
|
private _pushLog(log: LogEntry): void {
|
|
321
243
|
this.logs.push(log);
|
|
322
244
|
if (this.logs.length > MAX_LOG_RECORDS) {
|
|
323
245
|
this.logs.shift();
|
|
324
246
|
}
|
|
325
|
-
|
|
326
|
-
for (const subscription of this.subscriptions) {
|
|
327
|
-
subscription.newLogs.push(log);
|
|
328
|
-
}
|
|
329
247
|
}
|
|
330
248
|
|
|
331
249
|
private _logProcessor: LogProcessor = (config, entry) => {
|
|
@@ -339,19 +257,20 @@ export class TraceProcessor {
|
|
|
339
257
|
return;
|
|
340
258
|
}
|
|
341
259
|
|
|
342
|
-
const context =
|
|
343
|
-
|
|
344
|
-
context
|
|
260
|
+
const context: Record<string, any> = { ...entry.computedContext };
|
|
261
|
+
if (entry.computedError !== undefined) {
|
|
262
|
+
context.error = entry.computedError;
|
|
345
263
|
}
|
|
346
264
|
|
|
265
|
+
const { filename, line } = entry.computedMeta;
|
|
347
266
|
const entryToPush: LogEntry = {
|
|
348
267
|
level: entry.level,
|
|
349
|
-
message: entry.message ??
|
|
268
|
+
message: entry.message ?? entry.computedError ?? '',
|
|
350
269
|
context,
|
|
351
|
-
timestamp: new Date(),
|
|
270
|
+
timestamp: new Date(entry.timestamp),
|
|
352
271
|
meta: {
|
|
353
|
-
file:
|
|
354
|
-
line:
|
|
272
|
+
file: filename ?? '',
|
|
273
|
+
line: line ?? 0,
|
|
355
274
|
resourceId: resource.data.id,
|
|
356
275
|
},
|
|
357
276
|
};
|
|
@@ -363,121 +282,6 @@ export class TraceProcessor {
|
|
|
363
282
|
};
|
|
364
283
|
}
|
|
365
284
|
|
|
366
|
-
// TODO(burdon): Comment.
|
|
367
|
-
export class TracingSpan {
|
|
368
|
-
static nextId = 0;
|
|
369
|
-
|
|
370
|
-
readonly id: number;
|
|
371
|
-
readonly parentId: number | null = null;
|
|
372
|
-
readonly methodName: string;
|
|
373
|
-
readonly resourceId: number | null = null;
|
|
374
|
-
readonly op: string | undefined;
|
|
375
|
-
readonly attributes: Record<string, any>;
|
|
376
|
-
startTs: number;
|
|
377
|
-
endTs: number | null = null;
|
|
378
|
-
error: SerializedError | null = null;
|
|
379
|
-
|
|
380
|
-
private _showInBrowserTimeline: boolean;
|
|
381
|
-
private _showInRemoteTracing: boolean;
|
|
382
|
-
private readonly _ctx: Context;
|
|
383
|
-
|
|
384
|
-
constructor(
|
|
385
|
-
private _traceProcessor: TraceProcessor,
|
|
386
|
-
params: TraceSpanProps,
|
|
387
|
-
) {
|
|
388
|
-
this.id = TracingSpan.nextId++;
|
|
389
|
-
this.methodName = params.methodName;
|
|
390
|
-
this.resourceId = _traceProcessor.getResourceId(params.instance);
|
|
391
|
-
this.startTs = performance.now();
|
|
392
|
-
this._showInBrowserTimeline = params.showInBrowserTimeline;
|
|
393
|
-
this._showInRemoteTracing = params.showInRemoteTracing ?? true;
|
|
394
|
-
this.op = params.op;
|
|
395
|
-
this.attributes = params.attributes ?? {};
|
|
396
|
-
|
|
397
|
-
const baseCtx = params.parentCtx ?? new Context();
|
|
398
|
-
this._ctx = this._showInRemoteTracing
|
|
399
|
-
? baseCtx.derive({ attributes: { [TRACE_SPAN_ATTRIBUTE]: this.id } })
|
|
400
|
-
: baseCtx.derive();
|
|
401
|
-
if (params.parentCtx) {
|
|
402
|
-
const parentId = params.parentCtx.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
403
|
-
if (typeof parentId === 'number') {
|
|
404
|
-
this.parentId = parentId;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
get name() {
|
|
410
|
-
const resource = this._traceProcessor.resources.get(this.resourceId!);
|
|
411
|
-
return resource ? `${resource.sanitizedClassName}#${resource.data.instanceId}.${this.methodName}` : this.methodName;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/** Sanitized class name of the owning resource, if available. */
|
|
415
|
-
get sanitizedClassName(): string | undefined {
|
|
416
|
-
const resource = this.resourceId != null ? this._traceProcessor.resources.get(this.resourceId) : undefined;
|
|
417
|
-
return resource?.sanitizedClassName;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
get ctx(): Context {
|
|
421
|
-
return this._ctx;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
get showInRemoteTracing(): boolean {
|
|
425
|
-
return this._showInRemoteTracing;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
markSuccess(): void {
|
|
429
|
-
this.endTs = performance.now();
|
|
430
|
-
this._traceProcessor._flushSpan(this);
|
|
431
|
-
|
|
432
|
-
if (this._showInBrowserTimeline) {
|
|
433
|
-
this._markInBrowserTimeline();
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
markError(err: unknown): void {
|
|
438
|
-
this.endTs = performance.now();
|
|
439
|
-
this.error = serializeError(err);
|
|
440
|
-
this._traceProcessor._flushSpan(this);
|
|
441
|
-
|
|
442
|
-
if (this._showInBrowserTimeline) {
|
|
443
|
-
this._markInBrowserTimeline();
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
serialize(): Span {
|
|
448
|
-
return {
|
|
449
|
-
id: this.id,
|
|
450
|
-
resourceId: this.resourceId ?? undefined,
|
|
451
|
-
methodName: this.methodName,
|
|
452
|
-
parentId: this.parentId ?? undefined,
|
|
453
|
-
startTs: this.startTs.toFixed(3),
|
|
454
|
-
endTs: this.endTs?.toFixed(3) ?? undefined,
|
|
455
|
-
error: this.error ?? undefined,
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
private _markInBrowserTimeline(): void {
|
|
460
|
-
if (typeof globalThis?.performance?.measure === 'function') {
|
|
461
|
-
performance.measure(this.name, { start: this.startTs, end: this.endTs! });
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// TODO(burdon): Log cause.
|
|
467
|
-
const serializeError = (err: unknown): SerializedError => {
|
|
468
|
-
if (err instanceof Error) {
|
|
469
|
-
return {
|
|
470
|
-
name: err.name,
|
|
471
|
-
message: err.message,
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return {
|
|
476
|
-
message: String(err),
|
|
477
|
-
};
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
// TODO(burdon): Rename singleton and move out of package.
|
|
481
285
|
export const TRACE_PROCESSOR: TraceProcessor = ((globalThis as any).TRACE_PROCESSOR ??= new TraceProcessor());
|
|
482
286
|
|
|
483
287
|
const sanitizeValue = (value: any, depth: number, traceProcessor: TraceProcessor): any => {
|
|
@@ -501,7 +305,6 @@ const sanitizeValue = (value: any, depth: number, traceProcessor: TraceProcessor
|
|
|
501
305
|
}
|
|
502
306
|
|
|
503
307
|
if (typeof value.toJSON === 'function') {
|
|
504
|
-
// TODO(dmaretskyi): This has potential to cause infinite recursion.
|
|
505
308
|
return sanitizeValue(value.toJSON(), depth, traceProcessor);
|
|
506
309
|
}
|
|
507
310
|
|
|
@@ -525,7 +328,6 @@ const sanitizeValue = (value: any, depth: number, traceProcessor: TraceProcessor
|
|
|
525
328
|
}
|
|
526
329
|
}
|
|
527
330
|
|
|
528
|
-
// TODO(dmaretskyi): Expose trait.
|
|
529
331
|
if (typeof value.truncate === 'function') {
|
|
530
332
|
return value.truncate();
|
|
531
333
|
}
|
|
@@ -534,20 +336,6 @@ const sanitizeValue = (value: any, depth: number, traceProcessor: TraceProcessor
|
|
|
534
336
|
}
|
|
535
337
|
};
|
|
536
338
|
|
|
537
|
-
const areEqualShallow = (a: any, b: any) => {
|
|
538
|
-
for (const key in a) {
|
|
539
|
-
if (!(key in b) || a[key] !== b[key]) {
|
|
540
|
-
return false;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
for (const key in b) {
|
|
544
|
-
if (!(key in a) || a[key] !== b[key]) {
|
|
545
|
-
return false;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
return true;
|
|
549
|
-
};
|
|
550
|
-
|
|
551
339
|
export const sanitizeClassName = (className: string) => {
|
|
552
340
|
let name = className.replace(/^_+/, '');
|
|
553
341
|
const SANITIZE_REGEX = /[^_](\d+)$/;
|
|
@@ -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
|
+
}
|