diff --git a/.gitignore b/.gitignore index 82746c5afe..248d4dce8a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ nr-security-home # benchmark results benchmark_results bin/.env + +# Local developer resources: +local/ diff --git a/lib/metrics/names.js b/lib/metrics/names.js index 1a2b94b86a..cb90af8e0f 100644 --- a/lib/metrics/names.js +++ b/lib/metrics/names.js @@ -353,7 +353,8 @@ const FEATURES = { SOURCE_MAPS: `${SUPPORTABILITY.FEATURES}/EnableSourceMaps`, CERTIFICATES: SUPPORTABILITY.FEATURES + '/Certificates', INSTRUMENTATION: { - ON_REQUIRE: SUPPORTABILITY.FEATURES + '/Instrumentation/OnRequire' + ON_REQUIRE: SUPPORTABILITY.FEATURES + '/Instrumentation/OnRequire', + SUBSCRIBER_USED: SUPPORTABILITY.FEATURES + '/Instrumentation/SubscriberUsed' } } diff --git a/lib/shimmer.js b/lib/shimmer.js index 0cb101d40e..1e94f5da28 100644 --- a/lib/shimmer.js +++ b/lib/shimmer.js @@ -306,7 +306,7 @@ const shimmer = (module.exports = { registerHooks(agent) { // add the packages from the subscriber based instrumentation // this is only added to add tracking metrics - pkgsToHook.push(...Object.keys(subscriptions), ...trackingPkgs) + Array.prototype.push.apply(pkgsToHook, trackingPkgs) this._ritm = new Hook(pkgsToHook, function onHook(exports, name, basedir) { return _postLoad(agent, exports, name, basedir) }) diff --git a/lib/subscribers/base.js b/lib/subscribers/base.js index 242b254adc..0744eb651b 100644 --- a/lib/subscribers/base.js +++ b/lib/subscribers/base.js @@ -7,6 +7,8 @@ // eslint-disable-next-line n/no-unsupported-features/node-builtins const { tracingChannel } = require('node:diagnostics_channel') const cat = require('#agentlib/util/cat.js') +const recordSupportabilityMetric = require('./record-supportability-metric.js') + // Used for the `traceCallback` work. // This can be removed when we add true support into orchestrion const makeCall = (fn) => (...args) => fn.call(...args) @@ -28,38 +30,45 @@ const ArrayPrototypeSplice = makeCall(Array.prototype.splice) /** * @property {object} agent A New Relic Node.js agent instance. - * @property {object} logger An agent logger instance. + * @property {TracingChannel} channel The tracing channel instance this subscriber will be monitoring. + * @property {string} channelName A unique name for the diagnostics channel + * that will be registered. * @property {object} config The agent configuration object. + * @property {string} id A unique identifier for the subscriber, combining the prefix, package + * name, and channel name. + * @property {object} logger An agent logger instance. * @property {string} packageName The name of the module being instrumented. * This is the same string one would pass to the `require` function. - * @property {string} channelName A unique name for the diagnostics channel - * that will be registered. + * @property {AsyncLocalStorage} store The async local storage instance used for context management. + * @property {number} [callback=null] Position of callback if it needs to be wrapped for instrumentation. + * -1 means last argument. * @property {string[]} [events=[]] Set of tracing channel event names to * register handlers for. For any name in the set, a corresponding method * must exist on the subscriber instance. The method will be passed the * event object. Possible event names are `start`, `end`, `asyncStart`, * `asyncEnd`, and `error`. - * - * See {@link https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel} - * @property {boolean} [opaque=false] If true, any children segments will not be created. + * See {@link https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel} * @property {boolean} [internal=false] If true, any children segments from the same library * will not be created. + * @property {boolean} [opaque=false] If true, any children segments will not be created. * @property {string} [prefix='orchestrion:'] String to prepend to diagnostics * channel event names. This provides a namespace for the events we are * injecting into a module. - * @property {boolean} [requireActiveTx=true] If true, the subscriber will only handle events - * when there is an active transaction. * @property {boolean} [propagateContext=false] If true, it will bind `asyncStart` to the store * and re-propagate the active context. It will also attach the `transaction` to the event in * `start.bindStore`. This is used for functions that queue async code and context is lost. - * @property {string} id A unique identifier for the subscriber, combining the prefix, package - * name, and channel name. - * @property {TracingChannel} channel The tracing channel instance this subscriber will be monitoring. - * @property {AsyncLocalStorage} store The async local storage instance used for context management. - * @property {number} [callback=null] Position of callback if it needs to be wrapped for instrumentation. - * -1 means last argument. + * @property {boolean} [requireActiveTx=true] If true, the subscriber will only handle events + * when there is an active transaction. + * @property {object} [targetModuleMeta] Defines the target module's name and + * version string, i.e. is an object `{ name, version }`. This is only necessary + * when target instrumentation can surface an unexpected name for the + * `packageName` property. For example, `express` uses multiple modules to + * compose its core functionality. We want to track things under the `express` + * name, but `packageName` will be set to `router` is most cases. */ class Subscriber { + #usageMetricRecorded = false + /** * @param {SubscriberParams} params the subscriber constructor params */ @@ -234,6 +243,15 @@ class Subscriber { * @returns {Context} The context after processing the event */ const handler = (data) => { + if (this.#usageMetricRecorded === false) { + recordSupportabilityMetric({ + agent: this.agent, + moduleName: this.packageName, + moduleVersion: data.moduleVersion + }) + this.#usageMetricRecorded = true + } + // only wrap the callback if a subscriber has a callback property defined if (this.callback !== null) { this.traceCallback(this.callback, data) diff --git a/lib/subscribers/dc-base.js b/lib/subscribers/dc-base.js index 17108701ef..eaf7107dc9 100644 --- a/lib/subscribers/dc-base.js +++ b/lib/subscribers/dc-base.js @@ -4,8 +4,11 @@ */ 'use strict' + // eslint-disable-next-line n/no-unsupported-features/node-builtins const dc = require('node:diagnostics_channel') +const recordSupportabilityMetric = require('./record-supportability-metric.js') +const resolvePackageVersion = require('./resolve-package-version.js') /** * The baseline parameters available to all subscribers. @@ -15,6 +18,13 @@ const dc = require('node:diagnostics_channel') * @property {object} logger An agent logger instance. * @property {string} packageName The package name being instrumented. * This is what a developer would provide to the `require` function. + * @property {boolean} [skipUsageMetricRecording=false] When set to `true`, the + * instrumentation will not attempt to record the usage metric. This is useful + * when the module being instrumented is also being instrumented via the + * Orchestrion based subscriber system. It is much cheaper to record the metric + * via Orchestrion based subscribers than through this direct diagnostics + * channel method (Orchestrion provides the module version, whereas we have + * to perform expensive operations here to get the same information). */ /** @@ -34,19 +44,25 @@ const dc = require('node:diagnostics_channel') * This is the same string one would pass to the `require` function. */ class Subscriber { + #usageMetricRecorded = false + /** * @param {SubscriberParams} params to function */ - constructor({ agent, logger, packageName }) { + constructor({ agent, logger, packageName, skipUsageMetricRecording = false }) { this.agent = agent this.logger = logger.child({ component: `${packageName}-subscriber` }) this.config = agent.config this.id = packageName + + if (skipUsageMetricRecording === true) { + this.#usageMetricRecorded = true + } } set channels(channels) { if (!Array.isArray(channels)) { - throw new Error('channels must be a collection of with propertiesof channel and hook') + throw new Error('channels must be a collection of objects with properties channel and hook') } this._channels = channels } @@ -73,18 +89,43 @@ class Subscriber { subscribe() { for (let index = 0; index < this.channels.length; index++) { + const chan = this.channels[index] const { hook, channel } = this.channels[index] const boundHook = hook.bind(this) - dc.subscribe(channel, boundHook) - this.channels[index].boundHook = boundHook + chan.boundHook = boundHook + chan.eventHandler = (message, name) => { + this.#supportability() + boundHook(message, name) + } + dc.subscribe(channel, chan.eventHandler) } } unsubscribe() { for (let index = 0; index < this.channels.length; index++) { - const { channel, boundHook } = this.channels[index] - dc.unsubscribe(channel, boundHook) + const { channel, eventHandler } = this.channels[index] + dc.unsubscribe(channel, eventHandler) + } + } + + /** + * Since this class subscribes to diagnostics channels natively published by + * target modules, we do not get the package metadata that Orchestrion + * provides in its channel events. So we have to try and find the package + * manifest and get the version out of it in order to record our + * supportability metric. + */ + #supportability() { + if (this.#usageMetricRecorded === true) { + return } + const version = resolvePackageVersion(this.id) + recordSupportabilityMetric({ + agent: this.agent, + moduleName: this.id, + moduleVersion: version + }) + this.#usageMetricRecorded = true } } diff --git a/lib/subscribers/fastify/index.js b/lib/subscribers/fastify/index.js index 804c675919..8153791fda 100644 --- a/lib/subscribers/fastify/index.js +++ b/lib/subscribers/fastify/index.js @@ -10,7 +10,7 @@ const MiddlewareWrapper = require('../middleware-wrapper') class FastifyInitialization extends DcBase { constructor({ agent, logger }) { - super({ agent, logger, packageName: 'fastify' }) + super({ agent, logger, packageName: 'fastify', skipUsageMetricRecording: true }) this.channels = [ { channel: initChannel, hook: this.handler } ] @@ -24,7 +24,10 @@ class FastifyInitialization extends DcBase { return } - routeOptions.handler = self.wrapper.wrap({ handler: routeOptions.handler, route: routeOptions.path }) + routeOptions.handler = self.wrapper.wrap({ + handler: routeOptions.handler, + route: routeOptions.path + }) }) } } diff --git a/lib/subscribers/record-supportability-metric.js b/lib/subscribers/record-supportability-metric.js new file mode 100644 index 0000000000..4c08e46c4a --- /dev/null +++ b/lib/subscribers/record-supportability-metric.js @@ -0,0 +1,39 @@ +/* + * Copyright 2025 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +module.exports = recordSupportabilityMetric + +const semver = require('semver') +const { + FEATURES: { + INSTRUMENTATION: { SUBSCRIBER_USED } + } +} = require('#agentlib/metrics/names.js') + +function recordSupportabilityMetric({ + agent, + moduleName, + moduleVersion = 'unknown' +} = {}) { + const major = moduleVersion === 'unknown' + ? semver.major(process.version) + : semver.major(moduleVersion) + + let metric = agent.metrics.getOrCreateMetric( + `${SUBSCRIBER_USED}/${moduleName}/${major}` + ) + if (metric.callCount === 0) { + metric.incrementCallCount() + } + + metric = agent.metrics.getOrCreateMetric( + `${SUBSCRIBER_USED}/${moduleName}` + ) + if (metric.callCount === 0) { + metric.incrementCallCount() + } +} diff --git a/lib/subscribers/resolve-package-version.js b/lib/subscribers/resolve-package-version.js new file mode 100644 index 0000000000..acc9c4d263 --- /dev/null +++ b/lib/subscribers/resolve-package-version.js @@ -0,0 +1,84 @@ +/* + * Copyright 2025 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const path = require('node:path') +const defaultLogger = require('#agentlib/logger.js').child({ + component: 'resolve-module-version' +}) + +const dcFuncFrame = /^\s*at Channel\.publish/ +const modPathReg = /at .+ \((.+):\d+:\d+\)/ + +module.exports = resolveModuleVersion + +/** + * Given a module name, attempt to read the version string from its + * associated package manifest. If the module is a built-in, or one that has + * been bundled with Node.js (e.g. `undici`), a package manifest will not be + * available. In this case, the string "unknown" will be returned. + * + * This version resolver assumes that it will be invoked through our + * diagnostics channel subscriber instrumentations. That is, it expects the + * call tree to be similar to: + * + * 1. some-module.function() + * 2. diagnostics_channel.publish() + * 3. subscriber.handler() + * + * @param {string} moduleSpecifier What would be passed to `resolve()`. + * @param {object} [deps] Optional dependencies. + * @param {object} [deps.logger] Agent logger instance. + * + * @returns {string} The version string from the package manifest or "unknown". + */ +function resolveModuleVersion(moduleSpecifier, { logger = defaultLogger } = {}) { + let pkgPath + // We'd prefer to use `require.resolve(moduleSpecifier)` here, but it gets + // a bit confused when there are non-standard module directories in play. + // Once we are able to refactor our "on require" metric recording to + // utilize `module.registerHooks`, we should be able to eliminate this + // slow algorithm. + const err = Error() + const stack = err.stack.split('\n') + do { + stack.shift() + } while (dcFuncFrame.test(stack[0]) === false && stack.length > 0) + const matches = modPathReg.exec(stack[1]) + pkgPath = matches?.[1] + + if (!pkgPath) { + logger.warn( + { moduleSpecifier }, + 'Could not resolve module path. Possibly a built-in or Node.js bundled module.' + ) + return 'unknown' + } + + const cwd = process.cwd() + let reachedCwd = false + let pkg + let base = path.dirname(pkgPath) + do { + try { + pkgPath = path.join(base, 'package.json') + pkg = require(pkgPath) + } catch { + base = path.resolve(path.join(base, '..')) + if (base === cwd) { + reachedCwd = true + } else if (reachedCwd === true) { + // We reached the supposed app root, attempted to load a manifest + // file in that location, and still couldn't find one. So we give up. + pkg = {} + } + } + } while (!pkg) + + const version = pkg.version ?? 'unknown' + logger.trace({ moduleSpecifier, version }, 'Resolved package version.') + return version +} diff --git a/lib/tracking-packages.js b/lib/tracking-packages.js index e441247677..aace6639b2 100644 --- a/lib/tracking-packages.js +++ b/lib/tracking-packages.js @@ -19,15 +19,7 @@ const trackingPkgs = [ 'fancy-log', 'knex', 'loglevel', - 'npmlog', - // These packages are required for the tracking metrics - // for @langchain/core to be created. Ideally, these - // should not be here. - // TODO: will be addressed in https://github.com/newrelic/node-newrelic/issues/3575 - '@langchain/core/prompts', - '@langchain/core/tools', - '@langchain/core/runnables', - '@langchain/core/vectorstores', + 'npmlog' ] module.exports = trackingPkgs diff --git a/test/lib/agent_helper.js b/test/lib/agent_helper.js index 9826e00cb5..287891c8b6 100644 --- a/test/lib/agent_helper.js +++ b/test/lib/agent_helper.js @@ -436,6 +436,19 @@ helper.makeGetRequest = (url, options, callback) => { */ helper.makeGetRequestAsync = util.promisify(helper.makeGetRequest) +helper.asyncHttpCall = function (url, options) { + return new Promise((resolve, reject) => { + helper.makeRequest(url, options || {}, callback) + + function callback (error, incomingMessage, body) { + if (error) { + return reject(error) + } + resolve({ response: incomingMessage, body }) + } + }) +} + helper.makeRequest = (url, options, callback) => { if (!options || typeof options === 'function') { callback = options diff --git a/test/lib/custom-assertions/assert-pkg-tracking-metrics.js b/test/lib/custom-assertions/assert-pkg-tracking-metrics.js index 86f4fe237b..7050f76bc8 100644 --- a/test/lib/custom-assertions/assert-pkg-tracking-metrics.js +++ b/test/lib/custom-assertions/assert-pkg-tracking-metrics.js @@ -4,9 +4,18 @@ */ 'use strict' -const NAMES = require('#agentlib/metrics/names.js') -const assertMetrics = require('./assert-metrics') + const semver = require('semver') +const assertMetrics = require('./assert-metrics') + +const { + FEATURES: { + INSTRUMENTATION: { + SUBSCRIBER_USED, + ON_REQUIRE + } + } +} = require('#agentlib/metrics/names.js') /** * assertion to verify tracking metrics for a given @@ -16,20 +25,46 @@ const semver = require('semver') * @param {string} params.pkg name of package * @param {string} params.version version of package * @param {Agent} params.agent agent instance + * @param {boolean} params.subscriberType When true, the metrics are expected + * to have been generated from a subscriber based instrumentation. Otherwise, + * the metrics are expected to be generated from a shimmer based + * instrumentation. * @param {object} [deps] Injected dependencies. * @param {object} [deps.assert] Assertion library to use. */ module.exports = function assertPackageMetrics( - { pkg, version, agent }, + { pkg, version, agent, subscriberType = false }, { assert = require('node:assert') } = {} ) { - const metrics = [ - [{ name: `${NAMES.FEATURES.INSTRUMENTATION.ON_REQUIRE}/${pkg}` }] - ] + const metrics = [] + const prefix = subscriberType === true + ? `${SUBSCRIBER_USED}/${pkg}` + : `${ON_REQUIRE}/${pkg}` + metrics.push([{ name: prefix }]) if (version) { - metrics.push([{ name: `${NAMES.FEATURES.INSTRUMENTATION.ON_REQUIRE}/${pkg}/Version/${semver.major(version)}` }]) + const major = semver.major(version) + const suffix = subscriberType === true + ? `/${major}` + : `/Version/${major}` + metrics.push([{ name: `${prefix}${suffix}` }]) } - assertMetrics(agent.metrics, metrics, false, false, { assert }) + try { + assertMetrics(agent.metrics, metrics, false, false, { assert }) + } catch { + const expected = metrics.flat().map((m) => m.name) + const foundMetrics = Object.keys(agent.metrics._metrics.unscoped).filter( + (k) => k.toLowerCase().startsWith('supportability') + ) + const msg = '\nExpected supportability metrics:\n' + + JSON.stringify(expected, null, 2) + + '\nBut only present supportability metrics:\n' + + JSON.stringify(foundMetrics, null, 2) + if (typeof assert.fail === 'function') { + assert.fail(msg) + } else { + throw Error(msg) + } + } } diff --git a/test/unit/lib/subscribers/base.test.js b/test/unit/lib/subscribers/base.test.js index e7f665a995..e358f270b3 100644 --- a/test/unit/lib/subscribers/base.test.js +++ b/test/unit/lib/subscribers/base.test.js @@ -182,6 +182,38 @@ test('should subscribe/unsubscribe to specific events on channel', (t) => { assert.equal(subscriber.subscriptions, null) }) +test('handler should record supportability metrics on first invocation', async (t) => { + const plan = tspl(t, { plan: 5 }) + const { agent, subscriber } = t.nr + const name = 'test-segment' + const metricNameBase = 'Supportability/Features/Instrumentation/SubscriberUsed/test-package' + subscriber.enable() + + const handler = subscriber.handler + let invocations = 0 + subscriber.handler = (data, ctx) => { + invocations += 1 + return handler.call(subscriber, data, ctx) + } + + helper.runInTransaction(agent, () => { + const event = { name, moduleVersion: '1.0.0' } + subscriber.channel.start.runStores(event, () => { + const metrics = agent.metrics._metrics.unscoped + plan.equal(metrics[metricNameBase].callCount, 1) + plan.equal(metrics[`${metricNameBase}/1`].callCount, 1) + + subscriber.channel.start.runStores(event, () => { + plan.equal(metrics[metricNameBase].callCount, 1) + plan.equal(metrics[`${metricNameBase}/1`].callCount, 1) + plan.equal(invocations, 2) + }) + }) + }) + + await plan.completed +}) + test('should call handler in start if transaction is active and create a new segment', async (t) => { const plan = tspl(t, { plan: 4 }) const { agent, subscriber } = t.nr diff --git a/test/unit/lib/subscribers/dc-base.test.js b/test/unit/lib/subscribers/dc-base.test.js new file mode 100644 index 0000000000..b52ce9ebfc --- /dev/null +++ b/test/unit/lib/subscribers/dc-base.test.js @@ -0,0 +1,68 @@ +/* + * Copyright 2025 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const dc = require('node:diagnostics_channel') + +const loggerMock = require('../../mocks/logger') +const helper = require('#testlib/agent_helper.js') +const Subscriber = require('#agentlib/subscribers/dc-base.js') + +const PROCESS_MAJOR = require('../../../../package.json').version.split('.')[0] + +test.beforeEach((ctx) => { + const agent = helper.loadMockedAgent() + const logger = loggerMock() + const subscriber = new Subscriber({ + agent, + logger, + packageName: 'test-package' + }) + ctx.nr = { agent, subscriber } +}) + +test.afterEach((ctx) => { + const { subscriber } = ctx.nr + subscriber.disable() + subscriber.unsubscribe() + helper.unloadAgent(ctx.nr.agent) +}) + +test('records supportability metric on first usage', (t) => { + t.plan(5) + const { agent, subscriber } = t.nr + + let invocations = 0 + const metricNameBase = 'Supportability/Features/Instrumentation/SubscriberUsed/test-package' + const chan = dc.channel('test.channel') + subscriber.channels = [ + { channel: 'test.channel', hook: handler } + ] + subscriber.subscribe() + + chan.publish({ foo: 'foo' }) + + function handler () { + invocations += 1 + t.assert.equal(agent.metrics._metrics.unscoped[metricNameBase].callCount, 1) + t.assert.equal( + agent.metrics._metrics.unscoped[`${metricNameBase}/${PROCESS_MAJOR}`].callCount, + 1 + ) + + if (invocations === 1) { + chan.publish({ bar: 'bar' }) + const cachedChan = subscriber.channels[0] + const keys = Object.keys(cachedChan).sort() + t.assert.deepStrictEqual( + keys, + ['boundHook', 'channel', 'eventHandler', 'hook'], + 'attaches required properties to cached channel' + ) + } + } +}) diff --git a/test/unit/lib/subscribers/fixtures/app_root.js b/test/unit/lib/subscribers/fixtures/app_root.js new file mode 100644 index 0000000000..0b8d9e2a0c --- /dev/null +++ b/test/unit/lib/subscribers/fixtures/app_root.js @@ -0,0 +1,20 @@ +/* + * Copyright 2026 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const dc = require('node:diagnostics_channel') +const resolvePackageVersion = require('../../../../../lib/subscribers/resolve-package-version') +const Baz = require('./baz/index.js') + +dc.subscribe('baz.test', handler) +const baz = new Baz() +baz.baz() + +function handler() { + const version = resolvePackageVersion('baz') + console.log(version) + process.exit(0) +} diff --git a/test/unit/lib/subscribers/fixtures/baz/index.js b/test/unit/lib/subscribers/fixtures/baz/index.js new file mode 100644 index 0000000000..0c46807776 --- /dev/null +++ b/test/unit/lib/subscribers/fixtures/baz/index.js @@ -0,0 +1,15 @@ +/* + * Copyright 2026 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const dc = require('node:diagnostics_channel') +const chan = dc.channel('baz.test') + +module.exports = class Foo { + baz() { + chan.publish({ hello: 'world' }) + } +} diff --git a/test/unit/lib/subscribers/fixtures/foo/index.js b/test/unit/lib/subscribers/fixtures/foo/index.js new file mode 100644 index 0000000000..a9c66f217e --- /dev/null +++ b/test/unit/lib/subscribers/fixtures/foo/index.js @@ -0,0 +1,15 @@ +/* + * Copyright 2026 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const dc = require('node:diagnostics_channel') +const chan = dc.channel('foo.test') + +module.exports = class Foo { + foo() { + chan.publish({ hello: 'world' }) + } +} diff --git a/test/unit/lib/subscribers/fixtures/foo/lib/bar.js b/test/unit/lib/subscribers/fixtures/foo/lib/bar.js new file mode 100644 index 0000000000..bc0f2c9ac3 --- /dev/null +++ b/test/unit/lib/subscribers/fixtures/foo/lib/bar.js @@ -0,0 +1,15 @@ +/* + * Copyright 2026 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const dc = require('node:diagnostics_channel') +const chan = dc.channel('bar.test') + +module.exports = class Bar { + bar() { + chan.publish({ hello: 'world' }) + } +} diff --git a/test/unit/lib/subscribers/fixtures/foo/package.json b/test/unit/lib/subscribers/fixtures/foo/package.json new file mode 100644 index 0000000000..1587a66968 --- /dev/null +++ b/test/unit/lib/subscribers/fixtures/foo/package.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} diff --git a/test/unit/lib/subscribers/resolve-package-version.test.js b/test/unit/lib/subscribers/resolve-package-version.test.js new file mode 100644 index 0000000000..e0bc671443 --- /dev/null +++ b/test/unit/lib/subscribers/resolve-package-version.test.js @@ -0,0 +1,128 @@ +/* + * Copyright 2025 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const dc = require('node:diagnostics_channel') +const path = require('node:path') +const { spawnSync } = require('node:child_process') +const resolvePackageVersion = require('#agentlib/subscribers/resolve-package-version.js') + +const nrPkg = require('../../../../package.json') +const Foo = require('./fixtures/foo/index.js') +const Bar = require('./fixtures/foo/lib/bar.js') +const Baz = require('./fixtures/baz/index.js') + +test.beforeEach((ctx) => { + ctx.nr = { + logs: { + warn: [], + trace: [] + } + } + ctx.nr.logger = { + warn(...args) { + ctx.nr.logs.warn.push(args) + }, + trace(...args) { + ctx.nr.logs.trace.push(args) + } + } +}) + +test('logs if module cannot be found', (t) => { + const result = resolvePackageVersion('foo', t.nr) + t.assert.equal(result, 'unknown') + t.assert.deepStrictEqual(t.nr.logs.warn, [ + [ + { moduleSpecifier: 'foo' }, + 'Could not resolve module path. Possibly a built-in or Node.js bundled module.' + ] + ]) +}) + +test('iterates up the tree to the package.json', (t) => { + t.plan(2) + + t.after(() => { + dc.unsubscribe('bar.test', handler) + }) + + dc.subscribe('bar.test', handler) + const bar = new Bar() + bar.bar() + + function handler() { + const result = resolvePackageVersion('foo', t.nr) + t.assert.equal(result, '1.0.0') + t.assert.deepStrictEqual(t.nr.logs.trace, [ + [ + { moduleSpecifier: 'foo', version: '1.0.0' }, + 'Resolved package version.' + ] + ]) + } +}) + +test('stops looking after reaching app root', (t) => { + t.plan(2) + + t.after(() => { + dc.unsubscribe('baz.test', handler) + }) + + dc.subscribe('baz.test', handler) + const baz = new Baz() + baz.baz() + + function handler() { + const result = resolvePackageVersion('baz', t.nr) + t.assert.equal(result, nrPkg.version) + t.assert.deepStrictEqual(t.nr.logs.trace, [ + [ + { moduleSpecifier: 'baz', version: nrPkg.version }, + 'Resolved package version.' + ] + ]) + } +}) + +test('returns unknown if app root does not have manifest', (t) => { + const result = spawnSync( + process.execPath, + [ + './app_root.js' + ], + { + cwd: path.join(__dirname, 'fixtures') + } + ) + t.assert.equal(result.stdout.toString(), 'unknown\n') +}) + +test('returns version', (t) => { + t.plan(2) + + t.after(() => { + dc.unsubscribe('foo.test', handler) + }) + + dc.subscribe('foo.test', handler) + const foo = new Foo() + foo.foo() + + // eslint-disable-next-line sonarjs/no-identical-functions + function handler() { + const result = resolvePackageVersion('foo', t.nr) + t.assert.equal(result, '1.0.0') + t.assert.deepStrictEqual(t.nr.logs.trace, [ + [ + { moduleSpecifier: 'foo', version: '1.0.0' }, + 'Resolved package version.' + ] + ]) + } +}) diff --git a/test/unit/lib/subscribers/undici.test.js b/test/unit/lib/subscribers/undici.test.js index d29f7015d7..8bde5b41f8 100644 --- a/test/unit/lib/subscribers/undici.test.js +++ b/test/unit/lib/subscribers/undici.test.js @@ -72,7 +72,7 @@ test('undici instrumentation', async function (t) { segment.start() agent.tracer.setSegment({ segment, transaction: tx }) channels.create.publish({ request: { origin: HOST, path: '/foo' } }) - assert.ok(loggerMock.trace.callCount, 1) + assert.equal(loggerMock.trace.callCount, 1) assert.deepEqual(loggerMock.trace.args[0], [ 'Not capturing data for outbound request (%s) because parent segment opaque (%s)', '/foo', diff --git a/test/versioned/amqplib/callback.test.js b/test/versioned/amqplib/callback.test.js index 2f8f904e53..f91e01b476 100644 --- a/test/versioned/amqplib/callback.test.js +++ b/test/versioned/amqplib/callback.test.js @@ -62,7 +62,7 @@ test('amqplib callback instrumentation', async function (t) { await t.test('should log tracking metrics', function(t) { const { agent } = t.nr - assertPackageMetrics({ agent, pkg: 'amqplib', version }) + assertPackageMetrics({ agent, pkg: 'amqplib', version, subscriberType: true }) }) await t.test('connect in a transaction', function (t, end) { diff --git a/test/versioned/amqplib/promises.test.js b/test/versioned/amqplib/promises.test.js index d4c9f2f6f6..059ebcc407 100644 --- a/test/versioned/amqplib/promises.test.js +++ b/test/versioned/amqplib/promises.test.js @@ -55,7 +55,7 @@ test('amqplib promise instrumentation', async function (t) { await t.test('should log tracking metrics', function(t) { const { agent } = t.nr - assertPackageMetrics({ agent, pkg: 'amqplib', version }) + assertPackageMetrics({ agent, pkg: 'amqplib', version, subscriberType: true }) }) await t.test('connect in a transaction', async function (t) { diff --git a/test/versioned/bunyan/bunyan.test.js b/test/versioned/bunyan/bunyan.test.js index c78620d3d0..48fc49ef45 100644 --- a/test/versioned/bunyan/bunyan.test.js +++ b/test/versioned/bunyan/bunyan.test.js @@ -51,9 +51,10 @@ test('logging enabled/disabled', async (t) => { await t.test('should log tracking metrics', function(t) { setup(t.nr, { application_logging: { enabled: true } }) - const { agent } = t.nr + const { agent, bunyan } = t.nr const { version } = require('bunyan/package.json') - assertPackageMetrics({ agent, pkg: 'bunyan', version }) + bunyan.createLogger({ name: 'test-logger' }) + assertPackageMetrics({ agent, pkg: 'bunyan', version, subscriberType: true }) }) await t.test('logging enabled', (t) => { diff --git a/test/versioned/elastic/elasticsearch.test.js b/test/versioned/elastic/elasticsearch.test.js index 9bcda6b061..68100c2a7b 100644 --- a/test/versioned/elastic/elasticsearch.test.js +++ b/test/versioned/elastic/elasticsearch.test.js @@ -406,8 +406,17 @@ test('Elasticsearch instrumentation', async (t) => { } expected['Datastore/instance/ElasticSearch/' + HOST_ID] = 5 checkMetrics(unscoped, expected) - const pkgName = '@elastic/elasticsearch' - assertPackageMetrics({ agent, pkg: pkgName, version: pkgVersion }) + + let version = pkgVersion + let pkgName + const major = Number(pkgVersion.split('.', 1)) + if (major >= 8) { + pkgName = '@elastic/transport' + version = helper.readPackageVersion(__dirname, pkgName) + } else { + pkgName = '@elastic/elasticsearch' + } + assertPackageMetrics({ agent, pkg: pkgName, version, subscriberType: true }) }) }) diff --git a/test/versioned/express/app-use.test.js b/test/versioned/express/app-use.test.js index 0fc858c829..a431d8892f 100644 --- a/test/versioned/express/app-use.test.js +++ b/test/versioned/express/app-use.test.js @@ -37,9 +37,8 @@ test('app should be at top of stack when mounted', { skip: isExpress5() }, async test('app should be at top of stack when mounted', async function (t) { const agent = helper.instrumentMockedAgent() + let version const express = require('express') - const { version } = require('express/package.json') - assertPackageMetrics({ agent, pkg: 'express', version }) const main = express() const app = express() const app2 = express() @@ -47,6 +46,14 @@ test('app should be at top of stack when mounted', async function (t) { const router2 = new express.Router() const server = http.createServer(main) + if (isExpress5() === true) { + const pkg = require('router/package.json') + version = pkg.version + } else { + const pkg = require('express/package.json') + version = pkg.version + } + t.after(function () { helper.unloadAgent(agent) server.close() @@ -67,6 +74,12 @@ test('app should be at top of stack when mounted', async function (t) { // store finished transactions const finishedTransactions = {} agent.on('transactionFinished', function (tx) { + assertPackageMetrics({ + agent, + pkg: isExpress5() === true ? 'router' : 'express', + version, + subscriberType: true + }) finishedTransactions[tx.id] = tx }) diff --git a/test/versioned/express/ignoring.test.js b/test/versioned/express/ignoring.test.js index 2402e8bd97..ad326bcf4e 100644 --- a/test/versioned/express/ignoring.test.js +++ b/test/versioned/express/ignoring.test.js @@ -8,45 +8,41 @@ const test = require('node:test') const helper = require('../../lib/agent_helper') const API = require('../../../api') -const tsplan = require('@matteo.collina/tspl') +const promiseResolvers = require('../../lib/promise-resolvers') const { setup, teardown } = require('./utils') -test.beforeEach(async (ctx) => { - await setup(ctx) -}) - -test.afterEach(teardown) - test('ignoring an Express route', async function (t) { - const { agent, app, port, isExpress5 } = t.nr - const plan = tsplan(t, { plan: 8 }) + t.plan(8) + + await setup(t) + t.after(() => { teardown(t) }) + const { agent, app, port } = t.nr + const { promise, resolve } = promiseResolvers() const api = new API(agent) agent.on('transactionFinished', function (transaction) { - plan.equal( + t.assert.equal( transaction.name, 'WebTransaction/Expressjs/GET//polling/:id', 'transaction has expected name even on error' ) - plan.ok(transaction.ignore, 'transaction is ignored') + t.assert.ok(transaction.ignore, 'transaction is ignored') - plan.ok(!agent.traces.trace, 'should have no transaction trace') + t.assert.ok(!agent.traces.trace, 'should have no transaction trace') - const metrics = agent.metrics._metrics.unscoped - // loading k2 adds instrumentation metrics for things it loads - let expectedMetrics = isExpress5 ? 8 : 6 - if (helper.isSecurityAgentEnabled(agent) === true) { - expectedMetrics = isExpress5 ? 19 : 17 - } - plan.equal( - Object.keys(metrics).length, - expectedMetrics, + const metrics = Object.keys(agent.metrics._metrics.unscoped).filter( + (k) => k.startsWith('Supportability/') === false + ) + t.assert.equal( + metrics.length, + 0, 'only supportability metrics added to agent collection' ) + const errors = agent.errors.traceAggregator.errors - plan.equal(errors.length, 0, 'no errors noticed') + t.assert.equal(errors.length, 0, 'no errors noticed') }) app.get('/polling/:id', function (req, res) { @@ -57,9 +53,14 @@ test('ignoring an Express route', async function (t) { const url = 'http://localhost:' + port + '/polling/31337' helper.makeGetRequest(url, function (error, res, body) { - plan.ifError(error) - plan.equal(res.statusCode, 400, 'got expected error') - plan.deepEqual(body, { status: 'pollpollpoll' }, 'got expected response') + t.assert.ifError(error) + t.assert.equal(res.statusCode, 400, 'got expected error') + t.assert.deepEqual(body, { status: 'pollpollpoll' }, 'got expected response') + + // The request finished callback is invoked after the transactionFinished + // callback. So we must trigger the end of the test here. + resolve() }) - await plan.completed + + await promise }) diff --git a/test/versioned/fastify/package.json b/test/versioned/fastify/package.json index 3a77e8da8c..bb4844c736 100644 --- a/test/versioned/fastify/package.json +++ b/test/versioned/fastify/package.json @@ -61,6 +61,14 @@ "errors.test.js", "new-state-tracking.test.js" ] + }, + + { + "engines": { "node": ">=20" }, + "dependencies": { + "fastify": ">=5.0.0" + }, + "files": [ "usage-metric.test.js" ] } ] } diff --git a/test/versioned/fastify/usage-metric.test.js b/test/versioned/fastify/usage-metric.test.js new file mode 100644 index 0000000000..03f076b4cd --- /dev/null +++ b/test/versioned/fastify/usage-metric.test.js @@ -0,0 +1,59 @@ +/* + * Copyright 2026 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const helper = require('../../lib/agent_helper') + +test('only records one usage metric', async (t) => { + t.plan(4) + + const agent = helper.instrumentMockedAgent() + const fastify = require('fastify') + const pkg = require('fastify/package.json') + const version = Number(pkg.version.split('.', 1)) + const keyBase = 'Supportability/Features/Instrumentation/SubscriberUsed/fastify' + const server = fastify({ logger: false }) + + t.after(() => { + helper.unloadAgent(agent) + server.close() + }) + + server.decorate('test', { + getter() { + return this._test + }, + setter(value) { + this._test = value + } + }) + + server.addHook('preHandler', function(req, res, done) { + this.test = true + done() + }) + + server.route({ + path: '/', + method: 'get', + handler (req, res) { + t.assert.equal(this.test, true) + res.send('ok') + } + }) + + const address = await server.listen({ port: 0 }) + + agent.on('transactionFinished', () => { + const metrics = agent.metrics._metrics.unscoped + t.assert.equal(metrics[keyBase].callCount, 1) + t.assert.equal(metrics[`${keyBase}/${version}`].callCount, 1) + }) + + const { body } = await helper.asyncHttpCall(address) + t.assert.equal(body, 'ok') +}) diff --git a/test/versioned/google-genai/chat-completions.test.js b/test/versioned/google-genai/chat-completions.test.js index 94151b0b66..edb496000a 100644 --- a/test/versioned/google-genai/chat-completions.test.js +++ b/test/versioned/google-genai/chat-completions.test.js @@ -53,9 +53,21 @@ test.afterEach((ctx) => { removeModules('@google/genai') }) -test('should log tracking metrics', function(t) { - const { agent } = t.nr - assertPackageMetrics({ agent, pkg: '@google/genai', version: pkgVersion }) +test('should log tracking metrics', function(t, end) { + t.plan(5) + const { agent, client } = t.nr + helper.runInTransaction(agent, async () => { + const model = 'gemini-2.0-flash' + await client.models.generateContent({ + model, + contents: 'You are a mathematician.' + }) + assertPackageMetrics( + { agent, pkg: '@google/genai', version: pkgVersion, subscriberType: true }, + { assert: t.assert } + ) + end() + }) }) test('should create span on successful models generateContent', (t, end) => { diff --git a/test/versioned/ioredis/ioredis.test.js b/test/versioned/ioredis/ioredis.test.js index 53d2773486..a48a68958f 100644 --- a/test/versioned/ioredis/ioredis.test.js +++ b/test/versioned/ioredis/ioredis.test.js @@ -63,7 +63,7 @@ test('ioredis instrumentation', async (t) => { expected['Datastore/instance/Redis/' + HOST_ID] = 2 assertMetrics(tx.metrics, expected, false, false, { assert: plan }) - assertPackageMetrics({ agent, pkg: 'ioredis', version: pkgVersion }) + assertPackageMetrics({ agent, pkg: 'ioredis', version: pkgVersion, subscriberType: true }) }) helper.runInTransaction(agent, async (transaction) => { diff --git a/test/versioned/iovalkey/iovalkey.test.js b/test/versioned/iovalkey/iovalkey.test.js index ab260a5990..57eacf7c07 100644 --- a/test/versioned/iovalkey/iovalkey.test.js +++ b/test/versioned/iovalkey/iovalkey.test.js @@ -63,7 +63,12 @@ test('iovalkey instrumentation', async (t) => { expected['Datastore/instance/Valkey/' + HOST_ID] = 2 assertMetrics(tx.metrics, expected, false, false, { assert: plan }) - assertPackageMetrics({ agent, pkg: 'iovalkey', version: pkgVersion }) + assertPackageMetrics({ + agent, + pkg: 'iovalkey', + version: pkgVersion, + subscriberType: true + }) }) helper.runInTransaction(agent, async (transaction) => { diff --git a/test/versioned/langchain/runnables-streaming.js b/test/versioned/langchain/runnables-streaming.js index 50a7d1e353..e355bac025 100644 --- a/test/versioned/langchain/runnables-streaming.js +++ b/test/versioned/langchain/runnables-streaming.js @@ -49,9 +49,19 @@ function runStreamingEnabledTests(config) { } = config return async (t) => { - await t.test('should log tracking metrics', function(t) { - const { agent, langchainCoreVersion } = t.nr - assertPackageMetrics({ agent, pkg: '@langchain/core', version: langchainCoreVersion }) + await t.test('should log tracking metrics', function(t, end) { + t.plan(5) + const { agent, langchainCoreVersion, prompt, model } = t.nr + helper.runInTransaction(agent, async () => { + await prompt.pipe(model).stream(inputData) + assertPackageMetrics({ + agent, + pkg: '@langchain/core', + version: langchainCoreVersion, + subscriberType: true + }, { assert: t.assert }) + end() + }) }) await t.test('should create langchain events for every stream call', (t, end) => { diff --git a/test/versioned/langchain/runnables.js b/test/versioned/langchain/runnables.js index 274b601e41..d5b7cb2507 100644 --- a/test/versioned/langchain/runnables.js +++ b/test/versioned/langchain/runnables.js @@ -41,9 +41,19 @@ function runRunnablesTests(config) { arrayParserOutput } = config - test('should log tracking metrics', function(t) { - const { agent, langchainCoreVersion } = t.nr - assertPackageMetrics({ agent, pkg: '@langchain/core', version: langchainCoreVersion }) + test('should log tracking metrics', function(t, end) { + t.plan(5) + const { agent, langchainCoreVersion, model, prompt } = t.nr + helper.runInTransaction(agent, async () => { + await prompt.pipe(model).invoke(inputData) + assertPackageMetrics({ + agent, + pkg: '@langchain/core', + version: langchainCoreVersion, + subscriberType: true + }, { assert: t.assert }) + end() + }) }) test('should create langchain events for every invoke call', (t, end) => { diff --git a/test/versioned/langchain/tools.test.js b/test/versioned/langchain/tools.test.js index 62b6d7b265..6106b5ac3e 100644 --- a/test/versioned/langchain/tools.test.js +++ b/test/versioned/langchain/tools.test.js @@ -39,10 +39,20 @@ test.afterEach((ctx) => { removeMatchedModules(/custom-tool\.js$/) }) -test('should log tracking metrics', function(t) { - const { agent } = t.nr +test('should log tracking metrics', function(t, end) { + t.plan(5) + const { agent, tool, input } = t.nr const { version } = require('@langchain/core/package.json') - assertPackageMetrics({ agent, pkg: '@langchain/core', version }) + helper.runInTransaction(agent, async () => { + await tool.call(input) + assertPackageMetrics({ + agent, + pkg: '@langchain/core', + version, + subscriberType: true + }, { assert: t.assert }) + end() + }) }) test('should create span on successful tools create', (t, end) => { diff --git a/test/versioned/langchain/vectorstore.js b/test/versioned/langchain/vectorstore.js index fd9f744af4..3d58468d6e 100644 --- a/test/versioned/langchain/vectorstore.js +++ b/test/versioned/langchain/vectorstore.js @@ -36,9 +36,19 @@ function runVectorstoreTests(config) { errorAssertion } = config - test('should log tracking metrics', function(t) { - const { agent, langchainCoreVersion } = t.nr - assertPackageMetrics({ agent, pkg: '@langchain/core', version: langchainCoreVersion }) + test('should log tracking metrics', function(t, end) { + t.plan(5) + const { agent, langchainCoreVersion, vs } = t.nr + helper.runInTransaction(agent, async () => { + await vs.similaritySearch(searchQuery, 1) + assertPackageMetrics({ + agent, + pkg: '@langchain/core', + version: langchainCoreVersion, + subscriberType: true + }, { assert: t.assert }) + end() + }) }) test('should create vectorstore events for every similarity search call', (t, end) => { diff --git a/test/versioned/mcp-sdk/client-stdio.test.js b/test/versioned/mcp-sdk/client-stdio.test.js index e17a2c43a3..e673d852e3 100644 --- a/test/versioned/mcp-sdk/client-stdio.test.js +++ b/test/versioned/mcp-sdk/client-stdio.test.js @@ -7,6 +7,7 @@ const test = require('node:test') const assert = require('node:assert') +const path = require('node:path') const { removeModules } = require('../../lib/cache-buster') const helper = require('../../lib/agent_helper') @@ -27,7 +28,7 @@ test.beforeEach(async (ctx) => { const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js') ctx.nr.transport = new StdioClientTransport({ command: 'node', - args: ['stdio-server.js'] + args: [path.join(__dirname, 'stdio-server.js')] }) ctx.nr.client = new Client( { @@ -45,10 +46,21 @@ test.afterEach(async (ctx) => { removeModules(['@modelcontextprotocol/sdk/client/index.js', '@modelcontextprotocol/sdk/client/stdio.js']) }) -test('should log tracking metrics', function(t) { - const { agent } = t.nr +test('should log tracking metrics', function(t, end) { + t.plan(5) + const { agent, client } = t.nr const pkgVersion = helper.readPackageVersion(__dirname, '@modelcontextprotocol/sdk') - assertPackageMetrics({ agent, pkg: '@modelcontextprotocol/sdk', version: pkgVersion }) + helper.runInTransaction(agent, async () => { + await client.callTool({ + name: 'echo', + arguments: { message: 'example message' } + }) + assertPackageMetrics( + { agent, pkg: '@modelcontextprotocol/sdk', version: pkgVersion, subscriberType: true }, + { assert: t.assert } + ) + end() + }) }) test('should create span for callTool', async (t) => { diff --git a/test/versioned/mcp-sdk/client-streaming.test.js b/test/versioned/mcp-sdk/client-streaming.test.js index 1a51a6b65a..4239c5f8d5 100644 --- a/test/versioned/mcp-sdk/client-streaming.test.js +++ b/test/versioned/mcp-sdk/client-streaming.test.js @@ -57,7 +57,12 @@ test.afterEach(async (ctx) => { test('should log package tracking metrics', (t) => { const { agent } = t.nr const version = helper.readPackageVersion(__dirname, '@modelcontextprotocol/sdk') - assertPackageMetrics({ agent, pkg: '@modelcontextprotocol/sdk', version }) + assertPackageMetrics({ + agent, + pkg: '@modelcontextprotocol/sdk', + version, + subscriberType: true + }) }) test('should create span for callTool', (t, end) => { diff --git a/test/versioned/mysql/basic.js b/test/versioned/mysql/basic.js index 4b3acd2e70..59b464ad5c 100644 --- a/test/versioned/mysql/basic.js +++ b/test/versioned/mysql/basic.js @@ -55,7 +55,7 @@ module.exports = function ({ lib, factory, poolFactory, constants, version }) { await t.test('should log tracking metrics', function(t) { const { agent } = t.nr - assertPackageMetrics({ agent, pkg: lib, version }) + assertPackageMetrics({ agent, pkg: lib, version, subscriberType: true }) }) await t.test('basic transaction', function testTransaction(t, end) { diff --git a/test/versioned/nestjs/nest.test.js b/test/versioned/nestjs/nest.test.js index b823fbedab..243edb4d1f 100644 --- a/test/versioned/nestjs/nest.test.js +++ b/test/versioned/nestjs/nest.test.js @@ -14,12 +14,14 @@ const { initNestApp, deleteNestApp } = require('./setup') const { assertPackageMetrics } = require('../../lib/custom-assertions') test('Nest.js', async (t) => { - const port = 8972 // chosen by rand(), guaranteed to be random - const baseUrl = `http://localhost:${port}` await initNestApp() const agent = helper.instrumentMockedAgent() const { bootstrap } = require('./test-app/dist/main.js') - const app = await bootstrap(port) + const app = await bootstrap(0) + const address = app.httpServer.address() + const baseUrl = address.family === 'IPv6' + ? `http://[${address.address}]:${address.port}` + : `http://${address.address}:${address.port}` t.after(async () => { app.close() @@ -28,7 +30,7 @@ test('Nest.js', async (t) => { await deleteNestApp() }) - await t.test('should log tracking metrics', function() { + await t.test('should log tracking metrics', async function() { // eslint-disable-next-line sonarjs/no-internal-api-use const { version } = require('./test-app/node_modules/@nestjs/core/package.json') assertPackageMetrics({ agent, pkg: '@nestjs/core', version }) diff --git a/test/versioned/nestjs/setup.js b/test/versioned/nestjs/setup.js index dcb7c18ef6..b71410dc20 100644 --- a/test/versioned/nestjs/setup.js +++ b/test/versioned/nestjs/setup.js @@ -23,7 +23,10 @@ async function initNestApp() { await deleteNestApp() } // skip install of deps because we will install them later to force peer dep failures on nest 10.x - await exec('npx nest new --package-manager npm --skip-git --skip-install test-app') + await exec( + 'npx nest new --package-manager npm --skip-git --skip-install test-app', + { cwd: __dirname } + ) // on versions of nest 10.x peer dep issues exist, ignore them and force install await exec('npm install --prefix test-app --force', { cwd: __dirname }) // We patch the default Nest app with some of our own functions. diff --git a/test/versioned/opensearch/opensearch.test.js b/test/versioned/opensearch/opensearch.test.js index 83b4b75b8f..e4c39cbaf7 100644 --- a/test/versioned/opensearch/opensearch.test.js +++ b/test/versioned/opensearch/opensearch.test.js @@ -351,7 +351,12 @@ test('opensearch instrumentation', async (t) => { } expected['Datastore/instance/OpenSearch/' + HOST_ID] = 5 checkMetrics(unscoped, expected) - assertPackageMetrics({ agent, pkg: '@opensearch-project/opensearch', version: pkgVersion }) + assertPackageMetrics({ + agent, + pkg: '@opensearch-project/opensearch', + version: pkgVersion, + subscriberType: true + }) }) }) diff --git a/test/versioned/pg/pg.common.js b/test/versioned/pg/pg.common.js index 9b0ea570ef..5506116c88 100644 --- a/test/versioned/pg/pg.common.js +++ b/test/versioned/pg/pg.common.js @@ -237,7 +237,7 @@ module.exports = function runTests(name, clientFactory) { await t.test('should log tracking metrics', function(t) { const { agent } = t.nr const version = helper.readPackageVersion(__dirname, 'pg') - assertPackageMetrics({ agent, pkg: 'pg', version }) + assertPackageMetrics({ agent, pkg: 'pg', version, subscriberType: true }) }) await t.test('simple query with prepared statement', (t, end) => { diff --git a/test/versioned/pino/pino.test.js b/test/versioned/pino/pino.test.js index 43dd56538a..4ef5f9f658 100644 --- a/test/versioned/pino/pino.test.js +++ b/test/versioned/pino/pino.test.js @@ -89,7 +89,7 @@ test('logging enabled', (t) => { logger.info(message) metric = agent.metrics.getMetric(LOGGING.LIBS.PINO) assert.equal(metric.callCount, 1, `should create ${LOGGING.LIBS.PINO} metric`) - assertPackageMetrics({ agent, pkg: 'pino', version: pinoVersion }) + assertPackageMetrics({ agent, pkg: 'pino', version: pinoVersion, subscriberType: true }) }) test('local_decorating', (t, end) => { diff --git a/test/versioned/undici/requests.test.js b/test/versioned/undici/requests.test.js index e4b8d08155..c78ef737a7 100644 --- a/test/versioned/undici/requests.test.js +++ b/test/versioned/undici/requests.test.js @@ -51,9 +51,17 @@ test('should not fail if request not in a transaction', async (t) => { assert.equal(statusCode, 200) }) -test('should create tracking metrics', (t) => { - const { agent, pkgVersion } = t.nr - assertPackageMetrics({ agent, pkg: 'undici', version: pkgVersion }) +test('should create tracking metrics', (t, end) => { + t.plan(5) + const { agent, pkgVersion, undici, REQUEST_URL } = t.nr + helper.runInTransaction(agent, async () => { + await undici.request(REQUEST_URL) + assertPackageMetrics( + { agent, pkg: 'undici', version: pkgVersion, subscriberType: true }, + { assert: t.assert } + ) + end() + }) }) test('should properly name segments', async (t) => {