diff --git a/bun.lockb b/bun.lockb index c5404f5..08d299d 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 7c4518c..34d7903 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "prettier": "@marais/prettier", "dependencies": { "flattie": "^1.1.1", - "tctx": "^0.1.0" + "tctx": "^0.2.3" }, "devDependencies": { "@marais/prettier": "0.0.4", diff --git a/src/async.test.ts b/src/async.test.ts index 050eeea..74aaabb 100644 --- a/src/async.test.ts +++ b/src/async.test.ts @@ -3,7 +3,7 @@ import { AsyncLocalStorage } from 'node:async_hooks'; import { suite, test } from 'uvu'; import * as assert from 'uvu/assert'; -import { make } from 'tctx'; +import { make } from 'tctx/traceparent'; import type { Exporter } from 'rian'; import * as rian from 'rian/async'; diff --git a/src/async.ts b/src/async.ts index 62048d5..1804b01 100644 --- a/src/async.ts +++ b/src/async.ts @@ -4,8 +4,8 @@ import type { CallableScope, ClockLike, Options, Sampler, Scope, Span } from 'ri import type { Tracer } from 'rian/async'; import { measure } from 'rian/utils'; -import { type Traceparent } from 'tctx'; -import * as tctx from 'tctx'; +import { type Traceparent } from 'tctx/traceparent'; +import * as traceparent from 'tctx/traceparent'; import { span_buffer, wait_promises } from './_internal'; @@ -37,11 +37,12 @@ export function span(name: string, parent_id?: Traceparent | string) { const should_sample = api.sampler; // --- - const parent = (typeof parent_id === 'string' ? tctx.parse(parent_id) : (parent_id || current_span?.traceparent)); - const id = parent?.child() || tctx.make(); + const parent = (typeof parent_id === 'string' ? traceparent.parse(parent_id) : (parent_id || current_span?.traceparent)); + const id = parent?.child() || traceparent.make(); - const is_sampling = typeof should_sample == 'boolean' ? should_sample : should_sample(name, id, scope); - !is_sampling ? tctx.unsample(id) : tctx.sample(id); + const is_sampling = typeof should_sample == 'boolean' ? should_sample : should_sample(id.parent_id, parent, name, scope); + if (is_sampling) traceparent.sample(id); + else traceparent.unsample(id); const span_obj: Span = { id, parent, name, diff --git a/src/index.d.ts b/src/index.d.ts index 1ace399..b150214 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -49,13 +49,17 @@ export type Context = { */ export type Sampler = ( /** - * The name of the span. + * The id of the new span looking for a sampling decision. + */ + id: string, + /** + * The parent id of the new span looking for a sampling decision. */ - name: string, + parent: Traceparent | undefined, /** - * The traceparent id of the span. + * The name of the span. */ - id: Traceparent, + name: string, /** * The tracer this span belongs to. */ diff --git a/src/index.ts b/src/index.ts index 81bfc92..18a5fae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,8 @@ import type { CallableScope, Options, Span, Tracer } from 'rian'; import { measure } from 'rian/utils'; import { span_buffer, wait_promises } from './_internal'; -import { type Traceparent } from 'tctx'; -import * as tctx from 'tctx'; +import { type Traceparent } from 'tctx/traceparent'; +import * as traceparent from 'tctx/traceparent'; export { report, configure } from './_internal'; @@ -21,11 +21,12 @@ export function tracer(name: string, options?: Options): Tracer { parent_id?: Traceparent | string, ): CallableScope => { // --- - const parent = (typeof parent_id === 'string' ? tctx.parse(parent_id) : parent_id); - const id = parent?.child() || tctx.make(); + const parent = (typeof parent_id === 'string' ? traceparent.parse(parent_id) : parent_id); + const id = parent?.child() || traceparent.make(); - const is_sampling = typeof should_sample == 'boolean' ? should_sample : should_sample(name, id, scope); - !is_sampling ? tctx.unsample(id) : tctx.sample(id); + const is_sampling = typeof should_sample == 'boolean' ? should_sample : should_sample(id.parent_id, parent, name, scope); + if (is_sampling) traceparent.sample(id); + else traceparent.unsample(id); const span_obj: Span = { id, parent, name, diff --git a/src/rian.test.ts b/src/rian.test.ts index 42dc9af..0287493 100644 --- a/src/rian.test.ts +++ b/src/rian.test.ts @@ -1,10 +1,11 @@ import { spy, spyOn } from 'nanospy'; -import { is_sampled } from 'tctx'; +import { is_sampled } from 'tctx/traceparent'; import { suite, test } from 'uvu'; import * as assert from 'uvu/assert'; import * as rian from 'rian'; import * as utils from 'rian/utils'; +import { traceparent } from 'tctx'; const noop = () => {}; @@ -229,9 +230,71 @@ buffer('flush all', async () => { assert.equal(spans[1].name, 'span 2'); }); +const sampler = suite('sampler'); + +sampler('should allow a sampler', async () => { + let should_sample = false; + + const tracer = rian.tracer('test', { + sampler: () => should_sample, + }); + + tracer.span('not sampled')(() => { }); + should_sample = true; + tracer.span('sampled')(() => { }); + + const exporter = spy(returns); + const scopedSpans: rian.ScopedSpans[] = await rian.report(exporter); + + assert.equal(scopedSpans.length, 1); + assert.equal(scopedSpans.at(0)!.spans.length, 1); + assert.equal(scopedSpans.at(0)?.spans.at(0)?.name, 'sampled'); +}); + +sampler('allow a sampler to make a decision from its parent', async () => { + let no_parent_sample = true; + + const S: rian.Sampler = (_id, parentId) => { + if (!parentId) return no_parent_sample; + return traceparent.is_sampled(parentId); + } + + const tracer = rian.tracer('test', { sampler: S }); + + tracer.span('sampled#1')((s) => { + no_parent_sample = false; + s.span('sampled#1.1')(() => { }) + }); + + no_parent_sample = false; + tracer.span('not sampled#1')((s) => { + s.span('not sampled#1.1')(() => { }) + }); + + no_parent_sample = true; + tracer.span('sampled#2')((s) => { + s.span('sampled#2.1')(() => { }) + }); + + no_parent_sample = false; + + const exporter = spy(returns); + const scopedSpans: rian.ScopedSpans[] = await rian.report(exporter); + + assert.equal(scopedSpans.length, 1); + assert.equal(scopedSpans.at(0)!.spans.length, 4); + assert.equal(scopedSpans.at(0)!.spans.map(s => s.name), [ + 'sampled#1', + 'sampled#1.1', + 'sampled#2', + 'sampled#2.1', + ]); +}); + test.run(); fn.run(); measure.run(); sampled.run(); events.run(); buffer.run(); +sampler.run();