diff --git a/README.md b/README.md index 29b6585df0..4091bdd090 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ OpenTelemetry can collect tracing data automatically using plugins. Vendors/User - [@opentelemetry/plugin-express][otel-plugin-express] - [@opentelemetry/plugin-dns][otel-plugin-dns] - [@opentelemetry/hapi-instrumentation][otel-contrib-hapi-instrumentation] -- [@opentelemetry/koa-instrumentation][otel-contrib-koa-instrumentation] +- [@opentelemetry/instrumentation-koa][otel-contrib-instrumentation-koa] - [@opentelemetry/instrumentation-graphql][otel-contrib-instrumentation-graphql] ### Web Plugins @@ -135,5 +135,5 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [otel-plugin-express]: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-plugin-express [otel-plugins-node-core-and-contrib]: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/metapackages/plugins-node-core-and-contrib [otel-contrib-hapi-instrumentation]: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-hapi-instrumentation -[otel-contrib-koa-instrumentation]: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-koa-instrumentation +[otel-contrib-instrumentation-koa]: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-koa [otel-contrib-instrumentation-graphql]: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-graphql diff --git a/examples/koa/package.json b/examples/koa/package.json index c72492ae61..eda6cdc073 100644 --- a/examples/koa/package.json +++ b/examples/koa/package.json @@ -35,9 +35,10 @@ "@opentelemetry/api": "^0.15.0", "@opentelemetry/exporter-jaeger": "^0.15.0", "@opentelemetry/exporter-zipkin": "^0.15.0", - "@opentelemetry/koa-instrumentation": "^0.13.0", + "@opentelemetry/instrumentation": "^0.15.0", + "@opentelemetry/instrumentation-http": "^0.15.0", + "@opentelemetry/instrumentation-koa": "^0.13.1", "@opentelemetry/node": "^0.15.0", - "@opentelemetry/plugin-http": "^0.15.0", "@opentelemetry/tracing": "^0.15.0", "axios": "^0.19.0", "koa": "^2.13.0" diff --git a/examples/koa/server.js b/examples/koa/server.js index 7bd3a1b2f1..3f7b6a1b36 100644 --- a/examples/koa/server.js +++ b/examples/koa/server.js @@ -1,7 +1,8 @@ 'use strict'; -// eslint-disable-next-line -const tracer = require('./tracer')('example-koa-server'); +const api = require('@opentelemetry/api'); + +require('./tracer')('example-koa-server'); // Adding Koa router (if desired) const router = require('@koa/router')(); @@ -28,7 +29,7 @@ const posts = ['post 0', 'post 1', 'post 2']; function addPost(ctx) { posts.push(`post ${posts.length}`); - const currentSpan = tracer.getCurrentSpan(); + const currentSpan = api.getSpan(api.context.active()); currentSpan.addEvent('Added post'); currentSpan.setAttribute('Date', new Date()); ctx.body = `Added post: ${posts[posts.length - 1]}`; @@ -47,7 +48,7 @@ async function showNewPost(ctx) { function runTest(ctx) { console.log('runTest'); - const currentSpan = tracer.getCurrentSpan(); + const currentSpan = api.getSpan(api.context.active()); const { traceId } = currentSpan.context(); console.log(`traceid: ${traceId}`); console.log(`Jaeger URL: http://localhost:16686/trace/${traceId}`); diff --git a/examples/koa/tracer.js b/examples/koa/tracer.js index fec566dd21..a451dfbf04 100644 --- a/examples/koa/tracer.js +++ b/examples/koa/tracer.js @@ -1,6 +1,10 @@ 'use strict'; -const opentelemetry = require('@opentelemetry/api'); +const { KoaInstrumentation } = require('@opentelemetry/instrumentation-koa'); +const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); + +const api = require('@opentelemetry/api'); +const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const { NodeTracerProvider } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); @@ -9,19 +13,7 @@ const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); const EXPORTER = process.env.EXPORTER || ''; module.exports = (serviceName) => { - const provider = new NodeTracerProvider({ - plugins: { - koa: { - enabled: true, - path: '@opentelemetry/koa-instrumentation', - enhancedDatabaseReporting: true, - }, - http: { - enabled: true, - path: '@opentelemetry/plugin-http', - }, - }, - }); + const provider = new NodeTracerProvider(); let exporter; if (EXPORTER === 'jaeger') { @@ -31,8 +23,16 @@ module.exports = (serviceName) => { } provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); + registerInstrumentations({ + instrumentations: [ + new KoaInstrumentation(), + new HttpInstrumentation(), + ], + tracerProvider: provider, + }); + // Initialize the OpenTelemetry APIs to use the NodeTracerProvider bindings provider.register(); - return opentelemetry.trace.getTracer('koa-example'); + return api.trace.getTracer('koa-example'); }; diff --git a/package.json b/package.json index 81f6198632..bec3521bf1 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.13.1", "description": "This is a repository for OpenTelemetry JavaScript contributions.", "repository": { - "type" : "git", - "url" : "https://github.com/open-telemetry/opentelemetry-js-contrib.git" + "type": "git", + "url": "https://github.com/open-telemetry/opentelemetry-js-contrib.git" }, "publishConfig": { "access": "public" diff --git a/plugins/node/opentelemetry-koa-instrumentation/.eslintignore b/plugins/node/opentelemetry-instrumentation-koa/.eslintignore similarity index 100% rename from plugins/node/opentelemetry-koa-instrumentation/.eslintignore rename to plugins/node/opentelemetry-instrumentation-koa/.eslintignore diff --git a/plugins/node/opentelemetry-koa-instrumentation/.eslintrc.js b/plugins/node/opentelemetry-instrumentation-koa/.eslintrc.js similarity index 100% rename from plugins/node/opentelemetry-koa-instrumentation/.eslintrc.js rename to plugins/node/opentelemetry-instrumentation-koa/.eslintrc.js diff --git a/plugins/node/opentelemetry-koa-instrumentation/.npmignore b/plugins/node/opentelemetry-instrumentation-koa/.npmignore similarity index 100% rename from plugins/node/opentelemetry-koa-instrumentation/.npmignore rename to plugins/node/opentelemetry-instrumentation-koa/.npmignore diff --git a/plugins/node/opentelemetry-koa-instrumentation/LICENSE b/plugins/node/opentelemetry-instrumentation-koa/LICENSE similarity index 100% rename from plugins/node/opentelemetry-koa-instrumentation/LICENSE rename to plugins/node/opentelemetry-instrumentation-koa/LICENSE diff --git a/plugins/node/opentelemetry-koa-instrumentation/README.md b/plugins/node/opentelemetry-instrumentation-koa/README.md similarity index 60% rename from plugins/node/opentelemetry-koa-instrumentation/README.md rename to plugins/node/opentelemetry-instrumentation-koa/README.md index 64b13fec20..edf30a7309 100644 --- a/plugins/node/opentelemetry-koa-instrumentation/README.md +++ b/plugins/node/opentelemetry-instrumentation-koa/README.md @@ -12,7 +12,7 @@ For automatic instrumentation see the ## Installation ```bash -npm install --save @opentelemetry/koa-instrumentation +npm install --save @opentelemetry/instrumentation-koa ``` ### Supported Versions - Koa `^2.0.0` @@ -21,33 +21,47 @@ npm install --save @opentelemetry/koa-instrumentation OpenTelemetry Koa Instrumentation allows the user to automatically collect trace data and export them to their backend of choice, to give observability to distributed systems. -To load a specific instrumentation (Koa in this case), specify it in the Node Tracer's configuration. +To load all of the [default supported plugins](https://github.com/open-telemetry/opentelemetry-js#plugins), use the below approach. Each plugin is only loaded when the module that it patches is loaded; in other words, there is no computational overhead for listing plugins for unused modules. + ```js const { NodeTracerProvider } = require('@opentelemetry/node'); - -const provider = new NodeTracerProvider({ - plugins: { - koa: { - enabled: true, - // You may use a package name or absolute path to the file. - path: '@opentelemetry/koa-instrumentation', - } - } +const provider = new NodeTracerProvider(); +const { registerInstrumentations } = require('@opentelemetry/instrumentation'); +registerInstrumentations({ + tracerProvider: provider, }); ``` -To load all of the [supported instrumentations](https://github.com/open-telemetry/opentelemetry-js#plugins), use below approach. Each instrumentation is only loaded when the module that it patches is loaded; in other words, there is no computational overhead for listing instrumentations for unused modules. +If instead you would just want to load a specific instrumentation only (**koa** in this case); + ```js const { NodeTracerProvider } = require('@opentelemetry/node'); +const { KoaInstrumentation } = require('@opentelemetry/instrumentation-koa'); +const provider = new NodeTracerProvider(); +const koaInstrumentation = new KoaInstrumentation(); +koaInstrumentation.setTracerProvider(provider); +``` + +You can combine loading default plugins and KoaInstrumentation at the same time: +```js +const { NodeTracerProvider } = require('@opentelemetry/node'); +const { KoaInstrumentation } = require('@opentelemetry/instrumentation-koa'); +const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const provider = new NodeTracerProvider(); +registerInstrumentations({ + instrumentations: [ + new KoaInstrumentation(), + ], + tracerProvider: provider, +}); ``` See [examples/koa](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/examples/koa) for a short example using both Koa and @koa/router ## Koa Packages -This package provides automatic tracing for middleware added using either the core [`Koa`](https://github.com/koajs/koa) package or the [`@koa/router`](https://github.com/koajs/router) package. +This package provides automatic tracing for middleware added using either the core [`Koa`](https://github.com/koajs/koa) package or the [`@koa/router`](https://github.com/koajs/router) package. ## Useful links - For more information on OpenTelemetry, visit: @@ -62,7 +76,7 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [gitter-url]: https://gitter.im/open-telemetry/opentelemetry-node?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge [license-url]: https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/LICENSE [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat -[dependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib/status.svg?path=plugins/node/opentelemetry-koa-instrumentation -[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib?path=plugins/node/opentelemetry-koa-instrumentation -[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib/dev-status.svg?path=plugins/node/opentelemetry-koa-instrumentation -[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib?path=plugins/node/opentelemetry-koa-instrumentation&type=dev +[dependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib/status.svg?path=plugins/node/opentelemetry-instrumentation-koa +[dependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib?path=plugins/node/opentelemetry-instrumentation-koa +[devDependencies-image]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib/dev-status.svg?path=plugins/node/opentelemetry-instrumentation-koa +[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js-contrib?path=plugins/node/opentelemetry-instrumentation-koa&type=dev diff --git a/plugins/node/opentelemetry-koa-instrumentation/package.json b/plugins/node/opentelemetry-instrumentation-koa/package.json similarity index 92% rename from plugins/node/opentelemetry-koa-instrumentation/package.json rename to plugins/node/opentelemetry-instrumentation-koa/package.json index c0364e0299..d15676277e 100644 --- a/plugins/node/opentelemetry-koa-instrumentation/package.json +++ b/plugins/node/opentelemetry-instrumentation-koa/package.json @@ -1,5 +1,5 @@ { - "name": "@opentelemetry/koa-instrumentation", + "name": "@opentelemetry/instrumentation-koa", "version": "0.13.1", "description": "OpenTelemetry Koa automatic instrumentation package.", "main": "build/src/index.js", @@ -51,7 +51,6 @@ "@types/koa__router": "8.0.2", "@types/mocha": "7.0.2", "@types/node": "12.12.47", - "@types/shimmer": "1.0.1", "codecov": "3.7.1", "gts": "3.1.0", "koa": "2.13.0", @@ -67,7 +66,7 @@ "dependencies": { "@opentelemetry/api": "^0.15.0", "@opentelemetry/core": "^0.15.0", - "@opentelemetry/semantic-conventions": "^0.15.0", - "shimmer": "^1.2.1" + "@opentelemetry/instrumentation": "^0.15.0", + "@opentelemetry/semantic-conventions": "^0.15.0" } } diff --git a/plugins/node/opentelemetry-koa-instrumentation/src/index.ts b/plugins/node/opentelemetry-instrumentation-koa/src/index.ts similarity index 100% rename from plugins/node/opentelemetry-koa-instrumentation/src/index.ts rename to plugins/node/opentelemetry-instrumentation-koa/src/index.ts diff --git a/plugins/node/opentelemetry-koa-instrumentation/src/koa.ts b/plugins/node/opentelemetry-instrumentation-koa/src/koa.ts similarity index 78% rename from plugins/node/opentelemetry-koa-instrumentation/src/koa.ts rename to plugins/node/opentelemetry-instrumentation-koa/src/koa.ts index a90c3dfa84..47c9038e09 100644 --- a/plugins/node/opentelemetry-koa-instrumentation/src/koa.ts +++ b/plugins/node/opentelemetry-instrumentation-koa/src/koa.ts @@ -15,9 +15,14 @@ */ import * as api from '@opentelemetry/api'; -import { BasePlugin } from '@opentelemetry/core'; +import { + isWrapped, + InstrumentationBase, + InstrumentationConfig, + InstrumentationNodeModuleDefinition, +} from '@opentelemetry/instrumentation'; + import type * as koa from 'koa'; -import * as shimmer from 'shimmer'; import { KoaMiddleware, KoaContext, @@ -28,34 +33,35 @@ import { VERSION } from './version'; import { getMiddlewareMetadata } from './utils'; /** Koa instrumentation for OpenTelemetry */ -export class KoaInstrumentation extends BasePlugin { +export class KoaInstrumentation extends InstrumentationBase { static readonly component = KoaComponentName; - readonly supportedVersions = ['^2.0.0']; - - constructor(readonly moduleName: string) { - super('@opentelemetry/koa-instrumentation', VERSION); - } - - /** - * Patches Koa operations by wrapping the Koa.use function - */ - protected patch(): typeof koa { - this._logger.debug('Patching Koa'); - if (this._moduleExports == null) { - return this._moduleExports; - } - this._logger.debug('Patching Koa.use'); - shimmer.wrap(this._moduleExports.prototype, 'use', this._getKoaUsePatch); - - return this._moduleExports; + constructor(config?: InstrumentationConfig) { + super('@opentelemetry/instrumentation-koa', VERSION, config); } - - /** - * Unpatches all Koa operations - */ - protected unpatch(): void { - this._logger.debug('Unpatching Koa'); - shimmer.unwrap(this._moduleExports.prototype, 'use'); + protected init() { + return new InstrumentationNodeModuleDefinition( + 'koa', + ['^2.0.0'], + moduleExports => { + if (moduleExports == null) { + return moduleExports; + } + if (isWrapped(moduleExports.prototype.use)) { + this._unwrap(moduleExports.prototype, 'use'); + } + this._wrap( + moduleExports.prototype, + 'use', + this._getKoaUsePatch.bind(this) + ); + return moduleExports; + }, + moduleExports => { + if (isWrapped(moduleExports.prototype.use)) { + this._unwrap(moduleExports.prototype, 'use'); + } + } + ); } /** @@ -64,6 +70,7 @@ export class KoaInstrumentation extends BasePlugin { * @param {KoaMiddleware} middleware - the original middleware function */ private _getKoaUsePatch(original: (middleware: KoaMiddleware) => koa) { + const plugin = this; return function use(this: koa, middlewareFunction: KoaMiddleware) { let patchedFunction: KoaMiddleware; if (middlewareFunction.router) { @@ -127,7 +134,7 @@ export class KoaInstrumentation extends BasePlugin { isRouter, layerPath ); - const span = this._tracer.startSpan(metadata.name, { + const span = this.tracer.startSpan(metadata.name, { attributes: metadata.attributes, }); @@ -147,5 +154,3 @@ export class KoaInstrumentation extends BasePlugin { }; } } - -export const plugin = new KoaInstrumentation(KoaComponentName); diff --git a/plugins/node/opentelemetry-koa-instrumentation/src/types.ts b/plugins/node/opentelemetry-instrumentation-koa/src/types.ts similarity index 100% rename from plugins/node/opentelemetry-koa-instrumentation/src/types.ts rename to plugins/node/opentelemetry-instrumentation-koa/src/types.ts diff --git a/plugins/node/opentelemetry-koa-instrumentation/src/utils.ts b/plugins/node/opentelemetry-instrumentation-koa/src/utils.ts similarity index 100% rename from plugins/node/opentelemetry-koa-instrumentation/src/utils.ts rename to plugins/node/opentelemetry-instrumentation-koa/src/utils.ts diff --git a/plugins/node/opentelemetry-koa-instrumentation/src/version.ts b/plugins/node/opentelemetry-instrumentation-koa/src/version.ts similarity index 100% rename from plugins/node/opentelemetry-koa-instrumentation/src/version.ts rename to plugins/node/opentelemetry-instrumentation-koa/src/version.ts diff --git a/plugins/node/opentelemetry-koa-instrumentation/test/koa.test.ts b/plugins/node/opentelemetry-instrumentation-koa/test/koa.test.ts similarity index 64% rename from plugins/node/opentelemetry-koa-instrumentation/test/koa.test.ts rename to plugins/node/opentelemetry-instrumentation-koa/test/koa.test.ts index 30eadc3c99..ba7db72aff 100644 --- a/plugins/node/opentelemetry-koa-instrumentation/test/koa.test.ts +++ b/plugins/node/opentelemetry-instrumentation-koa/test/koa.test.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { context, setSpan, NoopLogger } from '@opentelemetry/api'; +import * as KoaRouter from '@koa/router'; +import { context, setSpan } from '@opentelemetry/api'; import { NodeTracerProvider } from '@opentelemetry/node'; import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; import { @@ -24,12 +25,16 @@ import { import { ExceptionAttribute, ExceptionEventName, + HttpAttribute, } from '@opentelemetry/semantic-conventions'; + +import { KoaInstrumentation } from '../src'; +const plugin = new KoaInstrumentation(); + import * as assert from 'assert'; import * as koa from 'koa'; import * as http from 'http'; import { AddressInfo } from 'net'; -import { plugin } from '../src'; import { AttributeNames, KoaLayerType } from '../src/types'; const httpRequest = { @@ -51,12 +56,12 @@ const httpRequest = { }, }; -describe('Koa Instrumentation - Core Tests', () => { - const logger = new NoopLogger(); +describe('Koa Instrumentation', () => { const provider = new NodeTracerProvider(); const memoryExporter = new InMemorySpanExporter(); const spanProcessor = new SimpleSpanProcessor(memoryExporter); provider.addSpanProcessor(spanProcessor); + plugin.setTracerProvider(provider); const tracer = provider.getTracer('default'); let contextManager: AsyncHooksContextManager; let app: koa; @@ -64,7 +69,11 @@ describe('Koa Instrumentation - Core Tests', () => { let port: number; before(() => { - plugin.enable(koa, provider, logger); + plugin.enable(); + }); + + after(() => { + plugin.disable(); }); beforeEach(async () => { @@ -113,6 +122,130 @@ describe('Koa Instrumentation - Core Tests', () => { throw new Error('I failed!'); }; + describe('Instrumenting @koa/router calls', () => { + it('should create a child span for middlewares', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + app.use((ctx, next) => + context.with(setSpan(context.active(), rootSpan), next) + ); + + const router = new KoaRouter(); + router.get('/post/:id', ctx => { + ctx.body = `Post id: ${ctx.params.id}`; + }); + + app.use(router.routes()); + + await context.with(setSpan(context.active(), rootSpan), async () => { + await httpRequest.get(`http://localhost:${port}/post/0`); + rootSpan.end(); + + assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 2); + const requestHandlerSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('router - /post/:id')); + assert.notStrictEqual(requestHandlerSpan, undefined); + + assert.strictEqual( + requestHandlerSpan?.attributes[AttributeNames.KOA_TYPE], + KoaLayerType.ROUTER + ); + + assert.strictEqual( + requestHandlerSpan?.attributes[HttpAttribute.HTTP_ROUTE], + '/post/:id' + ); + + const exportedRootSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name === 'rootSpan'); + assert.notStrictEqual(exportedRootSpan, undefined); + }); + }); + + it('should correctly instrument nested routers', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + app.use((ctx, next) => + context.with(setSpan(context.active(), rootSpan), next) + ); + + const router = new KoaRouter(); + const nestedRouter = new KoaRouter(); + nestedRouter.get('/post/:id', ctx => { + ctx.body = `Post id: ${ctx.params.id}`; + }); + + router.use('/:first', nestedRouter.routes()); + app.use(router.routes()); + + await context.with(setSpan(context.active(), rootSpan), async () => { + await httpRequest.get(`http://localhost:${port}/test/post/0`); + rootSpan.end(); + + assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 2); + const requestHandlerSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('router - /:first/post/:id')); + assert.notStrictEqual(requestHandlerSpan, undefined); + + assert.strictEqual( + requestHandlerSpan?.attributes[AttributeNames.KOA_TYPE], + KoaLayerType.ROUTER + ); + + assert.strictEqual( + requestHandlerSpan?.attributes[HttpAttribute.HTTP_ROUTE], + '/:first/post/:id' + ); + + const exportedRootSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name === 'rootSpan'); + assert.notStrictEqual(exportedRootSpan, undefined); + }); + }); + + it('should correctly instrument prefixed routers', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + app.use((ctx, next) => + context.with(setSpan(context.active(), rootSpan), next) + ); + + const router = new KoaRouter(); + router.get('/post/:id', ctx => { + ctx.body = `Post id: ${ctx.params.id}`; + }); + router.prefix('/:first'); + app.use(router.routes()); + + await context.with(setSpan(context.active(), rootSpan), async () => { + await httpRequest.get(`http://localhost:${port}/test/post/0`); + rootSpan.end(); + + assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 2); + const requestHandlerSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('router - /:first/post/:id')); + assert.notStrictEqual(requestHandlerSpan, undefined); + + assert.strictEqual( + requestHandlerSpan?.attributes[AttributeNames.KOA_TYPE], + KoaLayerType.ROUTER + ); + + assert.strictEqual( + requestHandlerSpan?.attributes[HttpAttribute.HTTP_ROUTE], + '/:first/post/:id' + ); + + const exportedRootSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name === 'rootSpan'); + assert.notStrictEqual(exportedRootSpan, undefined); + }); + }); + }); + describe('Instrumenting core middleware calls', () => { it('should create a child span for middlewares', async () => { const rootSpan = tracer.startSpan('rootSpan'); @@ -126,7 +259,7 @@ describe('Koa Instrumentation - Core Tests', () => { await context.with(setSpan(context.active(), rootSpan), async () => { await httpRequest.get(`http://localhost:${port}`); rootSpan.end(); - assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 8); + assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 5); assert.notStrictEqual( memoryExporter @@ -187,7 +320,7 @@ describe('Koa Instrumentation - Core Tests', () => { await context.with(setSpan(context.active(), rootSpan), async () => { await httpRequest.get(`http://localhost:${port}`); rootSpan.end(); - assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 3); + assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 2); const requestHandlerSpan = memoryExporter .getFinishedSpans() @@ -215,7 +348,7 @@ describe('Koa Instrumentation - Core Tests', () => { assert.deepStrictEqual(res, 'Internal Server Error'); rootSpan.end(); - assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 3); + assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 2); const requestHandlerSpan = memoryExporter .getFinishedSpans() diff --git a/plugins/node/opentelemetry-koa-instrumentation/tsconfig.json b/plugins/node/opentelemetry-instrumentation-koa/tsconfig.json similarity index 100% rename from plugins/node/opentelemetry-koa-instrumentation/tsconfig.json rename to plugins/node/opentelemetry-instrumentation-koa/tsconfig.json diff --git a/plugins/node/opentelemetry-koa-instrumentation/test/koa-router.test.ts b/plugins/node/opentelemetry-koa-instrumentation/test/koa-router.test.ts deleted file mode 100644 index d748522119..0000000000 --- a/plugins/node/opentelemetry-koa-instrumentation/test/koa-router.test.ts +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { context, setSpan, NoopLogger } from '@opentelemetry/api'; -import { NodeTracerProvider } from '@opentelemetry/node'; -import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; -import { - InMemorySpanExporter, - SimpleSpanProcessor, -} from '@opentelemetry/tracing'; -import * as assert from 'assert'; -import * as koa from 'koa'; -import * as KoaRouter from '@koa/router'; -import * as http from 'http'; -import { AddressInfo } from 'net'; -import { plugin } from '../src'; -import { AttributeNames, KoaLayerType } from '../src/types'; -import { HttpAttribute } from '@opentelemetry/semantic-conventions'; - -const httpRequest = { - get: (options: http.ClientRequestArgs | string) => { - return new Promise((resolve, reject) => { - return http.get(options, resp => { - let data = ''; - resp.on('data', chunk => { - data += chunk; - }); - resp.on('end', () => { - resolve(data); - }); - resp.on('error', err => { - reject(err); - }); - }); - }); - }, -}; - -describe('Koa Instrumentation - Router Tests', () => { - const logger = new NoopLogger(); - const provider = new NodeTracerProvider(); - const memoryExporter = new InMemorySpanExporter(); - const spanProcessor = new SimpleSpanProcessor(memoryExporter); - provider.addSpanProcessor(spanProcessor); - const tracer = provider.getTracer('default'); - let contextManager: AsyncHooksContextManager; - let app: koa; - let server: http.Server; - let port: number; - - before(() => { - plugin.enable(koa, provider, logger); - }); - - beforeEach(async () => { - contextManager = new AsyncHooksContextManager(); - context.setGlobalContextManager(contextManager.enable()); - - app = new koa(); - server = http.createServer(app.callback()); - await new Promise(resolve => server.listen(0, resolve)); - port = (server.address() as AddressInfo).port; - assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); - }); - - afterEach(() => { - memoryExporter.reset(); - context.disable(); - server.close(); - }); - - describe('Instrumenting @koa/router calls', () => { - it('should create a child span for middlewares', async () => { - const rootSpan = tracer.startSpan('rootSpan'); - app.use((ctx, next) => - context.with(setSpan(context.active(), rootSpan), next) - ); - - const router = new KoaRouter(); - router.get('/post/:id', ctx => { - ctx.body = `Post id: ${ctx.params.id}`; - }); - - app.use(router.routes()); - - await context.with(setSpan(context.active(), rootSpan), async () => { - await httpRequest.get(`http://localhost:${port}/post/0`); - rootSpan.end(); - - assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 2); - const requestHandlerSpan = memoryExporter - .getFinishedSpans() - .find(span => span.name.includes('router - /post/:id')); - assert.notStrictEqual(requestHandlerSpan, undefined); - - assert.strictEqual( - requestHandlerSpan?.attributes[AttributeNames.KOA_TYPE], - KoaLayerType.ROUTER - ); - - assert.strictEqual( - requestHandlerSpan?.attributes[HttpAttribute.HTTP_ROUTE], - '/post/:id' - ); - - const exportedRootSpan = memoryExporter - .getFinishedSpans() - .find(span => span.name === 'rootSpan'); - assert.notStrictEqual(exportedRootSpan, undefined); - }); - }); - - it('should correctly instrument nested routers', async () => { - const rootSpan = tracer.startSpan('rootSpan'); - app.use((ctx, next) => - context.with(setSpan(context.active(), rootSpan), next) - ); - - const router = new KoaRouter(); - const nestedRouter = new KoaRouter(); - nestedRouter.get('/post/:id', ctx => { - ctx.body = `Post id: ${ctx.params.id}`; - }); - - router.use('/:first', nestedRouter.routes()); - app.use(router.routes()); - - await context.with(setSpan(context.active(), rootSpan), async () => { - await httpRequest.get(`http://localhost:${port}/test/post/0`); - rootSpan.end(); - - assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 2); - const requestHandlerSpan = memoryExporter - .getFinishedSpans() - .find(span => span.name.includes('router - /:first/post/:id')); - assert.notStrictEqual(requestHandlerSpan, undefined); - - assert.strictEqual( - requestHandlerSpan?.attributes[AttributeNames.KOA_TYPE], - KoaLayerType.ROUTER - ); - - assert.strictEqual( - requestHandlerSpan?.attributes[HttpAttribute.HTTP_ROUTE], - '/:first/post/:id' - ); - - const exportedRootSpan = memoryExporter - .getFinishedSpans() - .find(span => span.name === 'rootSpan'); - assert.notStrictEqual(exportedRootSpan, undefined); - }); - }); - - it('should correctly instrument prefixed routers', async () => { - const rootSpan = tracer.startSpan('rootSpan'); - app.use((ctx, next) => - context.with(setSpan(context.active(), rootSpan), next) - ); - - const router = new KoaRouter(); - router.get('/post/:id', ctx => { - ctx.body = `Post id: ${ctx.params.id}`; - }); - router.prefix('/:first'); - app.use(router.routes()); - - await context.with(setSpan(context.active(), rootSpan), async () => { - await httpRequest.get(`http://localhost:${port}/test/post/0`); - rootSpan.end(); - - assert.deepStrictEqual(memoryExporter.getFinishedSpans().length, 2); - const requestHandlerSpan = memoryExporter - .getFinishedSpans() - .find(span => span.name.includes('router - /:first/post/:id')); - assert.notStrictEqual(requestHandlerSpan, undefined); - - assert.strictEqual( - requestHandlerSpan?.attributes[AttributeNames.KOA_TYPE], - KoaLayerType.ROUTER - ); - - assert.strictEqual( - requestHandlerSpan?.attributes[HttpAttribute.HTTP_ROUTE], - '/:first/post/:id' - ); - - const exportedRootSpan = memoryExporter - .getFinishedSpans() - .find(span => span.name === 'rootSpan'); - assert.notStrictEqual(exportedRootSpan, undefined); - }); - }); - }); -});