diff --git a/packages/nodejs/.changesets/add-createrootspan-function.md b/packages/nodejs/.changesets/add-createrootspan-function.md new file mode 100644 index 00000000..698b16ff --- /dev/null +++ b/packages/nodejs/.changesets/add-createrootspan-function.md @@ -0,0 +1,6 @@ +--- +bump: "patch" +type: "add" +--- + +Add the `createRootSpan` function to the Tracer to allow explicit creation of RootSpans even if another RootSpan already exists and is tracked as the current RootSpan. Make sure to not forget about the previous RootSpan, and close it as well at some point when using this function. diff --git a/packages/nodejs/src/__tests__/scope.test.ts b/packages/nodejs/src/__tests__/scope.test.ts index 9b002c32..f883e38d 100644 --- a/packages/nodejs/src/__tests__/scope.test.ts +++ b/packages/nodejs/src/__tests__/scope.test.ts @@ -93,18 +93,6 @@ describe("ScopeManager", () => { expect(scopeManager.root()).toBe(span) expect(scopeManager.active()).toBe(span) }) - - describe("when there is an active span", () => { - it("does not set the active span for the current process", () => { - const rootSpan = new RootSpan() - const childSpan = new ChildSpan(rootSpan) - - scopeManager.withContext(childSpan, () => { - scopeManager.setRoot(rootSpan) - expect(scopeManager.active()).toBe(childSpan) - }) - }) - }) }) describe(".withContext()", () => { diff --git a/packages/nodejs/src/__tests__/tracer.test.ts b/packages/nodejs/src/__tests__/tracer.test.ts index a0f3effd..b0a3e01b 100644 --- a/packages/nodejs/src/__tests__/tracer.test.ts +++ b/packages/nodejs/src/__tests__/tracer.test.ts @@ -37,6 +37,22 @@ describe("Tracer", () => { }) }) + describe(".createRootSpan()", () => { + it("creates a new span and assigns it as a root span", () => { + const rootSpan1 = tracer.createRootSpan() + expect(rootSpan1).toBeInstanceOf(RootSpan) + rootSpan1.close() + expect(tracer.rootSpan()).toEqual(rootSpan1) + expect(tracer.currentSpan()).toEqual(rootSpan1) + + const rootSpan2 = tracer.createRootSpan() + expect(rootSpan2).toBeInstanceOf(RootSpan) + rootSpan2.close() + expect(tracer.rootSpan()).toEqual(rootSpan2) + expect(tracer.currentSpan()).toEqual(rootSpan2) + }) + }) + describe(".sendError()", () => { const err = new Error("FooBarError") diff --git a/packages/nodejs/src/instrumentation/http/lifecycle/incoming.ts b/packages/nodejs/src/instrumentation/http/lifecycle/incoming.ts index 25b07f63..6eaf44b5 100644 --- a/packages/nodejs/src/instrumentation/http/lifecycle/incoming.ts +++ b/packages/nodejs/src/instrumentation/http/lifecycle/incoming.ts @@ -43,7 +43,7 @@ function incomingRequest( } const rootSpan = tracer - .createSpan() + .createRootSpan() /** * For our processor to work, root `Span`s must have a groupable, non-dynamic * name to be easily grouped into performance samples. diff --git a/packages/nodejs/src/interfaces/tracer.ts b/packages/nodejs/src/interfaces/tracer.ts index 63a7181a..0a48747c 100644 --- a/packages/nodejs/src/interfaces/tracer.ts +++ b/packages/nodejs/src/interfaces/tracer.ts @@ -20,6 +20,13 @@ export interface Tracer { */ createSpan(options?: Partial, context?: SpanContext): Span + /** + * Creates a new `Span` instance that is always the new RootSpan in the current + * async context. If a previous RootSpan existed, it's ignored from this point on. + * Make sure it's closed beforehand or handled by another part of the app. + */ + createRootSpan(options?: Partial): Span + /** * Returns the current Span. * diff --git a/packages/nodejs/src/noops/tracer.ts b/packages/nodejs/src/noops/tracer.ts index d0a5036c..77ceee81 100644 --- a/packages/nodejs/src/noops/tracer.ts +++ b/packages/nodejs/src/noops/tracer.ts @@ -14,6 +14,10 @@ export class NoopTracer implements Tracer { return new NoopSpan() } + public createRootSpan(_options?: Partial): Span { + return new NoopSpan() + } + public currentSpan(): Span { return new NoopSpan() } diff --git a/packages/nodejs/src/scope.ts b/packages/nodejs/src/scope.ts index 16e59412..7c51e075 100644 --- a/packages/nodejs/src/scope.ts +++ b/packages/nodejs/src/scope.ts @@ -117,9 +117,7 @@ export class ScopeManager { public setRoot(rootSpan: Span) { const uid = asyncHooks.executionAsyncId() this.#roots.set(uid, rootSpan) - if (!this.#scopes.has(uid)) { - this.#scopes.set(uid, rootSpan) - } + this.#scopes.set(uid, rootSpan) } /* diff --git a/packages/nodejs/src/tracer.ts b/packages/nodejs/src/tracer.ts index 0091c190..ab24136c 100644 --- a/packages/nodejs/src/tracer.ts +++ b/packages/nodejs/src/tracer.ts @@ -44,14 +44,18 @@ export class BaseTracer implements Tracer { if (spanOrContext) { return new ChildSpan(spanOrContext, options) } else if (activeRootSpan instanceof NoopSpan) { - const rootSpan = new RootSpan(options) - this.#scopeManager.setRoot(rootSpan) - return rootSpan + return this.createRootSpan(options) } else { return new ChildSpan(activeRootSpan, options) } } + public createRootSpan(options?: Partial): Span { + const rootSpan = new RootSpan(options) + this.#scopeManager.setRoot(rootSpan) + return rootSpan + } + /** * Returns the current Span. *