From 777c473a9d4f4240992fb47282c40ac1834e0784 Mon Sep 17 00:00:00 2001 From: Sean Dearnaley Date: Sun, 30 Aug 2026 21:12:49 -0400 Subject: [PATCH] fix: finish spans when decorated methods throw --- src/decorator.injector.spec.ts | 28 ++++++++++++++-------------- src/decorator.injector.ts | 4 +++- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/decorator.injector.spec.ts b/src/decorator.injector.spec.ts index 098999d..260003f 100644 --- a/src/decorator.injector.spec.ts +++ b/src/decorator.injector.spec.ts @@ -186,13 +186,15 @@ describe('DecoratorInjector', () => { scopeSpy.mockClear(); }); - it('should record exception with sync function', async () => { + it('should record and finish a span when a sync function throws', async () => { // given + const error = new Error('hello'); + @Injectable() class HelloService { @Span('hello') hi() { - throw new Error('hello'); + throw error; } } @@ -220,9 +222,7 @@ describe('DecoratorInjector', () => { .mockImplementation(() => scope); // when - expect(() => { - helloService.hi(); - }).toThrowError('hello'); + expect(() => helloService.hi()).toThrow(error); // then expect( @@ -237,22 +237,22 @@ describe('DecoratorInjector', () => { expect(tracer.startSpan).toHaveBeenCalledWith('hello', { childOf: null }); expect(tracer.scope().active).toHaveBeenCalled(); expect(tracer.scope().activate).toHaveBeenCalled(); - expect(mockSpan.finish).toHaveBeenCalled(); - expect(mockSpan.setTag).toHaveBeenCalledWith('error', new Error('hello')); + expect(mockSpan.setTag).toHaveBeenCalledWith('error', error); + expect(mockSpan.finish).toHaveBeenCalledTimes(1); startSpanSpy.mockClear(); scopeSpy.mockClear(); }); - it('should record exception with async function', async () => { + it('should record and finish a span when an async function rejects', async () => { // given + const error = new Error('hello'); + @Injectable() class HelloService { @Span('hello') async hi() { - return new Promise((_resolve, reject) => { - setTimeout(() => reject(new Error('hello')), 100); - }); + throw error; } } @@ -280,7 +280,7 @@ describe('DecoratorInjector', () => { .mockImplementation(() => scope); // when - await expect(helloService.hi()).rejects.toEqual(new Error('hello')); + await expect(helloService.hi()).rejects.toBe(error); // then expect( @@ -295,8 +295,8 @@ describe('DecoratorInjector', () => { expect(tracer.startSpan).toHaveBeenCalledWith('hello', { childOf: null }); expect(tracer.scope().active).toHaveBeenCalled(); expect(tracer.scope().activate).toHaveBeenCalled(); - expect(mockSpan.finish).toHaveBeenCalled(); - expect(mockSpan.setTag).toHaveBeenCalledWith('error', new Error('hello')); + expect(mockSpan.setTag).toHaveBeenCalledWith('error', error); + expect(mockSpan.finish).toHaveBeenCalledTimes(1); startSpanSpy.mockClear(); scopeSpy.mockClear(); diff --git a/src/decorator.injector.ts b/src/decorator.injector.ts index 3173485..473f76e 100644 --- a/src/decorator.injector.ts +++ b/src/decorator.injector.ts @@ -69,7 +69,6 @@ export class DecoratorInjector implements Injector { */ private static recordException(error, span: Span) { span.setTag('error', error); - throw error; } /** @@ -185,6 +184,7 @@ export class DecoratorInjector implements Injector { .apply(this, args) .catch((error) => { DecoratorInjector.recordException(error, span); + throw error; }) .finally(() => span.finish()); } else { @@ -195,6 +195,7 @@ export class DecoratorInjector implements Injector { return result .catch((error) => { DecoratorInjector.recordException(error, span); + throw error; }) .finally(() => span.finish()); } @@ -204,6 +205,7 @@ export class DecoratorInjector implements Injector { } catch (error) { DecoratorInjector.recordException(error, span); span.finish(); + throw error; } } });