diff --git a/plugins/node/opentelemetry-koa-instrumentation/src/koa.ts b/plugins/node/opentelemetry-koa-instrumentation/src/koa.ts index f7be8855f0..175f1e8712 100644 --- a/plugins/node/opentelemetry-koa-instrumentation/src/koa.ts +++ b/plugins/node/opentelemetry-koa-instrumentation/src/koa.ts @@ -131,9 +131,14 @@ export class KoaInstrumentation extends BasePlugin { }); return this._tracer.withSpan(span, async () => { - const result = await middlewareLayer(context, next); - span.end(); - return result; + try { + return await middlewareLayer(context, next); + } catch (err) { + span.recordException(err); + throw err; + } finally { + span.end(); + } }); }; } diff --git a/plugins/node/opentelemetry-koa-instrumentation/test/koa.test.ts b/plugins/node/opentelemetry-koa-instrumentation/test/koa.test.ts index cefcb26a6c..ce541ffa2e 100644 --- a/plugins/node/opentelemetry-koa-instrumentation/test/koa.test.ts +++ b/plugins/node/opentelemetry-koa-instrumentation/test/koa.test.ts @@ -22,6 +22,10 @@ import { InMemorySpanExporter, SimpleSpanProcessor, } from '@opentelemetry/tracing'; +import { + ExceptionAttribute, + ExceptionEventName, +} from '@opentelemetry/semantic-conventions'; import * as assert from 'assert'; import * as koa from 'koa'; import * as http from 'http'; @@ -106,6 +110,10 @@ describe('Koa Instrumentation - Core Tests', () => { ctx.body = `${ctx.method} ${ctx.url} - ${ms}ms`; }; + const failingMiddleware: koa.Middleware = async (_ctx, _next) => { + throw new Error('I failed!'); + }; + describe('Instrumenting core middleware calls', () => { it('should create a child span for middlewares', async () => { const rootSpan = tracer.startSpan('rootSpan'); @@ -193,6 +201,40 @@ describe('Koa Instrumentation - Core Tests', () => { assert.notStrictEqual(exportedRootSpan, undefined); }); }); + + it('should propagate exceptions in the middleware while marking the span with an exception', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + app.use((_ctx, next) => tracer.withSpan(rootSpan, next)); + app.use(failingMiddleware); + const res = await httpRequest.get(`http://localhost:${port}`); + assert.deepStrictEqual(res, 'Internal Server Error'); + + rootSpan.end(); + assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 3); + + const requestHandlerSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('failingMiddleware')); + assert.notStrictEqual(requestHandlerSpan, undefined); + + assert.strictEqual( + requestHandlerSpan?.attributes[AttributeNames.KOA_TYPE], + KoaLayerType.MIDDLEWARE + ); + const exportedRootSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name === 'rootSpan'); + assert.ok(exportedRootSpan); + const exceptionEvent = requestHandlerSpan.events.find( + event => event.name === ExceptionEventName + ); + assert.ok(exceptionEvent, 'There should be an exception event recorded'); + assert.deepStrictEqual(exceptionEvent.name, 'exception'); + assert.deepStrictEqual( + exceptionEvent.attributes![ExceptionAttribute.MESSAGE], + 'I failed!' + ); + }); }); describe('Disabling koa instrumentation', () => {