diff --git a/plugins/node/opentelemetry-instrumentation-express/src/instrumentation.ts b/plugins/node/opentelemetry-instrumentation-express/src/instrumentation.ts index 155b63f494..ab23a8d7b7 100644 --- a/plugins/node/opentelemetry-instrumentation-express/src/instrumentation.ts +++ b/plugins/node/opentelemetry-instrumentation-express/src/instrumentation.ts @@ -61,41 +61,63 @@ export class ExpressInstrumentation extends InstrumentationBase=4.0.0 <5'], moduleExports => { - const routerProto = moduleExports.Router as unknown as express.Router; - // patch express.Router.route - if (isWrapped(routerProto.route)) { - this._unwrap(routerProto, 'route'); - } - this._wrap(routerProto, 'route', this._getRoutePatch()); - // patch express.Router.use - if (isWrapped(routerProto.use)) { - this._unwrap(routerProto, 'use'); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this._wrap(routerProto, 'use', this._getRouterUsePatch() as any); - // patch express.Application.use - if (isWrapped(moduleExports.application.use)) { - this._unwrap(moduleExports.application, 'use'); - } - this._wrap( - moduleExports.application, - 'use', - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this._getAppUsePatch() as any - ); - return moduleExports; + this._setup(moduleExports, false); + }, + moduleExports => { + this._tearDown(moduleExports, false); + } + ), + new InstrumentationNodeModuleDefinition( + 'express', + ['>=5 <6'], + moduleExports => { + this._setup(moduleExports, true); }, moduleExports => { - if (moduleExports === undefined) return; - const routerProto = moduleExports.Router as unknown as express.Router; - this._unwrap(routerProto, 'route'); - this._unwrap(routerProto, 'use'); - this._unwrap(moduleExports.application, 'use'); + this._tearDown(moduleExports, true); } ), ]; } + private _setup(moduleExports: any, isExpressV5: boolean) { + const routerProto = isExpressV5 + ? moduleExports.Router.prototype + : moduleExports.Router; + // patch express.Router.route + if (isWrapped(routerProto.route)) { + this._unwrap(routerProto, 'route'); + } + this._wrap(routerProto, 'route', this._getRoutePatch()); + // patch express.Router.use + if (isWrapped(routerProto.use)) { + this._unwrap(routerProto, 'use'); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this._wrap(routerProto, 'use', this._getRouterUsePatch() as any); + // patch express.Application.use + if (isWrapped(moduleExports.application.use)) { + this._unwrap(moduleExports.application, 'use'); + } + this._wrap( + moduleExports.application, + 'use', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this._getAppUsePatch(isExpressV5) as any + ); + return moduleExports; + } + + private _tearDown(moduleExports: any, isExpressV5: boolean) { + if (moduleExports === undefined) return; + const routerProto = isExpressV5 + ? moduleExports.Router.prototype + : moduleExports.Router; + this._unwrap(routerProto, 'route'); + this._unwrap(routerProto, 'use'); + this._unwrap(moduleExports.application, 'use'); + } + /** * Get the patch for Router.route function */ @@ -135,16 +157,22 @@ export class ExpressInstrumentation extends InstrumentationBase ) { + // if we access app.router in express 4.x we trigger an assertion error + // This property existed in v3, was removed in v4 and then re-added in v5 + const router = isExpressV5 ? this.router : this._router; const route = original.apply(this, args); - const layer = this._router.stack[this._router.stack.length - 1]; - instrumentation._applyPatch(layer, getLayerPath(args)); + if (router) { + const layer = router.stack[router.stack.length - 1]; + instrumentation._applyPatch(layer, getLayerPath(args)); + } return route; }; }; diff --git a/plugins/node/opentelemetry-instrumentation-express/src/internal-types.ts b/plugins/node/opentelemetry-instrumentation-express/src/internal-types.ts index ebdc260bc3..bf3ee2d4a9 100644 --- a/plugins/node/opentelemetry-instrumentation-express/src/internal-types.ts +++ b/plugins/node/opentelemetry-instrumentation-express/src/internal-types.ts @@ -15,7 +15,6 @@ */ import type { Request } from 'express'; -import { Attributes } from '@opentelemetry/api'; /** * This symbol is used to mark express layer as being already instrumented @@ -48,11 +47,6 @@ export type PathParams = string | RegExp | Array; // https://github.com/expressjs/express/blob/main/lib/router/index.js#L53 export type ExpressRouter = { - params: { [key: string]: string }; - _params: string[]; - caseSensitive: boolean; - mergeParams: boolean; - strict: boolean; stack: ExpressLayer[]; }; @@ -61,13 +55,6 @@ export type ExpressLayer = { handle: Function & Record; [kLayerPatched]?: boolean; name: string; - params: { [key: string]: string }; path: string; - regexp: RegExp; route?: ExpressLayer; }; - -export type LayerMetadata = { - attributes: Attributes; - name: string; -}; diff --git a/plugins/node/opentelemetry-instrumentation-express/src/utils.ts b/plugins/node/opentelemetry-instrumentation-express/src/utils.ts index fdc8f3728c..28c9992d70 100644 --- a/plugins/node/opentelemetry-instrumentation-express/src/utils.ts +++ b/plugins/node/opentelemetry-instrumentation-express/src/utils.ts @@ -91,7 +91,7 @@ export const getLayerMetadata = ( }, name: `router - ${extractedRouterPath}`, }; - } else if (layer.name === 'bound dispatch') { + } else if (layer.name === 'bound dispatch' || layer.name === 'handle') { return { attributes: { [AttributeNames.EXPRESS_NAME]: diff --git a/plugins/node/opentelemetry-instrumentation-express/test/custom-config.test.ts b/plugins/node/opentelemetry-instrumentation-express/test/v4/custom-config.test.ts similarity index 96% rename from plugins/node/opentelemetry-instrumentation-express/test/custom-config.test.ts rename to plugins/node/opentelemetry-instrumentation-express/test/v4/custom-config.test.ts index 50d644086a..9ac55a9c79 100644 --- a/plugins/node/opentelemetry-instrumentation-express/test/custom-config.test.ts +++ b/plugins/node/opentelemetry-instrumentation-express/test/v4/custom-config.test.ts @@ -24,9 +24,12 @@ import { import { SEMATTRS_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; import * as assert from 'assert'; import { RPCMetadata, RPCType, setRPCMetadata } from '@opentelemetry/core'; -import { ExpressLayerType } from '../src/enums/ExpressLayerType'; -import { AttributeNames } from '../src/enums/AttributeNames'; -import { ExpressInstrumentation, ExpressInstrumentationConfig } from '../src'; +import { ExpressLayerType } from '../../src/enums/ExpressLayerType'; +import { AttributeNames } from '../../src/enums/AttributeNames'; +import { + ExpressInstrumentation, + ExpressInstrumentationConfig, +} from '../../src'; import { createServer, httpRequest } from './utils'; const instrumentation = new ExpressInstrumentation({ diff --git a/plugins/node/opentelemetry-instrumentation-express/test/express.test.ts b/plugins/node/opentelemetry-instrumentation-express/test/v4/express.test.ts similarity index 99% rename from plugins/node/opentelemetry-instrumentation-express/test/express.test.ts rename to plugins/node/opentelemetry-instrumentation-express/test/v4/express.test.ts index 5991688ffd..5d0e92a4b1 100644 --- a/plugins/node/opentelemetry-instrumentation-express/test/express.test.ts +++ b/plugins/node/opentelemetry-instrumentation-express/test/v4/express.test.ts @@ -22,8 +22,8 @@ import { SimpleSpanProcessor, } from '@opentelemetry/sdk-trace-base'; import * as assert from 'assert'; -import { AttributeNames } from '../src/enums/AttributeNames'; -import { ExpressInstrumentation } from '../src'; +import { AttributeNames } from '../../src/enums/AttributeNames'; +import { ExpressInstrumentation } from '../../src'; import { createServer, httpRequest, serverWithMiddleware } from './utils'; import { SEMATTRS_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; import * as testUtils from '@opentelemetry/contrib-test-utils'; diff --git a/plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express-nested-router.mjs b/plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express-nested-router.mjs similarity index 96% rename from plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express-nested-router.mjs rename to plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express-nested-router.mjs index 98ba0bf236..e229815d9c 100644 --- a/plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express-nested-router.mjs +++ b/plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express-nested-router.mjs @@ -18,7 +18,7 @@ import { promisify } from 'util'; import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { ExpressInstrumentation } from '../../build/src/index.js'; +import { ExpressInstrumentation } from '../../../build/src/index.js'; const sdk = createTestNodeSdk({ serviceName: 'use-express-nested', diff --git a/plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express-regex.mjs b/plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express-regex.mjs similarity index 97% rename from plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express-regex.mjs rename to plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express-regex.mjs index d674342dd6..5ef7fdede9 100644 --- a/plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express-regex.mjs +++ b/plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express-regex.mjs @@ -21,7 +21,7 @@ import { promisify } from 'util'; import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { ExpressInstrumentation } from '../../build/src/index.js'; +import { ExpressInstrumentation } from '../../../build/src/index.js'; const sdk = createTestNodeSdk({ serviceName: 'use-express-regex', diff --git a/plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express-router.mjs b/plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express-router.mjs similarity index 96% rename from plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express-router.mjs rename to plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express-router.mjs index 5846da88ae..c97a2ba3a7 100644 --- a/plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express-router.mjs +++ b/plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express-router.mjs @@ -18,7 +18,7 @@ import { promisify } from 'util'; import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { ExpressInstrumentation } from '../../build/src/index.js'; +import { ExpressInstrumentation } from '../../../build/src/index.js'; const sdk = createTestNodeSdk({ serviceName: 'use-express-nested', diff --git a/plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express.mjs b/plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express.mjs similarity index 92% rename from plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express.mjs rename to plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express.mjs index 6dd75360f2..b256f79c97 100644 --- a/plugins/node/opentelemetry-instrumentation-express/test/fixtures/use-express.mjs +++ b/plugins/node/opentelemetry-instrumentation-express/test/v4/fixtures/use-express.mjs @@ -21,15 +21,12 @@ import { promisify } from 'util'; import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { ExpressInstrumentation } from '../../build/src/index.js'; +import { ExpressInstrumentation } from '../../../build/src/index.js'; const sdk = createTestNodeSdk({ serviceName: 'use-express', - instrumentations: [ - new HttpInstrumentation(), - new ExpressInstrumentation() - ] -}) + instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()], +}); sdk.start(); import express from 'express'; diff --git a/plugins/node/opentelemetry-instrumentation-express/test/hooks.test.ts b/plugins/node/opentelemetry-instrumentation-express/test/v4/hooks.test.ts similarity index 97% rename from plugins/node/opentelemetry-instrumentation-express/test/hooks.test.ts rename to plugins/node/opentelemetry-instrumentation-express/test/v4/hooks.test.ts index 78cfdd3d4a..ba16d9a636 100644 --- a/plugins/node/opentelemetry-instrumentation-express/test/hooks.test.ts +++ b/plugins/node/opentelemetry-instrumentation-express/test/v4/hooks.test.ts @@ -24,9 +24,9 @@ import { import * as assert from 'assert'; import type * as http from 'http'; import * as sinon from 'sinon'; -import { ExpressInstrumentation } from '../src'; -import { ExpressRequestInfo, SpanNameHook } from '../src/types'; -import { ExpressLayerType } from '../src/enums/ExpressLayerType'; +import { ExpressInstrumentation } from '../../src'; +import { ExpressRequestInfo, SpanNameHook } from '../../src/types'; +import { ExpressLayerType } from '../../src/enums/ExpressLayerType'; import { SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions'; const instrumentation = new ExpressInstrumentation(); diff --git a/plugins/node/opentelemetry-instrumentation-express/test/ignore-all.test.ts b/plugins/node/opentelemetry-instrumentation-express/test/v4/ignore-all.test.ts similarity index 97% rename from plugins/node/opentelemetry-instrumentation-express/test/ignore-all.test.ts rename to plugins/node/opentelemetry-instrumentation-express/test/v4/ignore-all.test.ts index 0fdebeabfb..d180fa9c70 100644 --- a/plugins/node/opentelemetry-instrumentation-express/test/ignore-all.test.ts +++ b/plugins/node/opentelemetry-instrumentation-express/test/v4/ignore-all.test.ts @@ -23,8 +23,8 @@ import { } from '@opentelemetry/sdk-trace-base'; import * as assert from 'assert'; import { RPCMetadata, RPCType, setRPCMetadata } from '@opentelemetry/core'; -import { AttributeNames } from '../src/enums/AttributeNames'; -import { ExpressInstrumentation, ExpressLayerType } from '../src'; +import { AttributeNames } from '../../src/enums/AttributeNames'; +import { ExpressInstrumentation, ExpressLayerType } from '../../src'; import { createServer, httpRequest } from './utils'; const instrumentation = new ExpressInstrumentation({ diff --git a/plugins/node/opentelemetry-instrumentation-express/test/utils.ts b/plugins/node/opentelemetry-instrumentation-express/test/v4/utils.ts similarity index 100% rename from plugins/node/opentelemetry-instrumentation-express/test/utils.ts rename to plugins/node/opentelemetry-instrumentation-express/test/v4/utils.ts diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/custom-config.test.ts b/plugins/node/opentelemetry-instrumentation-express/test/v5/custom-config.test.ts new file mode 100644 index 0000000000..b462435cce --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/custom-config.test.ts @@ -0,0 +1,221 @@ +/* + * 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, trace } from '@opentelemetry/api'; +import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; +import { + InMemorySpanExporter, + SimpleSpanProcessor, +} from '@opentelemetry/sdk-trace-base'; +import { SEMATTRS_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import * as assert from 'assert'; +import { RPCMetadata, RPCType, setRPCMetadata } from '@opentelemetry/core'; +import { ExpressLayerType } from '../../src/enums/ExpressLayerType'; +import { AttributeNames } from '../../src/enums/AttributeNames'; +import { + ExpressInstrumentation, + ExpressInstrumentationConfig, +} from '../../src'; +import { createServer, httpRequest } from './utils'; + +const nodeVersionNotSupported = process.version.startsWith('v14'); +if (nodeVersionNotSupported) { + console.log('Skipping express v5 tests for node version 14'); + process.exit(0); +} + +const instrumentation = new ExpressInstrumentation({ + ignoreLayersType: [ExpressLayerType.MIDDLEWARE], +}); +instrumentation.enable(); +instrumentation.disable(); + +import * as express from 'express'; +import * as http from 'http'; + +describe('ExpressInstrumentation', () => { + const provider = new NodeTracerProvider(); + const memoryExporter = new InMemorySpanExporter(); + const spanProcessor = new SimpleSpanProcessor(memoryExporter); + provider.addSpanProcessor(spanProcessor); + const tracer = provider.getTracer('default'); + const contextManager = new AsyncHooksContextManager().enable(); + + before(() => { + instrumentation.setTracerProvider(provider); + context.setGlobalContextManager(contextManager); + instrumentation.enable(); + }); + + afterEach(() => { + contextManager.disable(); + contextManager.enable(); + memoryExporter.reset(); + }); + + describe('Instrumenting with specific config', () => { + let app: express.Express; + let server: http.Server; + let port: number; + + beforeEach(async () => { + app = express(); + const httpServer = await createServer(app); + server = httpServer.server; + port = httpServer.port; + }); + + afterEach(() => { + server.close(); + }); + + it('should ignore specific middlewares based on config', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + + app.use(express.json()); + app.use((req, res, next) => { + for (let i = 0; i < 1000; i++) { + continue; + } + return next(); + }); + + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/toto/tata`); + rootSpan.end(); + assert.deepEqual( + memoryExporter + .getFinishedSpans() + .filter( + span => + span.attributes[AttributeNames.EXPRESS_TYPE] === + ExpressLayerType.MIDDLEWARE + ).length, + 0 + ); + const exportedRootSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name === 'rootSpan'); + assert.notStrictEqual(exportedRootSpan, undefined); + } + ); + }); + + it('should not repeat middleware paths in the span name', async () => { + let rpcMetadata: RPCMetadata; + app.use((req, res, next) => { + rpcMetadata = { type: RPCType.HTTP, span: rootSpan }; + return context.with( + setRPCMetadata( + trace.setSpan(context.active(), rootSpan), + rpcMetadata + ), + next + ); + }); + + app.use('/mw', (req, res, next) => { + next(); + }); + + app.get('/mw', (req, res) => { + res.send('ok'); + }); + + const rootSpan = tracer.startSpan('rootSpan'); + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get(`http://localhost:${port}/mw`); + assert.strictEqual(response, 'ok'); + rootSpan.end(); + + const requestHandlerSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('request handler')); + assert.notStrictEqual(requestHandlerSpan, undefined); + assert.strictEqual( + requestHandlerSpan?.attributes[SEMATTRS_HTTP_ROUTE], + '/mw' + ); + + assert.strictEqual( + requestHandlerSpan?.attributes[AttributeNames.EXPRESS_TYPE], + 'request_handler' + ); + assert.strictEqual(rpcMetadata.route, '/mw'); + } + ); + }); + + it('should correctly name http root path when its /', async () => { + instrumentation.setConfig({ + ignoreLayerTypes: [ + ExpressLayerType.MIDDLEWARE, + ExpressLayerType.REQUEST_HANDLER, + ], + } as ExpressInstrumentationConfig); + let rpcMetadata: RPCMetadata; + app.use((req, res, next) => { + rpcMetadata = { type: RPCType.HTTP, span: rootSpan }; + return context.with( + setRPCMetadata( + trace.setSpan(context.active(), rootSpan), + rpcMetadata + ), + next + ); + }); + + app.get('/', (req, res) => { + res.send('ok'); + }); + + const rootSpan = tracer.startSpan('rootSpan'); + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get(`http://localhost:${port}/`); + assert.strictEqual(response, 'ok'); + rootSpan.end(); + + const requestHandlerSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('request handler')); + assert.notStrictEqual(requestHandlerSpan, undefined); + assert.strictEqual( + requestHandlerSpan?.attributes[SEMATTRS_HTTP_ROUTE], + '/' + ); + + assert.strictEqual( + requestHandlerSpan?.attributes[AttributeNames.EXPRESS_TYPE], + 'request_handler' + ); + assert.strictEqual(rpcMetadata?.route, '/'); + } + ); + }); + }); +}); diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/express.test.ts b/plugins/node/opentelemetry-instrumentation-express/test/v5/express.test.ts new file mode 100644 index 0000000000..700542a3d6 --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/express.test.ts @@ -0,0 +1,858 @@ +/* + * 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 { SpanStatusCode, context, trace } from '@opentelemetry/api'; +import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; +import { + InMemorySpanExporter, + SimpleSpanProcessor, +} from '@opentelemetry/sdk-trace-base'; +import * as assert from 'assert'; +import { AttributeNames } from '../../src/enums/AttributeNames'; +import { ExpressInstrumentation } from '../../src'; +import { createServer, httpRequest, serverWithMiddleware } from './utils'; +import { SEMATTRS_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import * as testUtils from '@opentelemetry/contrib-test-utils'; + +const nodeVersionNotSupported = process.version.startsWith('v14'); +if (nodeVersionNotSupported) { + console.log('Skipping express v5 tests for node version 14'); + process.exit(0); +} + +const instrumentation = new ExpressInstrumentation(); +instrumentation.enable(); +instrumentation.disable(); + +import * as express from 'express'; +import { RPCMetadata, getRPCMetadata } from '@opentelemetry/core'; +import { Server } from 'http'; + +describe('ExpressInstrumentation', () => { + const provider = new NodeTracerProvider(); + const memoryExporter = new InMemorySpanExporter(); + const spanProcessor = new SimpleSpanProcessor(memoryExporter); + provider.addSpanProcessor(spanProcessor); + const tracer = provider.getTracer('default'); + const contextManager = new AsyncHooksContextManager().enable(); + + before(() => { + instrumentation.setTracerProvider(provider); + context.setGlobalContextManager(contextManager); + instrumentation.enable(); + }); + + afterEach(() => { + contextManager.disable(); + contextManager.enable(); + memoryExporter.reset(); + }); + + describe('Instrumenting normal get operations', () => { + let server: Server, port: number; + afterEach(() => { + server?.close(); + }); + + it('should create a child span for middlewares', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + const customMiddleware: express.RequestHandler = (req, res, next) => { + for (let i = 0; i < 1000000; i++) { + continue; + } + return next(); + }; + let finishListenerCount: number | undefined; + let rpcMetadata: RPCMetadata | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use(express.json()); + app.use((req, res, next) => { + rpcMetadata = getRPCMetadata(context.active()); + res.on('finish', () => { + finishListenerCount = res.listenerCount('finish'); + }); + next(); + }); + for (let index = 0; index < 15; index++) { + app.use(customMiddleware); + } + }); + server = httpServer.server; + port = httpServer.port; + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/toto/tata` + ); + assert.strictEqual(response, 'tata'); + rootSpan.end(); + assert.strictEqual(finishListenerCount, 2); + assert.notStrictEqual( + memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('customMiddleware')), + undefined + ); + assert.notStrictEqual( + memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('jsonParser')), + undefined + ); + const requestHandlerSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('request handler')); + assert.notStrictEqual(requestHandlerSpan, undefined); + assert.strictEqual( + requestHandlerSpan?.attributes[SEMATTRS_HTTP_ROUTE], + '/toto/:id' + ); + assert.strictEqual( + requestHandlerSpan?.attributes[AttributeNames.EXPRESS_TYPE], + 'request_handler' + ); + assert.strictEqual(rpcMetadata?.route, '/toto/:id'); + } + ); + }); + + it('supports sync middlewares directly responding', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let finishListenerCount: number | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use((req, res, next) => { + res.on('finish', () => { + finishListenerCount = res.listenerCount('finish'); + }); + next(); + }); + const syncMiddleware: express.RequestHandler = (req, res, next) => { + for (let i = 0; i < 1000000; i++) { + continue; + } + res.status(200).end('middleware'); + }; + for (let index = 0; index < 15; index++) { + app.use(syncMiddleware); + } + }); + server = httpServer.server; + port = httpServer.port; + + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/toto/tata` + ); + assert.strictEqual(response, 'middleware'); + rootSpan.end(); + assert.strictEqual(finishListenerCount, 3); + assert.notStrictEqual( + memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('syncMiddleware')), + undefined, + 'no syncMiddleware span' + ); + } + ); + }); + + it('supports async middlewares', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let finishListenerCount: number | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use((req, res, next) => { + res.on('finish', () => { + finishListenerCount = res.listenerCount('finish'); + }); + next(); + }); + const asyncMiddleware: express.RequestHandler = (req, res, next) => { + setTimeout(() => { + next(); + }, 50); + }; + for (let index = 0; index < 15; index++) { + app.use(asyncMiddleware); + } + }); + server = httpServer.server; + port = httpServer.port; + + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/toto/tata` + ); + assert.strictEqual(response, 'tata'); + rootSpan.end(); + assert.strictEqual(finishListenerCount, 2); + assert.notStrictEqual( + memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('asyncMiddleware')), + undefined, + 'no asyncMiddleware span' + ); + } + ); + }); + + it('supports async middlewares directly responding', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let finishListenerCount: number | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use((req, res, next) => { + res.on('finish', () => { + finishListenerCount = res.listenerCount('finish'); + }); + next(); + }); + const asyncMiddleware: express.RequestHandler = (req, res, next) => { + setTimeout(() => { + res.status(200).end('middleware'); + }, 50); + }; + for (let index = 0; index < 15; index++) { + app.use(asyncMiddleware); + } + }); + server = httpServer.server; + port = httpServer.port; + + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/toto/tata` + ); + assert.strictEqual(response, 'middleware'); + rootSpan.end(); + assert.strictEqual(finishListenerCount, 3); + assert.notStrictEqual( + memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('asyncMiddleware')), + undefined, + 'no asyncMiddleware span' + ); + } + ); + }); + + it('captures sync middleware errors', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let finishListenerCount: number | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use((req, res, next) => { + res.on('finish', () => { + finishListenerCount = res.listenerCount('finish'); + }); + next(); + }); + + const errorMiddleware: express.RequestHandler = (req, res, next) => { + throw new Error('message'); + }; + app.use(errorMiddleware); + }); + server = httpServer.server; + port = httpServer.port; + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/toto/tata`); + rootSpan.end(); + assert.strictEqual(finishListenerCount, 3); + + const errorSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('errorMiddleware')); + assert.notStrictEqual(errorSpan, undefined); + + assert.deepStrictEqual(errorSpan!.status, { + code: SpanStatusCode.ERROR, + message: 'message', + }); + assert.notStrictEqual( + errorSpan!.events.find(event => event.name === 'exception'), + undefined + ); + } + ); + }); + + it('captures async middleware errors', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let finishListenerCount: number | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use((req, res, next) => { + res.on('finish', () => { + finishListenerCount = res.listenerCount('finish'); + }); + next(); + }); + + const errorMiddleware: express.RequestHandler = (req, res, next) => { + setTimeout(() => next(new Error('message')), 10); + }; + app.use(errorMiddleware); + }); + server = httpServer.server; + port = httpServer.port; + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/toto/tata`); + rootSpan.end(); + assert.strictEqual(finishListenerCount, 2); + + const errorSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('errorMiddleware')); + assert.notStrictEqual(errorSpan, undefined); + + assert.deepStrictEqual(errorSpan!.status, { + code: SpanStatusCode.ERROR, + message: 'message', + }); + assert.notStrictEqual( + errorSpan!.events.find(event => event.name === 'exception'), + undefined + ); + } + ); + }); + + it('should not create span because there are no parent', async () => { + const app = express(); + app.use(express.json()); + app.use((req, res, next) => { + for (let i = 0; i < 1000000; i++) {} + return next(); + }); + const router = express.Router(); + app.use('/toto', router); + router.get('/:id', (req, res, next) => { + return res.status(200).end('test'); + }); + const httpServer = await createServer(app); + server = httpServer.server; + port = httpServer.port; + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + const res = await httpRequest.get(`http://localhost:${port}/toto/tata`); + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + assert.strictEqual(res, 'test'); + }); + + it('should update rpcMetadata.route with the bare middleware layer', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let rpcMetadata: RPCMetadata | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use(express.json()); + app.use((req, res, next) => { + rpcMetadata = getRPCMetadata(context.active()); + next(); + }); + + app.use('/bare_middleware', (req, res) => { + return res.status(200).end('test'); + }); + }); + server = httpServer.server; + port = httpServer.port; + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/bare_middleware/ignore_route_segment` + ); + assert.strictEqual(response, 'test'); + rootSpan.end(); + assert.strictEqual(rpcMetadata?.route, '/bare_middleware'); + } + ); + }); + + it('should update rpcMetadata.route with the latest middleware layer', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let rpcMetadata: RPCMetadata | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use(express.json()); + app.use((req, res, next) => { + rpcMetadata = getRPCMetadata(context.active()); + next(); + }); + + const router = express.Router(); + + app.use('/router', router); + + router.use('/router_middleware', (req, res) => { + return res.status(200).end('test'); + }); + }); + server = httpServer.server; + port = httpServer.port; + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/router/router_middleware/ignore_route_segment` + ); + assert.strictEqual(response, 'test'); + rootSpan.end(); + assert.strictEqual(rpcMetadata?.route, '/router/router_middleware'); + } + ); + }); + + it('should update rpcMetadata.route with the bare request handler layer', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let rpcMetadata: RPCMetadata | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use(express.json()); + app.use((req, res, next) => { + rpcMetadata = getRPCMetadata(context.active()); + next(); + }); + + app.get('/bare_route', (req, res) => { + return res.status(200).end('test'); + }); + }); + server = httpServer.server; + port = httpServer.port; + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/bare_route` + ); + assert.strictEqual(response, 'test'); + rootSpan.end(); + assert.strictEqual(rpcMetadata?.route, '/bare_route'); + } + ); + }); + + it('should ignore double slashes in routes', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let rpcMetadata: RPCMetadata | undefined; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use(express.json()); + app.use((req, res, next) => { + rpcMetadata = getRPCMetadata(context.active()); + next(); + }); + }); + server = httpServer.server; + port = httpServer.port; + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/double-slashes/foo` + ); + assert.strictEqual(response, 'foo'); + rootSpan.end(); + const requestHandlerSpan = memoryExporter + .getFinishedSpans() + .find(span => span.name.includes('request handler')); + assert.strictEqual( + requestHandlerSpan?.attributes[SEMATTRS_HTTP_ROUTE], + '/double-slashes/:id' + ); + assert.strictEqual(rpcMetadata?.route, '/double-slashes/:id'); + } + ); + }); + + it('should keep stack in the router layer handle', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let routerLayer: { name: string; handle: { stack: any[] } }; + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use(express.json()); + app.get('/bare_route', (req, res) => { + const stack = (req.app._router || req.app.router).stack as any[]; + routerLayer = stack.find(layer => layer.name === 'router'); + return res.status(200).end('test'); + }); + }); + server = httpServer.server; + port = httpServer.port; + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/bare_route` + ); + assert.strictEqual(response, 'test'); + rootSpan.end(); + assert.ok( + routerLayer?.handle?.stack?.length === 1, + 'router layer stack is accessible' + ); + } + ); + }); + + it('should keep the handle properties even if router is patched before instrumentation does it', async () => { + const rootSpan = tracer.startSpan('rootSpan'); + let routerLayer: { name: string; handle: { stack: any[] } }; + + const expressApp = express(); + const router = express.Router(); + const CustomRouter: (...p: Parameters) => void = ( + req, + res, + next + ) => router(req, res, next); + router.use('/:slug', (req, res, next) => { + const stack = (req.app._router || req.app.router).stack as any[]; + routerLayer = stack.find(router => router.name === 'CustomRouter'); + return res.status(200).end('bar'); + }); + // The patched router now has express router's own properties in its prototype so + // they are not accessible through `Object.keys(...)` + // https://github.com/TryGhost/Ghost/blob/fefb9ec395df8695d06442b6ecd3130dae374d94/ghost/core/core/frontend/web/site.js#L192 + Object.setPrototypeOf(CustomRouter, router); + expressApp.use(CustomRouter); + + const httpServer = await createServer(expressApp); + server = httpServer.server; + port = httpServer.port; + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + const response = await httpRequest.get( + `http://localhost:${port}/foo` + ); + assert.strictEqual(response, 'bar'); + rootSpan.end(); + assert.ok( + routerLayer.handle.stack.length === 1, + 'router layer stack is accessible' + ); + } + ); + }); + }); + + describe('Disabling plugin', () => { + let server: Server, port: number; + afterEach(() => { + server?.close(); + }); + it('should not create new spans', async () => { + instrumentation.disable(); + const rootSpan = tracer.startSpan('rootSpan'); + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.use(express.json()); + const customMiddleware: express.RequestHandler = (req, res, next) => { + for (let i = 0; i < 1000; i++) { + continue; + } + return next(); + }; + app.use(customMiddleware); + }); + server = httpServer.server; + port = httpServer.port; + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/toto/tata`); + rootSpan.end(); + // There should be exactly one span, and it should be the root span. + // There should not be any spans from the Express instrumentation. + assert.deepEqual(memoryExporter.getFinishedSpans().length, 1); + assert.notStrictEqual( + memoryExporter.getFinishedSpans()[0], + undefined + ); + } + ); + }); + }); + + it('should work with ESM usage', async () => { + await testUtils.runTestFixture({ + cwd: __dirname, + argv: ['fixtures/use-express.mjs'], + env: { + NODE_OPTIONS: + '--experimental-loader=@opentelemetry/instrumentation/hook.mjs', + NODE_NO_WARNINGS: '1', + }, + checkResult: (err, stdout, stderr) => { + assert.ifError(err); + }, + checkCollector: (collector: testUtils.TestCollector) => { + // use-express.mjs creates an express app with a 'GET /post/:id' endpoint and + // a `simpleMiddleware`, then makes a single 'GET /post/0' request. We + // expect to see spans like this: + // span 'GET /post/:id' + // `- span 'middleware - simpleMiddleware' + // `- span 'request handler - /post/:id' + const spans = collector.sortedSpans; + assert.strictEqual(spans[0].name, 'GET'); + assert.strictEqual(spans[0].kind, testUtils.OtlpSpanKind.CLIENT); + assert.strictEqual(spans[1].name, 'GET /post/:id'); + assert.strictEqual(spans[1].kind, testUtils.OtlpSpanKind.SERVER); + assert.strictEqual(spans[1].parentSpanId, spans[0].spanId); + assert.strictEqual(spans[2].name, 'middleware - simpleMiddleware'); + assert.strictEqual(spans[2].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[2].parentSpanId, spans[1].spanId); + assert.strictEqual(spans[3].name, 'request handler - /post/:id'); + assert.strictEqual(spans[3].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[3].parentSpanId, spans[1].spanId); + }, + }); + }); + + it('should work with Express routers', async () => { + await testUtils.runTestFixture({ + cwd: __dirname, + argv: ['fixtures/use-express-router.mjs'], + env: { + NODE_OPTIONS: + '--experimental-loader=@opentelemetry/instrumentation/hook.mjs', + NODE_NO_WARNINGS: '1', + }, + checkResult: (err, stdout, stderr) => { + assert.ifError(err); + }, + checkCollector: (collector: testUtils.TestCollector) => { + const spans = collector.sortedSpans; + + assert.strictEqual(spans[0].name, 'GET'); + assert.strictEqual(spans[0].kind, testUtils.OtlpSpanKind.CLIENT); + assert.strictEqual(spans[1].name, 'GET /api/user/:id'); + assert.strictEqual(spans[1].kind, testUtils.OtlpSpanKind.SERVER); + assert.strictEqual(spans[1].parentSpanId, spans[0].spanId); + assert.strictEqual(spans[2].name, 'middleware - simpleMiddleware'); + assert.strictEqual(spans[2].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[2].parentSpanId, spans[1].spanId); + assert.strictEqual(spans[3].name, 'router - /api/user/:id'); + assert.strictEqual(spans[3].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[3].parentSpanId, spans[1].spanId); + assert.strictEqual(spans[4].name, 'request handler - /api/user/:id'); + assert.strictEqual(spans[4].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[4].parentSpanId, spans[1].spanId); + }, + }); + }); + + it('should work with nested Express routers', async () => { + await testUtils.runTestFixture({ + cwd: __dirname, + argv: ['fixtures/use-express-nested-router.mjs'], + env: { + NODE_OPTIONS: + '--experimental-loader=@opentelemetry/instrumentation/hook.mjs', + NODE_NO_WARNINGS: '1', + }, + checkResult: (err, stdout, stderr) => { + assert.ifError(err); + }, + checkCollector: (collector: testUtils.TestCollector) => { + const spans = collector.sortedSpans; + + assert.strictEqual(spans[0].name, 'GET'); + assert.strictEqual(spans[0].kind, testUtils.OtlpSpanKind.CLIENT); + assert.strictEqual(spans[1].name, 'GET /api/user/:id/posts/:postId'); + assert.strictEqual(spans[1].kind, testUtils.OtlpSpanKind.SERVER); + assert.strictEqual(spans[2].name, 'middleware - simpleMiddleware'); + assert.strictEqual(spans[2].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[2].parentSpanId, spans[1].spanId); + assert.strictEqual(spans[3].name, 'router - /api/user/:id'); + assert.strictEqual(spans[3].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[3].parentSpanId, spans[1].spanId); + assert.strictEqual(spans[4].name, 'router - /:postId'); + assert.strictEqual(spans[4].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[4].parentSpanId, spans[1].spanId); + assert.strictEqual( + spans[5].name, + 'request handler - /api/user/:id/posts/:postId' + ); + assert.strictEqual(spans[5].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[5].parentSpanId, spans[1].spanId); + }, + }); + }); + + it('should set a correct transaction name for routes specified in RegEx', async () => { + await testUtils.runTestFixture({ + cwd: __dirname, + argv: ['fixtures/use-express-regex.mjs'], + env: { + NODE_OPTIONS: + '--experimental-loader=@opentelemetry/instrumentation/hook.mjs', + NODE_NO_WARNINGS: '1', + TEST_REGEX_ROUTE: '/test/regex', + }, + checkResult: (err, stdout, stderr) => { + assert.ifError(err); + }, + checkCollector: (collector: testUtils.TestCollector) => { + const spans = collector.sortedSpans; + + assert.strictEqual(spans[0].name, 'GET'); + assert.strictEqual(spans[0].kind, testUtils.OtlpSpanKind.CLIENT); + assert.strictEqual(spans[1].name, 'GET /\\/test\\/regex/'); + assert.strictEqual(spans[1].parentSpanId, spans[0].spanId); + assert.strictEqual(spans[1].kind, testUtils.OtlpSpanKind.SERVER); + assert.strictEqual(spans[2].name, 'middleware - simpleMiddleware'); + assert.strictEqual(spans[2].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[2].parentSpanId, spans[1].spanId); + assert.strictEqual( + spans[3].name, + 'request handler - /\\/test\\/regex/' + ); + assert.strictEqual(spans[3].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[3].parentSpanId, spans[1].spanId); + }, + }); + }); + + it('should set a correct transaction name for routes consisting of array including numbers', async () => { + await testUtils.runTestFixture({ + cwd: __dirname, + argv: ['fixtures/use-express-regex.mjs'], + env: { + NODE_OPTIONS: + '--experimental-loader=@opentelemetry/instrumentation/hook.mjs', + NODE_NO_WARNINGS: '1', + TEST_REGEX_ROUTE: '/test/6/test', + }, + checkResult: err => { + assert.ifError(err); + }, + checkCollector: (collector: testUtils.TestCollector) => { + const spans = collector.sortedSpans; + + assert.strictEqual(spans[0].name, 'GET'); + assert.strictEqual(spans[0].kind, testUtils.OtlpSpanKind.CLIENT); + assert.strictEqual(spans[1].name, 'GET /test,6,/test/'); + assert.strictEqual(spans[1].kind, testUtils.OtlpSpanKind.SERVER); + assert.strictEqual(spans[2].name, 'middleware - simpleMiddleware'); + assert.strictEqual(spans[2].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[2].parentSpanId, spans[1].spanId); + assert.strictEqual(spans[3].name, 'request handler - /test,6,/test/'); + assert.strictEqual(spans[3].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[3].parentSpanId, spans[1].spanId); + }, + }); + }); + + for (const segment of ['array1', 'array5']) { + it('should set a correct transaction name for routes consisting of arrays of routes', async () => { + await testUtils.runTestFixture({ + cwd: __dirname, + argv: ['fixtures/use-express-regex.mjs'], + env: { + NODE_OPTIONS: + '--experimental-loader=@opentelemetry/instrumentation/hook.mjs', + NODE_NO_WARNINGS: '1', + TEST_REGEX_ROUTE: `/test/${segment}`, + }, + checkResult: err => { + assert.ifError(err); + }, + checkCollector: (collector: testUtils.TestCollector) => { + const spans = collector.sortedSpans; + + assert.strictEqual(spans[0].name, 'GET'); + assert.strictEqual(spans[0].kind, testUtils.OtlpSpanKind.CLIENT); + assert.strictEqual( + spans[1].name, + 'GET /test/array1,/\\/test\\/array[2-9]/' + ); + assert.strictEqual(spans[1].kind, testUtils.OtlpSpanKind.SERVER); + assert.strictEqual(spans[2].name, 'middleware - simpleMiddleware'); + assert.strictEqual(spans[2].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[2].parentSpanId, spans[1].spanId); + assert.strictEqual( + spans[3].name, + 'request handler - /test/array1,/\\/test\\/array[2-9]/' + ); + assert.strictEqual(spans[3].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[3].parentSpanId, spans[1].spanId); + }, + }); + }); + } + + const COMMON_PATH = + '/test/arr/:id,/\\/test\\/arr[0-9]*\\/required(path)?(\\/optionalPath)?\\/(lastParam)?/'; + + for (const [segment, path] of [ + ['arr/545', COMMON_PATH], + ['arr/required', COMMON_PATH], + ['arr/required', COMMON_PATH], + ['arr/requiredPath', COMMON_PATH], + ['arr/required/lastParam', COMMON_PATH], + ['arr55/required/lastParam', COMMON_PATH], + ['arr/requiredPath/optionalPath/', '/test,6,/test/'], + ['arr/requiredPath/optionalPath/lastParam', '/test,6,/test/'], + ]) { + it('should handle more complex regexes in route arrays correctly', async () => { + await testUtils.runTestFixture({ + cwd: __dirname, + argv: ['fixtures/use-express-regex.mjs'], + env: { + NODE_OPTIONS: + '--experimental-loader=@opentelemetry/instrumentation/hook.mjs', + NODE_NO_WARNINGS: '1', + TEST_REGEX_ROUTE: `/test/${segment}`, + }, + checkResult: err => { + assert.ifError(err); + }, + checkCollector: (collector: testUtils.TestCollector) => { + const spans = collector.sortedSpans; + + assert.strictEqual(spans[0].name, 'GET'); + assert.strictEqual(spans[0].kind, testUtils.OtlpSpanKind.CLIENT); + assert.strictEqual(spans[1].name, `GET ${path}`); + assert.strictEqual(spans[1].kind, testUtils.OtlpSpanKind.SERVER); + assert.strictEqual(spans[2].name, 'middleware - simpleMiddleware'); + assert.strictEqual(spans[2].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[2].parentSpanId, spans[1].spanId); + assert.strictEqual(spans[3].name, `request handler - ${path}`); + assert.strictEqual(spans[3].kind, testUtils.OtlpSpanKind.INTERNAL); + assert.strictEqual(spans[3].parentSpanId, spans[1].spanId); + }, + }); + }); + } +}); diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express-nested-router.mjs b/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express-nested-router.mjs new file mode 100644 index 0000000000..e229815d9c --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express-nested-router.mjs @@ -0,0 +1,81 @@ +/* + * 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 { promisify } from 'util'; +import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils'; + +import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; +import { ExpressInstrumentation } from '../../../build/src/index.js'; + +const sdk = createTestNodeSdk({ + serviceName: 'use-express-nested', + instrumentations: [ + new HttpInstrumentation(), + new ExpressInstrumentation() + ] +}) + +sdk.start(); + +import express from 'express'; +import * as http from 'http'; + +const app = express(); + +app.use(async function simpleMiddleware(req, res, next) { + // Wait a short delay to ensure this "middleware - ..." span clearly starts + // before the "router - ..." span. The test rely on a start-time-based sort + // of the produced spans. If they start in the same millisecond, then tests + // can be flaky. + await promisify(setTimeout)(10); + next(); +}); + +const userRouter = express.Router(); +const postsRouter = express.Router(); + +postsRouter.get('/:postId', (req, res, next) => { + res.json({ hello: 'yes' }); + res.end(); + next(); +}); + +userRouter.get('/api/user/:id', (req, res, next) => { + res.json({ hello: 'yes' }); + res.end(); + next(); +}); + +userRouter.use('/api/user/:id/posts', postsRouter); + +app.use(userRouter); + +const server = http.createServer(app); +await new Promise(resolve => server.listen(0, resolve)); +const port = server.address().port; + + +await new Promise(resolve => { + http.get(`http://localhost:${port}/api/user/123/posts/321`, (res) => { + res.resume(); + res.on('end', data => { + resolve(data); + }); + }) +}); + +await new Promise(resolve => server.close(resolve)); +await sdk.shutdown(); diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express-regex.mjs b/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express-regex.mjs new file mode 100644 index 0000000000..053ae8e8e5 --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express-regex.mjs @@ -0,0 +1,82 @@ +/* + * 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. + */ + +// Use express from an ES module: +// node --experimental-loader=@opentelemetry/instrumentation/hook.mjs use-express-regex.mjs + +import { promisify } from 'util'; +import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils'; + +import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; +import { ExpressInstrumentation } from '../../../build/src/index.js'; + +const sdk = createTestNodeSdk({ + serviceName: 'use-express-regex', + instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()], +}); +sdk.start(); + +import express from 'express'; +import * as http from 'http'; + +const app = express(); + +app.use(async function simpleMiddleware(req, res, next) { + // Wait a short delay to ensure this "middleware - ..." span clearly starts + // before the "router - ..." span. The test rely on a start-time-based sort + // of the produced spans. If they start in the same millisecond, then tests + // can be flaky. + await promisify(setTimeout)(10); + next(); +}); + +app.get( + [ + '/test/arr/:id', + /\/test\/arr[0-9]*\/required(path)?(\/optionalPath)?\/(lastParam)?/, + ], + (_req, res) => { + res.send({ response: 'response' }); + } +); + +app.get(/\/test\/regex/, (_req, res) => { + res.send({ response: 'response 2' }); +}); + +app.get(['/test/array1', /\/test\/array[2-9]/], (_req, res) => { + res.send({ response: 'response 3' }); +}); + +app.get(['/test', '6', /test/], (_req, res) => { + res.send({ response: 'response 4' }); +}); + +const server = http.createServer(app); +await new Promise(resolve => server.listen(0, resolve)); +const port = server.address().port; + +await new Promise(resolve => { + http.get(`http://localhost:${port}${process.env.TEST_REGEX_ROUTE}`, res => { + res.resume(); + res.on('end', () => { + resolve(); + }); + }); +}); + +await new Promise(resolve => server.close(resolve)); +await sdk.shutdown(); diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express-router.mjs b/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express-router.mjs new file mode 100644 index 0000000000..baf0def40b --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express-router.mjs @@ -0,0 +1,69 @@ +/* + * 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 { promisify } from 'util'; +import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils'; + +import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; +import { ExpressInstrumentation } from '../../../build/src/index.js'; + +const sdk = createTestNodeSdk({ + serviceName: 'use-express-nested', + instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()], +}); + +sdk.start(); + +import express from 'express'; +import * as http from 'http'; + +const app = express(); + +app.use(async function simpleMiddleware(req, res, next) { + // Wait a short delay to ensure this "middleware - ..." span clearly starts + // before the "router - ..." span. The test rely on a start-time-based sort + // of the produced spans. If they start in the same millisecond, then tests + // can be flaky. + await promisify(setTimeout)(10); + next(); +}); + +const router = express.Router(); + +router.get('/api/user/:id', (req, res, next) => { + res.json({ hello: 'yes' }); + res.end(); + next(); +}); + +app.use(router); + +const server = http.createServer(app); +await new Promise(resolve => server.listen(0, resolve)); +const port = server.address().port; + + +await new Promise(resolve => { + http.get(`http://localhost:${port}/api/user/123`, (res) => { + res.resume(); + res.on('end', data => { + resolve(data); + }); + }) +}); + +await new Promise(resolve => server.close(resolve)); +await sdk.shutdown(); diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express.mjs b/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express.mjs new file mode 100644 index 0000000000..abdcfb73d0 --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/fixtures/use-express.mjs @@ -0,0 +1,64 @@ +/* + * 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. + */ + +// Use express from an ES module: +// node --experimental-loader=@opentelemetry/instrumentation/hook.mjs use-express.mjs + +import { promisify } from 'util'; +import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils'; + +import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; +import { ExpressInstrumentation } from '../../../build/src/index.js'; + +const sdk = createTestNodeSdk({ + serviceName: 'use-express', + instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()], +}); +sdk.start(); + +import express from 'express'; +import * as http from 'http'; + +const app = express(); + +app.use(async function simpleMiddleware(req, res, next) { + // Wait a short delay to ensure this "middleware - ..." span clearly starts + // before the "router - ..." span. The test rely on a start-time-based sort + // of the produced spans. If they start in the same millisecond, then tests + // can be flaky. + await promisify(setTimeout)(10); + next(); +}); + +app.get('/post/:id', (req, res) => { + res.send(`Post id: ${req.params.id}`); +}); + +const server = http.createServer(app); +await new Promise(resolve => server.listen(0, resolve)); +const port = server.address().port; + +await new Promise(resolve => { + http.get(`http://localhost:${port}/post/0`, res => { + res.resume(); + res.on('end', () => { + resolve(); + }); + }); +}); + +await new Promise(resolve => server.close(resolve)); +await sdk.shutdown(); diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/hooks.test.ts b/plugins/node/opentelemetry-instrumentation-express/test/v5/hooks.test.ts new file mode 100644 index 0000000000..f2215e0fe9 --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/hooks.test.ts @@ -0,0 +1,256 @@ +/* + * 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, trace, Span } from '@opentelemetry/api'; +import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; +import { + InMemorySpanExporter, + SimpleSpanProcessor, +} from '@opentelemetry/sdk-trace-base'; +import * as assert from 'assert'; +import type * as http from 'http'; +import * as sinon from 'sinon'; +import { ExpressInstrumentation } from '../../src'; +import { ExpressRequestInfo, SpanNameHook } from '../../src/types'; +import { ExpressLayerType } from '../../src/enums/ExpressLayerType'; +import { SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions'; + +const nodeVersionNotSupported = process.version.startsWith('v14'); +if (nodeVersionNotSupported) { + console.log('Skipping express v5 tests for node version 14'); + process.exit(0); +} + +const instrumentation = new ExpressInstrumentation(); +instrumentation.enable(); +instrumentation.disable(); + +import { httpRequest, serverWithMiddleware } from './utils'; +import { RPCMetadata, getRPCMetadata } from '@opentelemetry/core'; + +describe('ExpressInstrumentation hooks', () => { + const provider = new NodeTracerProvider(); + const memoryExporter = new InMemorySpanExporter(); + const spanProcessor = new SimpleSpanProcessor(memoryExporter); + provider.addSpanProcessor(spanProcessor); + const tracer = provider.getTracer('default'); + const contextManager = new AsyncHooksContextManager().enable(); + + before(() => { + instrumentation.setTracerProvider(provider); + context.setGlobalContextManager(contextManager); + instrumentation.enable(); + }); + + afterEach(() => { + contextManager.disable(); + contextManager.enable(); + memoryExporter.reset(); + }); + + describe('span name hooks', () => { + let server: http.Server; + let port: number; + let rootSpan: Span; + let rpcMetadata: RPCMetadata | undefined; + + beforeEach(async () => { + rootSpan = tracer.startSpan('rootSpan'); + + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.get(/.*/, (req, res) => { + rpcMetadata = getRPCMetadata(context.active()); + res.send('ok'); + }); + }); + server = httpServer.server; + port = httpServer.port; + }); + + afterEach(() => { + server.close(); + }); + + it('should rename spans', async () => { + instrumentation.setConfig({ + spanNameHook: ({ route, layerType }) => { + return `custom: ${layerType} - ${route}`; + }, + }); + + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/foo/3`); + rootSpan.end(); + + const spans = memoryExporter.getFinishedSpans(); + assert.strictEqual(spans.length, 2); + + assert.notStrictEqual( + spans.find(span => span.name === 'custom: request_handler - /.*/'), + undefined + ); + } + ); + }); + + it('should use the default name when hook throws an error', async () => { + instrumentation.setConfig({ + spanNameHook: () => { + throw new Error(); + }, + }); + + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/foo/3`); + rootSpan.end(); + + const spans = memoryExporter.getFinishedSpans(); + assert.strictEqual(spans.length, 2); + + assert.strictEqual(rpcMetadata?.route, '/.*/'); + assert.notStrictEqual( + spans.find(span => span.name === 'request handler - /.*/'), + undefined + ); + } + ); + }); + + it('should use the default name when returning undefined from hook', async () => { + const spanNameHook: SpanNameHook = () => { + return undefined as unknown as string; + }; + instrumentation.setConfig({ + spanNameHook, + }); + + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/foo/3`); + rootSpan.end(); + + const spans = memoryExporter.getFinishedSpans(); + assert.strictEqual(spans.length, 2); + + assert.strictEqual(rpcMetadata?.route, '/.*/'); + assert.notStrictEqual( + spans.find(span => span.name === 'request handler - /.*/'), + undefined + ); + } + ); + }); + }); + + describe('request hooks', () => { + let server: http.Server; + let port: number; + let rootSpan: Span; + + beforeEach(async () => { + rootSpan = tracer.startSpan('rootSpan'); + + const httpServer = await serverWithMiddleware(tracer, rootSpan, app => { + app.get(/.*/, (req, res) => { + res.send('ok'); + }); + }); + server = httpServer.server; + port = httpServer.port; + }); + + afterEach(() => { + server.close(); + }); + + it('should call requestHook when set in config', async () => { + const requestHook = sinon.spy((span: Span, info: ExpressRequestInfo) => { + span.setAttribute(SEMATTRS_HTTP_METHOD, info.request.method); + + if (info.layerType) { + span.setAttribute('express.layer_type', info.layerType); + } + }); + + instrumentation.setConfig({ + requestHook, + }); + + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/foo/3`); + rootSpan.end(); + + const spans = memoryExporter.getFinishedSpans(); + const requestHandlerSpan = spans.find( + span => span.name === 'request handler - /.*/' + ); + + assert.strictEqual(spans.length, 2); + sinon.assert.calledOnce(requestHook); + assert.strictEqual( + requestHandlerSpan?.attributes['http.method'], + 'GET' + ); + assert.strictEqual( + requestHandlerSpan?.attributes['express.layer_type'], + ExpressLayerType.REQUEST_HANDLER + ); + } + ); + }); + + it('should ignore requestHook which throws exception', async () => { + const requestHook = sinon.spy((span: Span, info: ExpressRequestInfo) => { + // This is added before the exception is thrown thus we can expect it + span.setAttribute('http.method', info.request.method); + throw Error('error thrown in requestHook'); + }); + + instrumentation.setConfig({ + requestHook, + }); + + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/foo/3`); + rootSpan.end(); + + const spans = memoryExporter.getFinishedSpans(); + const requestHandlerSpan = spans.find( + span => span.name === 'request handler - /.*/' + ); + + assert.strictEqual(spans.length, 2); + assert.strictEqual( + requestHandlerSpan?.attributes['http.method'], + 'GET' + ); + + sinon.assert.threw(requestHook); + } + ); + }); + }); +}); diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/ignore-all.test.ts b/plugins/node/opentelemetry-instrumentation-express/test/v5/ignore-all.test.ts new file mode 100644 index 0000000000..69f1a9b880 --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/ignore-all.test.ts @@ -0,0 +1,148 @@ +/* + * 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, trace, Span } from '@opentelemetry/api'; +import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; +import { + InMemorySpanExporter, + SimpleSpanProcessor, +} from '@opentelemetry/sdk-trace-base'; +import * as assert from 'assert'; +import { RPCMetadata, RPCType, setRPCMetadata } from '@opentelemetry/core'; +import { AttributeNames } from '../../src/enums/AttributeNames'; +import { ExpressInstrumentation, ExpressLayerType } from '../../src'; +import { createServer, httpRequest } from './utils'; + +const nodeVersionNotSupported = process.version.startsWith('v14'); +if (nodeVersionNotSupported) { + console.log('Skipping express v5 tests for node version 14'); + process.exit(0); +} + +const instrumentation = new ExpressInstrumentation({ + ignoreLayersType: [ + ExpressLayerType.MIDDLEWARE, + ExpressLayerType.ROUTER, + ExpressLayerType.REQUEST_HANDLER, + ], +}); +instrumentation.enable(); +instrumentation.disable(); + +import * as express from 'express'; +import * as http from 'http'; + +describe('ExpressInstrumentation', () => { + const provider = new NodeTracerProvider(); + const memoryExporter = new InMemorySpanExporter(); + const spanProcessor = new SimpleSpanProcessor(memoryExporter); + provider.addSpanProcessor(spanProcessor); + const tracer = provider.getTracer('default'); + const contextManager = new AsyncHooksContextManager().enable(); + + before(() => { + instrumentation.setTracerProvider(provider); + context.setGlobalContextManager(contextManager); + instrumentation.enable(); + }); + + afterEach(() => { + contextManager.disable(); + contextManager.enable(); + memoryExporter.reset(); + }); + + describe('when route exists', () => { + let server: http.Server; + let port: number; + let rootSpan: Span; + let rpcMetadata: RPCMetadata; + + beforeEach(async () => { + rootSpan = tracer.startSpan('rootSpan'); + const app = express(); + + app.use((req, res, next) => { + rpcMetadata = { type: RPCType.HTTP, span: rootSpan }; + return context.with( + setRPCMetadata( + trace.setSpan(context.active(), rootSpan), + rpcMetadata + ), + next + ); + }); + app.use(express.json()); + app.use((req, res, next) => { + for (let i = 0; i < 1000; i++) {} + return next(); + }); + const router = express.Router(); + app.use('/toto', router); + router.get('/:id', (req, res) => { + setImmediate(() => { + res.status(200).end(); + }); + }); + + const httpServer = await createServer(app); + server = httpServer.server; + port = httpServer.port; + }); + + afterEach(() => { + server.close(); + }); + + it('should ignore all ExpressLayerType based on config', async () => { + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/toto/tata`); + rootSpan.end(); + assert.deepStrictEqual( + memoryExporter + .getFinishedSpans() + .filter( + span => + span.attributes[AttributeNames.EXPRESS_TYPE] === + ExpressLayerType.MIDDLEWARE || + span.attributes[AttributeNames.EXPRESS_TYPE] === + ExpressLayerType.ROUTER || + span.attributes[AttributeNames.EXPRESS_TYPE] === + ExpressLayerType.REQUEST_HANDLER + ).length, + 0 + ); + } + ); + }); + + it('rpcMetadata.route still capture correct route', async () => { + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + await context.with( + trace.setSpan(context.active(), rootSpan), + async () => { + await httpRequest.get(`http://localhost:${port}/toto/tata`); + rootSpan.end(); + assert.strictEqual(rpcMetadata.route, '/toto/:id'); + } + ); + }); + }); +}); diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/package-lock.json b/plugins/node/opentelemetry-instrumentation-express/test/v5/package-lock.json new file mode 100644 index 0000000000..97552472af --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/package-lock.json @@ -0,0 +1,1533 @@ +{ + "name": "v5", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "express": "5.0.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.1.tgz", + "integrity": "sha512-PagxbjvuPH6tv0f/kdVbFGcb79D236SLcDTs6DrQ7GizJ88S1UWP4nMXFEo/I4fdhGRGabvFfFjVGm3M7U8JwA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "3.1.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.5.2", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "^3.0.0", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz", + "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.0.0.tgz", + "integrity": "sha512-V4UkHQc+B7ldh1YC84HCXHwf60M4BOMvp9rkvTUWCK5apqDC1Esnbid4wm6nFyVuDy8XMfETsJw5lsIGBWyo0A==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", + "http-errors": "2.0.0", + "merge-descriptors": "^2.0.0", + "methods": "~1.1.2", + "mime-types": "^3.0.0", + "on-finished": "2.4.1", + "once": "1.4.0", + "parseurl": "~1.3.3", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "router": "^2.0.0", + "safe-buffer": "5.2.1", + "send": "^1.1.0", + "serve-static": "^2.1.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "^2.0.0", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/finalhandler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", + "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", + "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz", + "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==", + "license": "MIT", + "dependencies": { + "array-flatten": "3.0.0", + "is-promise": "4.0.0", + "methods": "~1.1.2", + "parseurl": "~1.3.3", + "path-to-regexp": "^8.0.0", + "setprototypeof": "1.2.0", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", + "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", + "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", + "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + }, + "dependencies": { + "accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "requires": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + } + }, + "array-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" + }, + "body-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.1.tgz", + "integrity": "sha512-PagxbjvuPH6tv0f/kdVbFGcb79D236SLcDTs6DrQ7GizJ88S1UWP4nMXFEo/I4fdhGRGabvFfFjVGm3M7U8JwA==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "3.1.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.5.2", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "^3.0.0", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + } + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + }, + "cookie-signature": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz", + "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==" + }, + "debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "requires": { + "ms": "2.1.2" + } + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.0.0.tgz", + "integrity": "sha512-V4UkHQc+B7ldh1YC84HCXHwf60M4BOMvp9rkvTUWCK5apqDC1Esnbid4wm6nFyVuDy8XMfETsJw5lsIGBWyo0A==", + "requires": { + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", + "http-errors": "2.0.0", + "merge-descriptors": "^2.0.0", + "methods": "~1.1.2", + "mime-types": "^3.0.0", + "on-finished": "2.4.1", + "once": "1.4.0", + "parseurl": "~1.3.3", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "router": "^2.0.0", + "safe-buffer": "5.2.1", + "send": "^1.1.0", + "serve-static": "^2.1.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "^2.0.0", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", + "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + }, + "merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==" + }, + "mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "requires": { + "mime-db": "^1.53.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" + }, + "object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", + "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "requires": { + "side-channel": "^1.0.6" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "router": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz", + "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==", + "requires": { + "array-flatten": "3.0.0", + "is-promise": "4.0.0", + "methods": "~1.1.2", + "parseurl": "~1.3.3", + "path-to-regexp": "^8.0.0", + "setprototypeof": "1.2.0", + "utils-merge": "1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", + "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "requires": { + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "dependencies": { + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", + "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", + "requires": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.0.0" + } + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "type-is": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", + "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "requires": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/package.json b/plugins/node/opentelemetry-instrumentation-express/test/v5/package.json new file mode 100644 index 0000000000..6baec58838 --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "express": "5.0.0" + } +} diff --git a/plugins/node/opentelemetry-instrumentation-express/test/v5/utils.ts b/plugins/node/opentelemetry-instrumentation-express/test/v5/utils.ts new file mode 100644 index 0000000000..f557d48fcd --- /dev/null +++ b/plugins/node/opentelemetry-instrumentation-express/test/v5/utils.ts @@ -0,0 +1,77 @@ +/* + * 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, trace, Span, Tracer } from '@opentelemetry/api'; +import { setRPCMetadata, RPCType } from '@opentelemetry/core'; +import * as http from 'http'; +import type { AddressInfo } from 'net'; +import * as express from 'express'; + +export 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); + }); + }); + }); + }, +}; + +export async function createServer(app: express.Express) { + const server = http.createServer(app); + await new Promise(resolve => server.listen(0, resolve)); + const port = (server.address() as AddressInfo).port; + return { server, port }; +} + +export async function serverWithMiddleware( + tracer: Tracer, + rootSpan: Span, + addMiddlewares: (app: express.Express) => void +) { + const app = express(); + if (tracer) { + app.use((req, res, next) => { + const rpcMetadata = { type: RPCType.HTTP, span: rootSpan }; + return context.with( + setRPCMetadata(trace.setSpan(context.active(), rootSpan), rpcMetadata), + next + ); + }); + } + + addMiddlewares(app); + + const router = express.Router(); + app.use('/toto', router); + app.use('/double-slashes/', router); + router.get('/:id', (req, res) => { + setImmediate(() => { + res.status(200).end(req.params.id); + }); + }); + + return createServer(app); +}