diff --git a/plugins/node/opentelemetry-instrumentation-hapi/package.json b/plugins/node/opentelemetry-instrumentation-hapi/package.json index 786653dbdf..c417bb4072 100644 --- a/plugins/node/opentelemetry-instrumentation-hapi/package.json +++ b/plugins/node/opentelemetry-instrumentation-hapi/package.json @@ -61,6 +61,7 @@ "typescript": "4.3.5" }, "dependencies": { + "@opentelemetry/core": "^0.23.0", "@opentelemetry/instrumentation": "^0.23.0", "@opentelemetry/semantic-conventions": "^0.23.0", "@types/hapi__hapi": "20.0.8" diff --git a/plugins/node/opentelemetry-instrumentation-hapi/src/instrumentation.ts b/plugins/node/opentelemetry-instrumentation-hapi/src/instrumentation.ts index 93c211c6dc..c9b11755a9 100644 --- a/plugins/node/opentelemetry-instrumentation-hapi/src/instrumentation.ts +++ b/plugins/node/opentelemetry-instrumentation-hapi/src/instrumentation.ts @@ -15,6 +15,7 @@ */ import * as api from '@opentelemetry/api'; +import { getRPCMetadata, RPCType } from '@opentelemetry/core'; import { InstrumentationBase, InstrumentationConfig, @@ -43,6 +44,7 @@ import { getExtMetadata, isDirectExtInput, isPatchableExtMethod, + getRootSpanMetadata, } from './utils'; /** Hapi instrumentation for OpenTelemetry */ @@ -374,6 +376,12 @@ export class HapiInstrumentation extends InstrumentationBase { if (api.trace.getSpan(api.context.active()) === undefined) { return await oldHandler(request, h, err); } + const rpcMetadata = getRPCMetadata(api.context.active()); + if (rpcMetadata?.type === RPCType.HTTP) { + const rootSpanMetadata = getRootSpanMetadata(route); + rpcMetadata.span.updateName(rootSpanMetadata.name); + rpcMetadata.span.setAttributes(rootSpanMetadata.attributes); + } const metadata = getRouteMetadata(route, pluginName); const span = instrumentation.tracer.startSpan(metadata.name, { attributes: metadata.attributes, diff --git a/plugins/node/opentelemetry-instrumentation-hapi/src/utils.ts b/plugins/node/opentelemetry-instrumentation-hapi/src/utils.ts index 20f9cab058..556f73070e 100644 --- a/plugins/node/opentelemetry-instrumentation-hapi/src/utils.ts +++ b/plugins/node/opentelemetry-instrumentation-hapi/src/utils.ts @@ -94,6 +94,20 @@ export const getRouteMetadata = ( }; }; +export const getRootSpanMetadata = ( + route: Hapi.ServerRoute +): { + attributes: SpanAttributes; + name: string; +} => { + return { + attributes: { + [SemanticAttributes.HTTP_ROUTE]: route.path, + }, + name: `${route.method} ${route.path}`, + }; +}; + export const getExtMetadata = ( extPoint: Hapi.ServerRequestExtType, pluginName?: string diff --git a/plugins/node/opentelemetry-instrumentation-hapi/test/hapi.test.ts b/plugins/node/opentelemetry-instrumentation-hapi/test/hapi.test.ts index 72f91a0c5d..23f21e8f34 100644 --- a/plugins/node/opentelemetry-instrumentation-hapi/test/hapi.test.ts +++ b/plugins/node/opentelemetry-instrumentation-hapi/test/hapi.test.ts @@ -15,6 +15,8 @@ */ import { context, trace } from '@opentelemetry/api'; +import { RPCType, setRPCMetadata } from '@opentelemetry/core'; +import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; import { NodeTracerProvider } from '@opentelemetry/node'; import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; import { @@ -325,6 +327,43 @@ describe('Hapi Instrumentation - Core Tests', () => { assert.strictEqual(res.statusCode, 200); assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 0); }); + + it('should rename root span with route information', async () => { + const rootSpan = tracer.startSpan('rootSpan', {}); + server.route({ + method: 'GET', + path: '/users/{userId}', + handler: (request, h) => { + return `Hello ${request.params.userId}`; + }, + }); + + await server.start(); + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + const rpcMetadata = { type: RPCType.HTTP, span: rootSpan }; + await context.with( + setRPCMetadata(trace.setSpan(context.active(), rootSpan), rpcMetadata), + async () => { + const res = await server.inject({ + method: 'GET', + url: '/users/1', + }); + assert.strictEqual(res.statusCode, 200); + + rootSpan.end(); + assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 2); + + const exportedRootSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name === 'GET /users/{userId}'); + assert.notStrictEqual(exportedRootSpan, undefined); + assert.strictEqual( + exportedRootSpan?.attributes[SemanticAttributes.HTTP_ROUTE], + '/users/{userId}' + ); + } + ); + }); }); describe('Disabling Hapi instrumentation', () => {