@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.
Files changed (47) hide show
  1. package/dist/lib/index.mjs +509 -0
  2. package/dist/lib/index.mjs.map +1 -0
  3. package/dist/types/src/api.d.ts +0 -44
  4. package/dist/types/src/api.d.ts.map +1 -1
  5. package/dist/types/src/index.d.ts +0 -2
  6. package/dist/types/src/index.d.ts.map +1 -1
  7. package/dist/types/src/trace-processor.d.ts +0 -40
  8. package/dist/types/src/trace-processor.d.ts.map +1 -1
  9. package/dist/types/tsconfig.tsbuildinfo +1 -1
  10. package/package.json +10 -13
  11. package/src/api.ts +3 -186
  12. package/src/index.ts +0 -2
  13. package/src/trace-processor.ts +1 -274
  14. package/src/tracing.test.ts +41 -326
  15. package/dist/lib/browser/index.mjs +0 -1065
  16. package/dist/lib/browser/index.mjs.map +0 -7
  17. package/dist/lib/browser/meta.json +0 -1
  18. package/dist/lib/node-esm/index.mjs +0 -1067
  19. package/dist/lib/node-esm/index.mjs.map +0 -7
  20. package/dist/lib/node-esm/meta.json +0 -1
  21. package/dist/types/src/metrics/base.d.ts +0 -7
  22. package/dist/types/src/metrics/base.d.ts.map +0 -1
  23. package/dist/types/src/metrics/custom-counter.d.ts +0 -8
  24. package/dist/types/src/metrics/custom-counter.d.ts.map +0 -1
  25. package/dist/types/src/metrics/index.d.ts +0 -7
  26. package/dist/types/src/metrics/index.d.ts.map +0 -1
  27. package/dist/types/src/metrics/map-counter.d.ts +0 -12
  28. package/dist/types/src/metrics/map-counter.d.ts.map +0 -1
  29. package/dist/types/src/metrics/time-series-counter.d.ts +0 -15
  30. package/dist/types/src/metrics/time-series-counter.d.ts.map +0 -1
  31. package/dist/types/src/metrics/time-usage-counter.d.ts +0 -15
  32. package/dist/types/src/metrics/time-usage-counter.d.ts.map +0 -1
  33. package/dist/types/src/metrics/unary-counter.d.ts +0 -12
  34. package/dist/types/src/metrics/unary-counter.d.ts.map +0 -1
  35. package/dist/types/src/symbols.d.ts +0 -10
  36. package/dist/types/src/symbols.d.ts.map +0 -1
  37. package/dist/types/src/weak-ref.d.ts +0 -2
  38. package/dist/types/src/weak-ref.d.ts.map +0 -1
  39. package/src/metrics/base.ts +0 -26
  40. package/src/metrics/custom-counter.ts +0 -22
  41. package/src/metrics/index.ts +0 -10
  42. package/src/metrics/map-counter.ts +0 -35
  43. package/src/metrics/time-series-counter.ts +0 -52
  44. package/src/metrics/time-usage-counter.ts +0 -62
  45. package/src/metrics/unary-counter.ts +0 -31
  46. package/src/symbols.ts +0 -24
  47. package/src/weak-ref.ts +0 -18
@@ -2,78 +2,14 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { afterEach, beforeEach, describe, expect, test } from 'vitest';
5
+ import { afterEach, beforeEach, describe, test } from 'vitest';
6
6
 
7
- import { Context, Resource, TRACE_SPAN_ATTRIBUTE, type TraceContextData } from '@dxos/context';
7
+ import { Context, TRACE_SPAN_ATTRIBUTE, type TraceContextData } from '@dxos/context';
8
8
 
9
9
  import { trace } from './api';
10
10
  import { TRACE_PROCESSOR } from './trace-processor';
11
11
  import type { RemoteSpan, StartSpanOptions, TracingBackend } from './tracing-types';
12
12
 
13
- const FeedStoreResource = Symbol.for('FeedStore');
14
-
15
- @trace.resource({ annotation: FeedStoreResource })
16
- class FeedStore {
17
- private readonly _storage: any;
18
-
19
- private readonly _feeds = new Map<string, Feed>();
20
-
21
- @trace.info()
22
- get dataStore() {
23
- return this._storage.type;
24
- }
25
-
26
- @trace.span()
27
- async openFeed(ctx: Context): Promise<Feed> {
28
- const feed = new Feed();
29
- feed.key = Math.random().toString(36).substring(2, 15);
30
- this._feeds.set(feed.key, feed);
31
- trace.addLink(this, feed, {});
32
-
33
- await feed.open(ctx);
34
- return feed;
35
- }
36
- }
37
-
38
- @trace.resource()
39
- class Feed {
40
- @trace.info()
41
- key!: string;
42
-
43
- @trace.span()
44
- async open(ctx: Context): Promise<void> {}
45
-
46
- @trace.span()
47
- async close(): Promise<void> {
48
- throw new Error('Not implemented');
49
- }
50
- }
51
-
52
- describe('tracing', () => {
53
- beforeEach(() => {
54
- TRACE_PROCESSOR.resources.clear();
55
- });
56
-
57
- test('feed store tracing', async () => {
58
- const store = new FeedStore();
59
- const feed1 = await store.openFeed(new Context());
60
- const feed2 = await store.openFeed(new Context());
61
-
62
- await feed2.close().catch(() => {});
63
-
64
- expect([...TRACE_PROCESSOR.resources.values()].map((r) => r.instance.deref())).to.deep.eq([store, feed1, feed2]);
65
- });
66
-
67
- test('findByAnnotation', async () => {
68
- const store = new FeedStore();
69
- expect(TRACE_PROCESSOR.findResourcesByAnnotation(FeedStoreResource)[0].instance.deref()).to.eq(store);
70
- });
71
- });
72
-
73
- //
74
- // Lifecycle span tests
75
- //
76
-
77
13
  type SpanRecord = {
78
14
  options: StartSpanOptions;
79
15
  ended: boolean;
@@ -113,11 +49,14 @@ const createMockBackend = (): { backend: TracingBackend; spans: SpanRecord[] } =
113
49
  return { backend, spans };
114
50
  };
115
51
 
116
- describe('lifecycle span', () => {
52
+ //
53
+ // Manual span tests
54
+ //
55
+
56
+ describe('manual spans', () => {
117
57
  let savedBackend: typeof TRACE_PROCESSOR.tracingBackend;
118
58
 
119
59
  beforeEach(() => {
120
- TRACE_PROCESSOR.resources.clear();
121
60
  savedBackend = TRACE_PROCESSOR.tracingBackend;
122
61
  spanCounter = 0;
123
62
  });
@@ -126,277 +65,60 @@ describe('lifecycle span', () => {
126
65
  TRACE_PROCESSOR.tracingBackend = savedBackend;
127
66
  });
128
67
 
129
- test('lifecycle span is started on open and ended on close', async ({ expect }) => {
130
- const { backend, spans } = createMockBackend();
131
- TRACE_PROCESSOR.tracingBackend = backend;
132
-
133
- @trace.resource({ lifecycle: true })
134
- class TestResource extends Resource {
135
- protected override async _open(_ctx: Context) {}
136
- protected override async _close(_ctx: Context) {}
137
- }
138
-
139
- const resource = new TestResource();
140
- await resource.open();
141
-
142
- const lifecycleSpan = spans.find((span) => span.options.name === 'TestResource.lifecycle');
143
- expect(lifecycleSpan).toBeDefined();
144
- expect(lifecycleSpan!.options.op).toBe('lifecycle');
145
- expect(lifecycleSpan!.ended).toBe(false);
146
-
147
- await resource.close();
148
- expect(lifecycleSpan!.ended).toBe(true);
149
- });
150
-
151
- test('this._ctx carries lifecycle span trace context', async ({ expect }) => {
152
- const { backend, spans } = createMockBackend();
153
- TRACE_PROCESSOR.tracingBackend = backend;
154
-
155
- let capturedCtxTraceData: TraceContextData | undefined;
156
-
157
- @trace.resource({ lifecycle: true })
158
- class TestResource extends Resource {
159
- protected override async _open(_ctx: Context) {
160
- capturedCtxTraceData = this._ctx.getAttribute(TRACE_SPAN_ATTRIBUTE);
161
- }
162
- }
163
-
164
- const resource = new TestResource();
165
- await resource.open();
166
-
167
- const lifecycleSpan = spans.find((span) => span.options.name === 'TestResource.lifecycle');
168
- expect(capturedCtxTraceData).toBeDefined();
169
- expect(capturedCtxTraceData!.traceparent).toBe(lifecycleSpan!.spanContext.traceparent);
170
-
171
- await resource.close();
172
- });
173
-
174
- test('@trace.span() inside _open is child of lifecycle span', async ({ expect }) => {
175
- const { backend, spans } = createMockBackend();
176
- TRACE_PROCESSOR.tracingBackend = backend;
177
-
178
- @trace.resource({ lifecycle: true })
179
- class TestResource extends Resource {
180
- @trace.span()
181
- protected override async _open(_ctx: Context) {}
182
- }
183
-
184
- const resource = new TestResource();
185
- await resource.open();
186
-
187
- const lifecycleSpan = spans.find((span) => span.options.name === 'TestResource.lifecycle');
188
- const openSpan = spans.find((span) => span.options.name === 'TestResource._open');
189
- expect(lifecycleSpan).toBeDefined();
190
- expect(openSpan).toBeDefined();
191
- expect(openSpan!.options.parentContext?.traceparent).toBe(lifecycleSpan!.spanContext.traceparent);
192
-
193
- await resource.close();
194
- });
195
-
196
- test('subscriptions use lifecycle context, not _open context', async ({ expect }) => {
197
- const { backend, spans } = createMockBackend();
198
- TRACE_PROCESSOR.tracingBackend = backend;
199
-
200
- let subscriptionParentTrace: TraceContextData | undefined;
201
-
202
- @trace.resource({ lifecycle: true })
203
- class TestResource extends Resource {
204
- @trace.span()
205
- protected override async _open(_ctx: Context) {
206
- // Simulate a subscription callback using this._ctx.
207
- subscriptionParentTrace = this._ctx.getAttribute(TRACE_SPAN_ATTRIBUTE);
208
- }
209
- }
210
-
211
- const resource = new TestResource();
212
- await resource.open();
213
-
214
- const lifecycleSpan = spans.find((span) => span.options.name === 'TestResource.lifecycle');
215
- const openSpan = spans.find((span) => span.options.name === 'TestResource._open');
216
-
217
- expect(subscriptionParentTrace!.traceparent).toBe(lifecycleSpan!.spanContext.traceparent);
218
- expect(subscriptionParentTrace!.traceparent).not.toBe(openSpan!.spanContext.traceparent);
219
-
220
- await resource.close();
221
- });
222
-
223
- test('nested resource lifecycles are nested', async ({ expect }) => {
224
- const { backend, spans } = createMockBackend();
225
- TRACE_PROCESSOR.tracingBackend = backend;
226
-
227
- @trace.resource({ lifecycle: true })
228
- class ChildResource extends Resource {
229
- protected override async _open(_ctx: Context) {}
230
- }
231
-
232
- @trace.resource({ lifecycle: true })
233
- class ParentResource extends Resource {
234
- child = new ChildResource();
235
-
236
- @trace.span()
237
- protected override async _open(ctx: Context) {
238
- await this.child.open(ctx);
239
- }
240
-
241
- protected override async _close(_ctx: Context) {
242
- await this.child.close();
243
- }
244
- }
245
-
246
- const parent = new ParentResource();
247
- await parent.open();
248
-
249
- const parentLifecycle = spans.find((span) => span.options.name === 'ParentResource.lifecycle');
250
- const parentOpen = spans.find((span) => span.options.name === 'ParentResource._open');
251
- const childLifecycle = spans.find((span) => span.options.name === 'ChildResource.lifecycle');
252
-
253
- expect(parentLifecycle).toBeDefined();
254
- expect(parentOpen).toBeDefined();
255
- expect(childLifecycle).toBeDefined();
256
-
257
- // Parent._open is child of Parent.lifecycle.
258
- expect(parentOpen!.options.parentContext?.traceparent).toBe(parentLifecycle!.spanContext.traceparent);
259
- // Child.lifecycle is child of Parent._open (direct async call).
260
- expect(childLifecycle!.options.parentContext?.traceparent).toBe(parentOpen!.spanContext.traceparent);
261
-
262
- await parent.close();
263
- expect(childLifecycle!.ended).toBe(true);
264
- expect(parentLifecycle!.ended).toBe(true);
265
- });
266
-
267
- test('lifecycle requires Resource base class', ({ expect }) => {
268
- expect(() => {
269
- @trace.resource({ lifecycle: true })
270
- class NotAResource {
271
- async open() {}
272
- async close() {}
273
- }
274
- void NotAResource;
275
- }).toThrow(/requires.*to extend Resource/);
276
- });
277
-
278
- test('no lifecycle span without opt-in', async ({ expect }) => {
279
- const { backend, spans } = createMockBackend();
280
- TRACE_PROCESSOR.tracingBackend = backend;
281
-
282
- @trace.resource()
283
- class PlainResource extends Resource {
284
- @trace.span()
285
- protected override async _open(_ctx: Context) {}
286
- }
287
-
288
- const resource = new PlainResource();
289
- await resource.open();
290
-
291
- const lifecycleSpan = spans.find((span) => span.options.name === 'PlainResource.lifecycle');
292
- expect(lifecycleSpan).toBeUndefined();
293
-
294
- await resource.close();
295
- });
296
-
297
- test('lifecycle span records error when _open throws', async ({ expect }) => {
68
+ test('spanStart nests under the parent context and spanEnd ends the span', ({ expect }) => {
298
69
  const { backend, spans } = createMockBackend();
299
70
  TRACE_PROCESSOR.tracingBackend = backend;
300
71
 
301
- const openError = new Error('open failed');
302
-
303
- @trace.resource({ lifecycle: true })
304
- class FailingResource extends Resource {
305
- protected override async _open(_ctx: Context) {
306
- throw openError;
307
- }
308
- }
309
-
310
- const resource = new FailingResource();
311
- await expect(resource.open()).rejects.toThrow('open failed');
312
-
313
- const lifecycleSpan = spans.find((span) => span.options.name === 'FailingResource.lifecycle');
314
- expect(lifecycleSpan).toBeDefined();
315
- expect(lifecycleSpan!.error).toBe(openError);
316
- expect(lifecycleSpan!.ended).toBe(true);
317
- });
318
-
319
- test('double open does not start a second lifecycle span', async ({ expect }) => {
320
- const { backend, spans } = createMockBackend();
321
- TRACE_PROCESSOR.tracingBackend = backend;
72
+ const parentTrace: TraceContextData = {
73
+ traceparent: '00-bbbb0000bbbb0000bbbb0000bbbb0000-cccc0000cccc0000-01',
74
+ };
75
+ const parentCtx = new Context({ attributes: { [TRACE_SPAN_ATTRIBUTE]: parentTrace } });
322
76
 
323
- @trace.resource({ lifecycle: true })
324
- class TestResource extends Resource {
325
- protected override async _open(_ctx: Context) {}
326
- }
77
+ const childCtx = trace.spanStart({ id: 'op-1', instance: {}, methodName: 'work', parentCtx });
327
78
 
328
- const resource = new TestResource();
329
- await resource.open();
330
- await resource.open();
79
+ const span = spans.find((record) => record.options.name.endsWith('.work'));
80
+ expect(span).toBeDefined();
81
+ expect(span!.options.parentContext?.traceparent).toBe(parentTrace.traceparent);
331
82
 
332
- const lifecycleSpans = spans.filter((span) => span.options.name === 'TestResource.lifecycle');
333
- expect(lifecycleSpans).toHaveLength(1);
83
+ // The returned ctx carries the new span's trace context so downstream spans nest under it.
84
+ expect(childCtx?.getAttribute(TRACE_SPAN_ATTRIBUTE)?.traceparent).toBe(span!.spanContext.traceparent);
334
85
 
335
- await resource.close();
86
+ expect(span!.ended).toBe(false);
87
+ trace.spanEnd('op-1');
88
+ expect(span!.ended).toBe(true);
336
89
  });
337
90
 
338
- test('lifecycle span works without tracing backend', async () => {
339
- TRACE_PROCESSOR.tracingBackend = undefined;
340
-
341
- @trace.resource({ lifecycle: true })
342
- class TestResource extends Resource {
343
- protected override async _open(_ctx: Context) {}
344
- }
345
-
346
- const resource = new TestResource();
347
- await resource.open();
348
- await resource.close();
349
- });
350
-
351
- test('caller trace context is parent of lifecycle span', async ({ expect }) => {
91
+ test('spanStart with showInRemoteTracing:false creates no remote span and returns parentCtx unchanged', ({
92
+ expect,
93
+ }) => {
352
94
  const { backend, spans } = createMockBackend();
353
95
  TRACE_PROCESSOR.tracingBackend = backend;
354
96
 
355
- @trace.resource({ lifecycle: true })
356
- class TestResource extends Resource {
357
- protected override async _open(_ctx: Context) {}
358
- }
359
-
360
- const callerTraceData: TraceContextData = {
361
- traceparent: '00-bbbb0000bbbb0000bbbb0000bbbb0000-cccc0000cccc0000-01',
362
- };
363
- const callerCtx = new Context({ attributes: { [TRACE_SPAN_ATTRIBUTE]: callerTraceData } });
364
-
365
- const resource = new TestResource();
366
- await resource.open(callerCtx);
367
-
368
- const lifecycleSpan = spans.find((span) => span.options.name === 'TestResource.lifecycle');
369
- expect(lifecycleSpan!.options.parentContext?.traceparent).toBe(callerTraceData.traceparent);
97
+ const parentCtx = new Context();
98
+ const result = trace.spanStart({
99
+ id: 'op-2',
100
+ instance: {},
101
+ methodName: 'work',
102
+ parentCtx,
103
+ showInRemoteTracing: false,
104
+ });
370
105
 
371
- await resource.close();
106
+ expect(result).toBe(parentCtx);
107
+ expect(spans.find((record) => record.options.name.endsWith('.work'))).toBeUndefined();
372
108
  });
373
109
 
374
- test('lifecycle span records error and ends when _close throws', async ({ expect }) => {
110
+ test('duplicate spanStart id returns parentCtx without starting a second span', ({ expect }) => {
375
111
  const { backend, spans } = createMockBackend();
376
112
  TRACE_PROCESSOR.tracingBackend = backend;
377
113
 
378
- const closeError = new Error('close failed');
379
-
380
- @trace.resource({ lifecycle: true })
381
- class FailingCloseResource extends Resource {
382
- protected override async _open(_ctx: Context) {}
383
-
384
- protected override async _close(_ctx: Context) {
385
- throw closeError;
386
- }
387
- }
388
-
389
- const resource = new FailingCloseResource();
390
- await resource.open();
391
-
392
- const lifecycleSpan = spans.find((span) => span.options.name === 'FailingCloseResource.lifecycle');
393
- expect(lifecycleSpan).toBeDefined();
394
- expect(lifecycleSpan!.ended).toBe(false);
114
+ const parentCtx = new Context();
115
+ trace.spanStart({ id: 'op-3', instance: {}, methodName: 'work', parentCtx });
116
+ const secondResult = trace.spanStart({ id: 'op-3', instance: {}, methodName: 'work', parentCtx });
395
117
 
396
- await expect(resource.close()).rejects.toThrow('close failed');
118
+ expect(secondResult).toBe(parentCtx);
119
+ expect(spans.filter((record) => record.options.name.endsWith('.work'))).toHaveLength(1);
397
120
 
398
- expect(lifecycleSpan!.error).toBe(closeError);
399
- expect(lifecycleSpan!.ended).toBe(true);
121
+ trace.spanEnd('op-3');
400
122
  });
401
123
  });
402
124
 
@@ -408,7 +130,6 @@ describe('buffering backend', () => {
408
130
  let savedBackend: typeof TRACE_PROCESSOR.tracingBackend;
409
131
 
410
132
  beforeEach(() => {
411
- TRACE_PROCESSOR.resources.clear();
412
133
  savedBackend = TRACE_PROCESSOR.tracingBackend;
413
134
  TRACE_PROCESSOR.tracingBackend = undefined;
414
135
  spanCounter = 0;
@@ -421,7 +142,6 @@ describe('buffering backend', () => {
421
142
  test('buffered spans are replayed into real backend on drain', async ({ expect }) => {
422
143
  const parentCtx = new Context();
423
144
 
424
- @trace.resource()
425
145
  class Svc {
426
146
  @trace.span()
427
147
  async work(ctx: Context) {}
@@ -440,7 +160,6 @@ describe('buffering backend', () => {
440
160
  });
441
161
 
442
162
  test('parent-child hierarchy is preserved across drain', ({ expect }) => {
443
- @trace.resource()
444
163
  class Svc {
445
164
  @trace.span()
446
165
  async parent(ctx: Context) {
@@ -467,7 +186,6 @@ describe('buffering backend', () => {
467
186
  test('stale buffered parent IDs on in-flight contexts are translated post-drain', async ({ expect }) => {
468
187
  let capturedCtx: Context | undefined;
469
188
 
470
- @trace.resource()
471
189
  class Svc {
472
190
  @trace.span()
473
191
  async setup(ctx: Context) {
@@ -500,7 +218,6 @@ describe('buffering backend', () => {
500
218
  test('errors and end() are replayed on drain', async ({ expect }) => {
501
219
  const testError = new Error('boom');
502
220
 
503
- @trace.resource()
504
221
  class Svc {
505
222
  @trace.span()
506
223
  async failingWork(ctx: Context) {
@@ -526,7 +243,6 @@ describe('buffering backend', () => {
526
243
  resolveWork = resolve;
527
244
  });
528
245
 
529
- @trace.resource()
530
246
  class Svc {
531
247
  @trace.span()
532
248
  async longWork(ctx: Context) {
@@ -553,7 +269,6 @@ describe('buffering backend', () => {
553
269
  test('replayed spans preserve original start and end timestamps', async ({ expect }) => {
554
270
  const beforeStart = Date.now();
555
271
 
556
- @trace.resource()
557
272
  class Svc {
558
273
  @trace.span()
559
274
  async work(ctx: Context) {}