@dxos/tracing 0.10.0 → 0.11.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/dist/lib/index.mjs +509 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/types/src/api.d.ts +0 -44
- package/dist/types/src/api.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/trace-processor.d.ts +0 -40
- package/dist/types/src/trace-processor.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -13
- package/src/api.ts +3 -186
- package/src/index.ts +0 -2
- package/src/trace-processor.ts +1 -274
- package/src/tracing.test.ts +41 -326
- package/dist/lib/browser/index.mjs +0 -1065
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/index.mjs +0 -1067
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/types/src/metrics/base.d.ts +0 -7
- package/dist/types/src/metrics/base.d.ts.map +0 -1
- package/dist/types/src/metrics/custom-counter.d.ts +0 -8
- package/dist/types/src/metrics/custom-counter.d.ts.map +0 -1
- package/dist/types/src/metrics/index.d.ts +0 -7
- package/dist/types/src/metrics/index.d.ts.map +0 -1
- package/dist/types/src/metrics/map-counter.d.ts +0 -12
- package/dist/types/src/metrics/map-counter.d.ts.map +0 -1
- package/dist/types/src/metrics/time-series-counter.d.ts +0 -15
- package/dist/types/src/metrics/time-series-counter.d.ts.map +0 -1
- package/dist/types/src/metrics/time-usage-counter.d.ts +0 -15
- package/dist/types/src/metrics/time-usage-counter.d.ts.map +0 -1
- package/dist/types/src/metrics/unary-counter.d.ts +0 -12
- package/dist/types/src/metrics/unary-counter.d.ts.map +0 -1
- package/dist/types/src/symbols.d.ts +0 -10
- package/dist/types/src/symbols.d.ts.map +0 -1
- package/dist/types/src/weak-ref.d.ts +0 -2
- package/dist/types/src/weak-ref.d.ts.map +0 -1
- package/src/metrics/base.ts +0 -26
- package/src/metrics/custom-counter.ts +0 -22
- package/src/metrics/index.ts +0 -10
- package/src/metrics/map-counter.ts +0 -35
- package/src/metrics/time-series-counter.ts +0 -52
- package/src/metrics/time-usage-counter.ts +0 -62
- package/src/metrics/unary-counter.ts +0 -31
- package/src/symbols.ts +0 -24
- package/src/weak-ref.ts +0 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/tracing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Async utilities.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -14,12 +14,9 @@
|
|
|
14
14
|
"type": "module",
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
|
+
"source": "./src/index.ts",
|
|
17
18
|
"types": "./dist/types/src/index.d.ts",
|
|
18
|
-
"
|
|
19
|
-
"node": {
|
|
20
|
-
"require": "./dist/lib/node/index.cjs",
|
|
21
|
-
"default": "./dist/lib/node-esm/index.mjs"
|
|
22
|
-
}
|
|
19
|
+
"import": "./dist/lib/index.mjs"
|
|
23
20
|
}
|
|
24
21
|
},
|
|
25
22
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -28,13 +25,13 @@
|
|
|
28
25
|
"src"
|
|
29
26
|
],
|
|
30
27
|
"dependencies": {
|
|
31
|
-
"@dxos/async": "0.
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/node-std": "0.
|
|
36
|
-
"@dxos/protocols": "0.
|
|
37
|
-
"@dxos/util": "0.
|
|
28
|
+
"@dxos/async": "0.11.1",
|
|
29
|
+
"@dxos/context": "0.11.1",
|
|
30
|
+
"@dxos/invariant": "0.11.1",
|
|
31
|
+
"@dxos/log": "0.11.1",
|
|
32
|
+
"@dxos/node-std": "0.11.1",
|
|
33
|
+
"@dxos/protocols": "0.11.1",
|
|
34
|
+
"@dxos/util": "0.11.1"
|
|
38
35
|
},
|
|
39
36
|
"publishConfig": {
|
|
40
37
|
"access": "public"
|
package/src/api.ts
CHANGED
|
@@ -2,178 +2,15 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Context,
|
|
5
|
+
import { Context, TRACE_SPAN_ATTRIBUTE } from '@dxos/context';
|
|
6
6
|
import { type MaybePromise } from '@dxos/util';
|
|
7
7
|
|
|
8
|
-
import { getTracingContext } from './symbols';
|
|
9
8
|
import { TRACE_PROCESSOR, sanitizeClassName } from './trace-processor';
|
|
10
9
|
import type { RemoteSpan } from './tracing-types';
|
|
11
10
|
|
|
12
|
-
const LIFECYCLE_SPAN = Symbol('dxos.tracing.lifecycle-span');
|
|
13
|
-
|
|
14
11
|
/** localStorage key that switches the browser OTEL sampler from 30% to 100%. */
|
|
15
12
|
export const TRACE_ALL_KEY = 'dxos.debug.traceAll';
|
|
16
13
|
|
|
17
|
-
/**
|
|
18
|
-
* Reads `@trace.info({ spanAttribute: true })` properties from the instance
|
|
19
|
-
* and writes them into the span attributes map.
|
|
20
|
-
*/
|
|
21
|
-
const collectSpanAttributes = (instance: any, spanAttributes: Record<string, any>) => {
|
|
22
|
-
const proto = Object.getPrototypeOf(instance);
|
|
23
|
-
if (!proto) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const tracingContext = getTracingContext(proto);
|
|
27
|
-
for (const [key, { options }] of Object.entries(tracingContext.infoProperties)) {
|
|
28
|
-
if (!options.spanAttribute) {
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
const value = typeof instance[key] === 'function' ? instance[key]() : instance[key];
|
|
33
|
-
if (value != null) {
|
|
34
|
-
const resolved = options.enum ? options.enum[value] : String(value);
|
|
35
|
-
spanAttributes[`ctx.${key}`] = resolved;
|
|
36
|
-
}
|
|
37
|
-
} catch {
|
|
38
|
-
// Skip properties that throw (e.g. uninitialized).
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export type ResourceOptions = {
|
|
44
|
-
annotation?: symbol;
|
|
45
|
-
/**
|
|
46
|
-
* Start a lifecycle span on `open()` and end it on `close()`.
|
|
47
|
-
* `this._ctx` carries the lifecycle span's trace context, so background work
|
|
48
|
-
* (subscriptions, timers) becomes children of the lifecycle span.
|
|
49
|
-
* Direct calls within `_open` use the `_open` span's context as usual.
|
|
50
|
-
* Requires the class to extend {@link Resource}.
|
|
51
|
-
*/
|
|
52
|
-
lifecycle?: boolean;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Annotates a class as a tracked resource.
|
|
57
|
-
*/
|
|
58
|
-
const resource =
|
|
59
|
-
(options?: ResourceOptions) =>
|
|
60
|
-
<T extends { new (...args: any[]): {} }>(constructor: T) => {
|
|
61
|
-
if (options?.lifecycle && !(constructor.prototype instanceof Resource)) {
|
|
62
|
-
throw new Error(`@trace.resource({ lifecycle: true }) requires ${constructor.name} to extend Resource`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const klass = (() =>
|
|
66
|
-
class extends constructor {
|
|
67
|
-
constructor(...rest: any[]) {
|
|
68
|
-
super(...rest);
|
|
69
|
-
TRACE_PROCESSOR.createTraceResource({ constructor, annotation: options?.annotation, instance: this });
|
|
70
|
-
}
|
|
71
|
-
})();
|
|
72
|
-
|
|
73
|
-
if (options?.lifecycle) {
|
|
74
|
-
const sanitizedName = sanitizeClassName(constructor.name);
|
|
75
|
-
const proto = klass.prototype as any;
|
|
76
|
-
const originalOpen = proto.open;
|
|
77
|
-
const originalClose = proto.close;
|
|
78
|
-
|
|
79
|
-
proto.open = async function (ctx?: Context): Promise<any> {
|
|
80
|
-
const self = this as any;
|
|
81
|
-
|
|
82
|
-
if (self._lifecycleState !== LifecycleState.CLOSED) {
|
|
83
|
-
return originalOpen.call(this, ctx);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const parentSpanContext = ctx?.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
87
|
-
const resourceEntry = TRACE_PROCESSOR.resourceInstanceIndex.get(this);
|
|
88
|
-
const spanAttributes: Record<string, any> = {};
|
|
89
|
-
if (resourceEntry) {
|
|
90
|
-
spanAttributes.entryPoint = resourceEntry.sanitizedClassName;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const remoteSpan = TRACE_PROCESSOR.tracingBackend?.startSpan({
|
|
94
|
-
name: `${sanitizedName}.lifecycle`,
|
|
95
|
-
op: 'lifecycle',
|
|
96
|
-
attributes: spanAttributes,
|
|
97
|
-
parentContext: parentSpanContext,
|
|
98
|
-
});
|
|
99
|
-
self[LIFECYCLE_SPAN] = remoteSpan;
|
|
100
|
-
|
|
101
|
-
let openCtx = ctx;
|
|
102
|
-
if (remoteSpan?.spanContext != null) {
|
|
103
|
-
const traceAttrs = { [TRACE_SPAN_ATTRIBUTE]: remoteSpan.spanContext };
|
|
104
|
-
openCtx = ctx ? ctx.derive({ attributes: traceAttrs }) : new Context({ attributes: traceAttrs });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
return await originalOpen.call(this, openCtx);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
remoteSpan?.setError?.(err);
|
|
111
|
-
remoteSpan?.end();
|
|
112
|
-
self[LIFECYCLE_SPAN] = undefined;
|
|
113
|
-
throw err;
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
proto.close = async function (ctx?: Context): Promise<any> {
|
|
118
|
-
const self = this as any;
|
|
119
|
-
const remoteSpan: RemoteSpan | undefined = self[LIFECYCLE_SPAN];
|
|
120
|
-
try {
|
|
121
|
-
return await originalClose.call(this, ctx);
|
|
122
|
-
} catch (err) {
|
|
123
|
-
remoteSpan?.setError?.(err);
|
|
124
|
-
throw err;
|
|
125
|
-
} finally {
|
|
126
|
-
if (remoteSpan) {
|
|
127
|
-
remoteSpan.end();
|
|
128
|
-
self[LIFECYCLE_SPAN] = undefined;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
Object.defineProperty(klass, 'name', { value: constructor.name });
|
|
135
|
-
return klass;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
export interface TimeAware {
|
|
139
|
-
tick(timeMs: number): void;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export type InfoOptions = {
|
|
143
|
-
/**
|
|
144
|
-
* Value is of enum type and should be converted to string.
|
|
145
|
-
*
|
|
146
|
-
* Example:
|
|
147
|
-
*
|
|
148
|
-
* ```ts
|
|
149
|
-
* @trace.info({ enum: SpaceState })
|
|
150
|
-
* get state(): SpaceState { ... }
|
|
151
|
-
* ```
|
|
152
|
-
*/
|
|
153
|
-
enum?: Record<string, any>;
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Max depth of the object to be included in the resource info section.
|
|
157
|
-
*
|
|
158
|
-
* null means no limit (a limit of 8 nested objects is still imposed).
|
|
159
|
-
*
|
|
160
|
-
* Default: 0 - objects will be stringified with toString.
|
|
161
|
-
*/
|
|
162
|
-
depth?: number | null;
|
|
163
|
-
|
|
164
|
-
/** When true, the property value is also set as an OTEL span attribute on every span created by this resource. */
|
|
165
|
-
spanAttribute?: boolean;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Marks a property or a method to be included in the resource info section.
|
|
170
|
-
*/
|
|
171
|
-
const info =
|
|
172
|
-
(opts: InfoOptions = {}) =>
|
|
173
|
-
(target: any, propertyKey: string, descriptor?: PropertyDescriptor) => {
|
|
174
|
-
getTracingContext(target).infoProperties[propertyKey] = { options: opts };
|
|
175
|
-
};
|
|
176
|
-
|
|
177
14
|
const mark = (name: string) => {
|
|
178
15
|
performance.mark(name);
|
|
179
16
|
};
|
|
@@ -201,15 +38,10 @@ const span =
|
|
|
201
38
|
|
|
202
39
|
const parentSpanContext = parentCtx?.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
203
40
|
|
|
204
|
-
const
|
|
205
|
-
const className = resourceEntry?.sanitizedClassName ?? sanitizeClassName(target.constructor?.name ?? 'unknown');
|
|
41
|
+
const className = sanitizeClassName(target.constructor?.name ?? 'unknown');
|
|
206
42
|
const spanName = `${className}.${propertyKey}`;
|
|
207
43
|
|
|
208
44
|
const spanAttributes: Record<string, any> = {};
|
|
209
|
-
if (resourceEntry) {
|
|
210
|
-
spanAttributes.entryPoint = resourceEntry.sanitizedClassName;
|
|
211
|
-
}
|
|
212
|
-
collectSpanAttributes(this, spanAttributes);
|
|
213
45
|
if (attributes) {
|
|
214
46
|
for (const [key, value] of Object.entries(attributes)) {
|
|
215
47
|
spanAttributes[key.startsWith('ctx.') ? key : `ctx.${key}`] = value;
|
|
@@ -278,8 +110,7 @@ const spanStart = (params: ManualSpanParams): Context | null => {
|
|
|
278
110
|
return params.parentCtx;
|
|
279
111
|
}
|
|
280
112
|
|
|
281
|
-
const
|
|
282
|
-
const className = resourceEntry?.sanitizedClassName ?? 'unknown';
|
|
113
|
+
const className = sanitizeClassName(params.instance?.constructor?.name ?? 'unknown');
|
|
283
114
|
const spanName = `${className}.${params.methodName}`;
|
|
284
115
|
|
|
285
116
|
if (params.showInBrowserTimeline) {
|
|
@@ -293,10 +124,6 @@ const spanStart = (params: ManualSpanParams): Context | null => {
|
|
|
293
124
|
const parentSpanContext = params.parentCtx?.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
294
125
|
|
|
295
126
|
const spanAttributes: Record<string, any> = {};
|
|
296
|
-
if (resourceEntry) {
|
|
297
|
-
spanAttributes.entryPoint = resourceEntry.sanitizedClassName;
|
|
298
|
-
}
|
|
299
|
-
collectSpanAttributes(params.instance, spanAttributes);
|
|
300
127
|
if (params.attributes) {
|
|
301
128
|
for (const [key, value] of Object.entries(params.attributes)) {
|
|
302
129
|
spanAttributes[key.startsWith('ctx.') ? key : `ctx.${key}`] = value;
|
|
@@ -334,13 +161,6 @@ const spanEnd = (id: string) => {
|
|
|
334
161
|
}
|
|
335
162
|
};
|
|
336
163
|
|
|
337
|
-
/**
|
|
338
|
-
* Attaches metrics counter to the resource.
|
|
339
|
-
*/
|
|
340
|
-
const metricsCounter = () => (target: any, propertyKey: string, descriptor?: PropertyDescriptor) => {
|
|
341
|
-
getTracingContext(target).metricsProperties[propertyKey] = {};
|
|
342
|
-
};
|
|
343
|
-
|
|
344
164
|
export type AddLinkOptions = {};
|
|
345
165
|
|
|
346
166
|
const addLink = (parent: any, child: any, opts: AddLinkOptions = {}) => {
|
|
@@ -376,10 +196,7 @@ const diagnostic = <T>(params: TraceDiagnosticProps<T>): TraceDiagnostic => {
|
|
|
376
196
|
export const trace = {
|
|
377
197
|
addLink,
|
|
378
198
|
diagnostic,
|
|
379
|
-
info,
|
|
380
199
|
mark,
|
|
381
|
-
metricsCounter,
|
|
382
|
-
resource,
|
|
383
200
|
span,
|
|
384
201
|
spanStart,
|
|
385
202
|
spanEnd,
|
package/src/index.ts
CHANGED
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
import { trace } from './api';
|
|
6
6
|
|
|
7
7
|
export * from './api';
|
|
8
|
-
export * from './symbols';
|
|
9
8
|
export * from './trace-processor';
|
|
10
9
|
export * from './tracing-types';
|
|
11
|
-
export * from './metrics';
|
|
12
10
|
export * from './diagnostic';
|
|
13
11
|
export * from './diagnostics-channel';
|
|
14
12
|
export * from './remote/metrics';
|
package/src/trace-processor.ts
CHANGED
|
@@ -2,57 +2,12 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import { type LogEntry } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
|
-
import { type Metric, type Resource } from '@dxos/protocols/proto/dxos/tracing';
|
|
8
|
-
import { getPrototypeSpecificInstanceId } from '@dxos/util';
|
|
9
|
-
|
|
10
|
-
import type { AddLinkOptions, TimeAware } from './api';
|
|
5
|
+
import type { AddLinkOptions } from './api';
|
|
11
6
|
import { BUFFERED_PREFIX, BufferingTracingBackend } from './buffering-backend';
|
|
12
7
|
import { DiagnosticsManager } from './diagnostic';
|
|
13
8
|
import { DiagnosticsChannel } from './diagnostics-channel';
|
|
14
|
-
import { type BaseCounter } from './metrics';
|
|
15
9
|
import { RemoteMetrics } from './remote/metrics';
|
|
16
|
-
import { getTracingContext } from './symbols';
|
|
17
10
|
import type { RemoteSpan, StartSpanOptions, TracingBackend } from './tracing-types';
|
|
18
|
-
import { WeakRef } from './weak-ref';
|
|
19
|
-
|
|
20
|
-
export type Diagnostics = {
|
|
21
|
-
resources: Record<string, Resource>;
|
|
22
|
-
logs: LogEntry[];
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type TraceResourceConstructorProps = {
|
|
26
|
-
constructor: { new (...args: any[]): {} };
|
|
27
|
-
instance: any;
|
|
28
|
-
annotation?: symbol;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export class ResourceEntry {
|
|
32
|
-
/**
|
|
33
|
-
* Sometimes bundlers mangle class names: WebFile -> WebFile2.
|
|
34
|
-
*
|
|
35
|
-
* We use a heuristic to remove the suffix.
|
|
36
|
-
*/
|
|
37
|
-
public readonly sanitizedClassName: string;
|
|
38
|
-
|
|
39
|
-
constructor(
|
|
40
|
-
public readonly data: Resource,
|
|
41
|
-
public readonly instance: WeakRef<any>,
|
|
42
|
-
public readonly annotation?: symbol,
|
|
43
|
-
) {
|
|
44
|
-
this.sanitizedClassName = sanitizeClassName(data.className);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
getMetric(name: string): Metric | undefined {
|
|
48
|
-
return this.data.metrics?.find((metric) => metric.name === name);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const MAX_RESOURCE_RECORDS = 2_000;
|
|
53
|
-
const MAX_LOG_RECORDS = 1_000;
|
|
54
|
-
|
|
55
|
-
const MAX_INFO_OBJECT_DEPTH = 8;
|
|
56
11
|
|
|
57
12
|
export class TraceProcessor {
|
|
58
13
|
public readonly diagnostics = new DiagnosticsManager();
|
|
@@ -96,17 +51,9 @@ export class TraceProcessor {
|
|
|
96
51
|
};
|
|
97
52
|
}
|
|
98
53
|
|
|
99
|
-
readonly resources = new Map<number, ResourceEntry>();
|
|
100
|
-
readonly resourceInstanceIndex = new WeakMap<any, ResourceEntry>();
|
|
101
|
-
readonly resourceIdList: number[] = [];
|
|
102
|
-
|
|
103
|
-
readonly logs: LogEntry[] = [];
|
|
104
|
-
|
|
105
54
|
private _instanceTag: string | null = null;
|
|
106
55
|
|
|
107
56
|
constructor() {
|
|
108
|
-
log.addProcessor(this._logProcessor.bind(this));
|
|
109
|
-
|
|
110
57
|
if (DiagnosticsChannel.supported) {
|
|
111
58
|
this.diagnosticsChannel.serve(this.diagnostics);
|
|
112
59
|
}
|
|
@@ -118,224 +65,12 @@ export class TraceProcessor {
|
|
|
118
65
|
this.diagnostics.setInstanceTag(tag);
|
|
119
66
|
}
|
|
120
67
|
|
|
121
|
-
/** @internal */
|
|
122
|
-
createTraceResource(params: TraceResourceConstructorProps): void {
|
|
123
|
-
const id = this.resources.size;
|
|
124
|
-
|
|
125
|
-
const tracingContext = getTracingContext(Object.getPrototypeOf(params.instance));
|
|
126
|
-
for (const key of Object.keys(tracingContext.metricsProperties)) {
|
|
127
|
-
(params.instance[key] as BaseCounter)._assign(params.instance, key);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const entry = new ResourceEntry(
|
|
131
|
-
{
|
|
132
|
-
id,
|
|
133
|
-
className: params.constructor.name,
|
|
134
|
-
instanceId: getPrototypeSpecificInstanceId(params.instance),
|
|
135
|
-
info: this.getResourceInfo(params.instance),
|
|
136
|
-
links: [],
|
|
137
|
-
metrics: this.getResourceMetrics(params.instance),
|
|
138
|
-
},
|
|
139
|
-
new WeakRef(params.instance),
|
|
140
|
-
params.annotation,
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
this.resources.set(id, entry);
|
|
144
|
-
this.resourceInstanceIndex.set(params.instance, entry);
|
|
145
|
-
this.resourceIdList.push(id);
|
|
146
|
-
if (this.resourceIdList.length > MAX_RESOURCE_RECORDS) {
|
|
147
|
-
this._clearResources();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
68
|
// TODO(burdon): Not implemented.
|
|
152
69
|
addLink(parent: any, child: any, opts: AddLinkOptions): void {}
|
|
153
|
-
|
|
154
|
-
getDiagnostics(): Diagnostics {
|
|
155
|
-
this.refresh();
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
resources: Object.fromEntries(
|
|
159
|
-
Array.from(this.resources.entries()).map(([id, entry]) => [
|
|
160
|
-
`${entry.sanitizedClassName}#${entry.data.instanceId}`,
|
|
161
|
-
entry.data,
|
|
162
|
-
]),
|
|
163
|
-
),
|
|
164
|
-
logs: this.logs.filter((log) => log.level >= LogLevel.INFO),
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
getResourceInfo(instance: any): Record<string, any> {
|
|
169
|
-
const res: Record<string, any> = {};
|
|
170
|
-
const tracingContext = getTracingContext(Object.getPrototypeOf(instance));
|
|
171
|
-
for (const [key, { options }] of Object.entries(tracingContext.infoProperties)) {
|
|
172
|
-
try {
|
|
173
|
-
const value = typeof instance[key] === 'function' ? instance[key]() : instance[key];
|
|
174
|
-
if (options.enum) {
|
|
175
|
-
res[key] = options.enum[value];
|
|
176
|
-
} else {
|
|
177
|
-
res[key] = sanitizeValue(
|
|
178
|
-
value,
|
|
179
|
-
options.depth === undefined ? 1 : (options.depth ?? MAX_INFO_OBJECT_DEPTH),
|
|
180
|
-
this,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
} catch (err: any) {
|
|
184
|
-
res[key] = err.message;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return res;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
getResourceMetrics(instance: any): Metric[] {
|
|
192
|
-
const res: Metric[] = [];
|
|
193
|
-
const tracingContext = getTracingContext(Object.getPrototypeOf(instance));
|
|
194
|
-
for (const [key, _opts] of Object.entries(tracingContext.metricsProperties)) {
|
|
195
|
-
res.push(instance[key].getData());
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return res;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
getResourceId(instance: any): number | null {
|
|
202
|
-
const entry = this.resourceInstanceIndex.get(instance);
|
|
203
|
-
return entry ? entry.data.id : null;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
findResourcesByClassName(className: string): ResourceEntry[] {
|
|
207
|
-
return [...this.resources.values()].filter(
|
|
208
|
-
(res) => res.data.className === className || res.sanitizedClassName === className,
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
findResourcesByAnnotation(annotation: symbol): ResourceEntry[] {
|
|
213
|
-
return [...this.resources.values()].filter((res) => res.annotation === annotation);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
refresh(): void {
|
|
217
|
-
for (const resource of this.resources.values()) {
|
|
218
|
-
const instance = resource.instance.deref();
|
|
219
|
-
if (!instance) {
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const tracingContext = getTracingContext(Object.getPrototypeOf(instance));
|
|
224
|
-
const time = performance.now();
|
|
225
|
-
(instance as TimeAware).tick?.(time);
|
|
226
|
-
for (const key of Object.keys(tracingContext.metricsProperties)) {
|
|
227
|
-
(instance[key] as BaseCounter)._tick?.(time);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
resource.data.info = this.getResourceInfo(instance);
|
|
231
|
-
resource.data.metrics = this.getResourceMetrics(instance);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
private _clearResources(): void {
|
|
236
|
-
while (this.resourceIdList.length > MAX_RESOURCE_RECORDS) {
|
|
237
|
-
const id = this.resourceIdList.shift()!;
|
|
238
|
-
this.resources.delete(id);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private _pushLog(log: LogEntry): void {
|
|
243
|
-
this.logs.push(log);
|
|
244
|
-
if (this.logs.length > MAX_LOG_RECORDS) {
|
|
245
|
-
this.logs.shift();
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private _logProcessor: LogProcessor = (config, entry) => {
|
|
250
|
-
switch (entry.level) {
|
|
251
|
-
case LogLevel.ERROR:
|
|
252
|
-
case LogLevel.WARN:
|
|
253
|
-
case LogLevel.TRACE: {
|
|
254
|
-
const scope = entry.meta?.S;
|
|
255
|
-
const resource = this.resourceInstanceIndex.get(scope);
|
|
256
|
-
if (!resource) {
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const context: Record<string, any> = { ...entry.computedContext };
|
|
261
|
-
if (entry.computedError !== undefined) {
|
|
262
|
-
context.error = entry.computedError;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const { filename, line } = entry.computedMeta;
|
|
266
|
-
const entryToPush: LogEntry = {
|
|
267
|
-
level: entry.level,
|
|
268
|
-
message: entry.message ?? entry.computedError ?? '',
|
|
269
|
-
context,
|
|
270
|
-
timestamp: new Date(entry.timestamp),
|
|
271
|
-
meta: {
|
|
272
|
-
file: filename ?? '',
|
|
273
|
-
line: line ?? 0,
|
|
274
|
-
resourceId: resource.data.id,
|
|
275
|
-
},
|
|
276
|
-
};
|
|
277
|
-
this._pushLog(entryToPush);
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
default:
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
70
|
}
|
|
284
71
|
|
|
285
72
|
export const TRACE_PROCESSOR: TraceProcessor = ((globalThis as any).TRACE_PROCESSOR ??= new TraceProcessor());
|
|
286
73
|
|
|
287
|
-
const sanitizeValue = (value: any, depth: number, traceProcessor: TraceProcessor): any => {
|
|
288
|
-
switch (typeof value) {
|
|
289
|
-
case 'string':
|
|
290
|
-
case 'number':
|
|
291
|
-
case 'boolean':
|
|
292
|
-
case 'undefined':
|
|
293
|
-
return value;
|
|
294
|
-
case 'object':
|
|
295
|
-
case 'function':
|
|
296
|
-
if (value === null) {
|
|
297
|
-
return value;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
{
|
|
301
|
-
const resourceEntry = traceProcessor.resourceInstanceIndex.get(value);
|
|
302
|
-
if (resourceEntry) {
|
|
303
|
-
return `${resourceEntry.sanitizedClassName}#${resourceEntry.data.instanceId}`;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (typeof value.toJSON === 'function') {
|
|
308
|
-
return sanitizeValue(value.toJSON(), depth, traceProcessor);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (depth > 0) {
|
|
312
|
-
if (isSetLike(value)) {
|
|
313
|
-
return Object.fromEntries(
|
|
314
|
-
Array.from(value.entries()).map((value) => sanitizeValue(value, depth - 1, traceProcessor)),
|
|
315
|
-
);
|
|
316
|
-
} else if (isMapLike(value)) {
|
|
317
|
-
return Object.fromEntries(
|
|
318
|
-
Array.from(value.entries()).map(([key, value]) => [key, sanitizeValue(value, depth - 1, traceProcessor)]),
|
|
319
|
-
);
|
|
320
|
-
} else if (Array.isArray(value)) {
|
|
321
|
-
return value.map((item: any) => sanitizeValue(item, depth - 1, traceProcessor));
|
|
322
|
-
} else if (typeof value === 'object') {
|
|
323
|
-
const res: any = {};
|
|
324
|
-
for (const key of Object.keys(value)) {
|
|
325
|
-
res[key] = sanitizeValue(value[key], depth - 1, traceProcessor);
|
|
326
|
-
}
|
|
327
|
-
return res;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (typeof value.truncate === 'function') {
|
|
332
|
-
return value.truncate();
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return value.toString();
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
|
|
339
74
|
export const sanitizeClassName = (className: string) => {
|
|
340
75
|
let name = className.replace(/^_+/, '');
|
|
341
76
|
const SANITIZE_REGEX = /[^_](\d+)$/;
|
|
@@ -345,11 +80,3 @@ export const sanitizeClassName = (className: string) => {
|
|
|
345
80
|
}
|
|
346
81
|
return name;
|
|
347
82
|
};
|
|
348
|
-
|
|
349
|
-
const isSetLike = (value: any): value is Set<any> =>
|
|
350
|
-
value instanceof Set ||
|
|
351
|
-
(typeof value === 'object' && value !== null && Object.getPrototypeOf(value).constructor.name === 'ComplexSet');
|
|
352
|
-
|
|
353
|
-
const isMapLike = (value: any): value is Map<any, any> =>
|
|
354
|
-
value instanceof Map ||
|
|
355
|
-
(typeof value === 'object' && value !== null && Object.getPrototypeOf(value).constructor.name === 'ComplexMap');
|