newrelic 13.0.0 → 13.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/NEWS.md +92 -0
  2. package/README.md +2 -4
  3. package/THIRD_PARTY_NOTICES.md +429 -9
  4. package/esm-loader.mjs +9 -1
  5. package/index.js +38 -3
  6. package/lib/agent.js +8 -2
  7. package/lib/config/build-instrumentation-config.js +3 -0
  8. package/lib/config/default.js +1397 -1383
  9. package/lib/config/index.js +15 -19
  10. package/lib/context-manager/context.js +13 -4
  11. package/lib/harvester.js +11 -13
  12. package/lib/instrumentation/@google/genai.js +8 -3
  13. package/lib/instrumentation/@hapi/hapi.js +4 -6
  14. package/lib/instrumentation/@nestjs/core.js +12 -14
  15. package/lib/instrumentation/aws-sdk/v3/bedrock.js +2 -2
  16. package/lib/instrumentation/core/http-outbound.js +11 -26
  17. package/lib/instrumentation/kafkajs/producer.js +13 -19
  18. package/lib/instrumentation/openai.js +2 -2
  19. package/lib/instrumentation/undici.js +48 -40
  20. package/lib/instrumentations.js +14 -5
  21. package/lib/llm-events/aws-bedrock/utils.js +1 -1
  22. package/lib/metrics/names.js +8 -0
  23. package/lib/metrics/normalizer.js +1 -3
  24. package/lib/otel/context.js +18 -14
  25. package/lib/otel/logs/bootstrap-logs.js +84 -0
  26. package/lib/otel/logs/no-op-exporter.js +25 -0
  27. package/lib/otel/logs/normalize-timestamp.js +59 -0
  28. package/lib/otel/logs/proxying-provider.js +46 -0
  29. package/lib/otel/logs/severity-to-string.js +56 -0
  30. package/lib/otel/metrics/bootstrap-metrics.js +1 -1
  31. package/lib/otel/setup.js +21 -8
  32. package/lib/patch-module.js +70 -0
  33. package/lib/serverless/aws-lambda.js +1 -3
  34. package/lib/shim/webframework-shim/middleware-mounter.js +1 -3
  35. package/lib/shimmer.js +56 -8
  36. package/lib/subscriber-configs.js +17 -0
  37. package/lib/subscribers/application-logs.js +55 -0
  38. package/lib/subscribers/base.js +177 -0
  39. package/lib/subscribers/create-config.js +27 -0
  40. package/lib/subscribers/db-operation.js +21 -0
  41. package/lib/subscribers/db-query.js +54 -0
  42. package/lib/subscribers/db.js +57 -0
  43. package/lib/subscribers/elasticsearch/config.js +49 -0
  44. package/lib/subscribers/elasticsearch/elasticsearch.js +42 -0
  45. package/lib/subscribers/elasticsearch/opensearch.js +15 -0
  46. package/lib/subscribers/elasticsearch/transport.js +14 -0
  47. package/lib/subscribers/ioredis/config.js +37 -0
  48. package/lib/subscribers/ioredis/index.js +48 -0
  49. package/lib/subscribers/mcp-sdk/client-prompt.js +23 -0
  50. package/lib/subscribers/mcp-sdk/client-resource.js +24 -0
  51. package/lib/subscribers/mcp-sdk/client-tool.js +23 -0
  52. package/lib/subscribers/mcp-sdk/client.js +29 -0
  53. package/lib/subscribers/mcp-sdk/config.js +104 -0
  54. package/lib/subscribers/pino/config.js +20 -0
  55. package/lib/subscribers/pino/index.js +102 -0
  56. package/lib/transaction/index.js +4 -1
  57. package/lib/transaction/trace/aggregator.js +7 -9
  58. package/lib/util/camel-case.js +1 -3
  59. package/lib/util/get-package-version.js +34 -0
  60. package/lib/util/urltils.js +19 -13
  61. package/lib/w3c/tracestate.js +3 -3
  62. package/newrelic.js +10 -0
  63. package/package.json +9 -5
  64. package/lib/instrumentation/@elastic/elasticsearch.js +0 -62
  65. package/lib/instrumentation/@opensearch-project/opensearch.js +0 -66
  66. package/lib/instrumentation/ioredis.js +0 -50
  67. package/lib/instrumentation/pino/nr-hooks.js +0 -19
  68. package/lib/instrumentation/pino/pino.js +0 -168
@@ -9,9 +9,13 @@ const FakeSpan = require('./fake-span')
9
9
  const Context = require('../context-manager/context')
10
10
 
11
11
  module.exports = class OtelContext extends Context {
12
- constructor(transaction, segment, parentContext) {
13
- super(transaction, segment)
14
- this._otelCtx = parentContext ? new Map(parentContext) : new Map()
12
+ constructor({ transaction, segment, otelContext, extras } = {}) {
13
+ super({ transaction, segment, extras })
14
+ this._otelCtx = otelContext ? new Map(otelContext) : new Map()
15
+ }
16
+
17
+ get otelCtx() {
18
+ return this._otelCtx
15
19
  }
16
20
 
17
21
  /**
@@ -23,11 +27,11 @@ module.exports = class OtelContext extends Context {
23
27
  * @param {Transaction} params.transaction active transaction
24
28
  * @returns {OtelContext} a newly constructed context
25
29
  */
26
- enterSegment({ segment, transaction = this._transaction }) {
30
+ enterSegment({ segment, transaction = this.transaction }) {
27
31
  if (transaction?.agent?.otelSpanKey && segment) {
28
- this._otelCtx.set(transaction.agent.otelSpanKey, new FakeSpan(segment, transaction))
32
+ this.otelCtx.set(transaction.agent.otelSpanKey, new FakeSpan(segment, transaction))
29
33
  }
30
- return new this.constructor(transaction, segment, this._otelCtx)
34
+ return new this.constructor({ transaction, segment, otelContext: this.otelCtx, extras: this.extras })
31
35
  }
32
36
 
33
37
  /**
@@ -39,9 +43,9 @@ module.exports = class OtelContext extends Context {
39
43
  */
40
44
  enterTransaction(transaction) {
41
45
  if (transaction?.agent?.otelSpanKey) {
42
- this._otelCtx.set(transaction.agent.otelSpanKey, new FakeSpan(transaction.trace.root, transaction))
46
+ this.otelCtx.set(transaction.agent.otelSpanKey, new FakeSpan(transaction.trace.root, transaction))
43
47
  }
44
- return new this.constructor(transaction, transaction?.trace?.root, this._otelCtx)
48
+ return new this.constructor({ transaction, segment: transaction?.trace?.root, otelContext: this.otelCtx, extras: this.extras })
45
49
  }
46
50
 
47
51
  /**
@@ -52,7 +56,7 @@ module.exports = class OtelContext extends Context {
52
56
  * @returns {*} The stored value.
53
57
  */
54
58
  getValue(key) {
55
- return this._otelCtx.get(key)
59
+ return this.otelCtx.get(key)
56
60
  }
57
61
 
58
62
  /**
@@ -69,12 +73,12 @@ module.exports = class OtelContext extends Context {
69
73
  if (value[otelSynthesis] && value[otelSynthesis].segment && value[otelSynthesis].transaction) {
70
74
  const { segment, transaction } = value[otelSynthesis]
71
75
  segment.start()
72
- ctx = new this.constructor(transaction, segment, this._otelCtx)
76
+ ctx = new this.constructor({ transaction, segment, otelContext: this.otelCtx, extras: this.extras })
73
77
  } else {
74
- ctx = new this.constructor(this._transaction, this._segment, this._otelCtx)
78
+ ctx = new this.constructor({ transaction: this.transaction, segment: this.segment, otelContext: this.otelCtx, extras: this.extras })
75
79
  }
76
80
 
77
- ctx._otelCtx.set(key, value)
81
+ ctx.otelCtx.set(key, value)
78
82
  return ctx
79
83
  }
80
84
 
@@ -86,8 +90,8 @@ module.exports = class OtelContext extends Context {
86
90
  * @returns {OtelContext} The context object.
87
91
  */
88
92
  deleteValue(key) {
89
- const ctx = new this.constructor(this._transaction, this._segment, this._otelCtx)
90
- ctx._otelCtx.delete(key)
93
+ const ctx = new this.constructor({ transaction: this._transaction, segment: this._segment, otelContext: this.otelCtx, extras: this.extras })
94
+ ctx.otelCtx.delete(key)
91
95
  return ctx
92
96
  }
93
97
  }
@@ -0,0 +1,84 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ module.exports = bootstrapOtelLogs
9
+
10
+ const logsApi = require('@opentelemetry/api-logs')
11
+ const logsSdk = require('@opentelemetry/sdk-logs')
12
+ const defaultLogger = require('#agentlib/logger.js').child({ component: 'otel-logs' })
13
+ const {
14
+ isApplicationLoggingEnabled,
15
+ isLogForwardingEnabled,
16
+ isMetricsEnabled,
17
+ incrementLoggingLinesMetrics
18
+ } = require('#agentlib/util/application-logging.js')
19
+
20
+ const NewRelicLoggerProvider = require('./proxying-provider.js')
21
+ const NoOpExporter = require('./no-op-exporter.js')
22
+ const normalizeTimestamp = require('./normalize-timestamp.js')
23
+ const severityToString = require('./severity-to-string.js')
24
+
25
+ /**
26
+ * Sets up the OTEL logging system and instruments it in such a fashion
27
+ * that logs are shipped to New Relic when logs forwarding is enabled.
28
+ *
29
+ * The API, as of 2025-07, is in development. We should expect changes that
30
+ * will require significant refactoring here. For example, we might need to
31
+ * accept instances of various components, e.g. record processors, in order
32
+ * for customer apps to work as they expect.
33
+ *
34
+ * @param {object} params Factory function parameters.
35
+ * @param {Agent} params.agent The Node.js agent instance.
36
+ * @param {object} [params.logger] An agent logger instance.
37
+ */
38
+ function bootstrapOtelLogs({ agent, logger = defaultLogger } = {}) {
39
+ if (isApplicationLoggingEnabled(agent.config) === false) {
40
+ logger.info('application logging disabled, skipping otel logs setup')
41
+ return
42
+ }
43
+
44
+ agent.metrics
45
+ .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Logs')
46
+ .incrementCallCount()
47
+
48
+ const exporter = new NoOpExporter()
49
+ const processor = new logsSdk.BatchLogRecordProcessor(exporter)
50
+ const otelProvider = new logsSdk.LoggerProvider({
51
+ processors: [processor]
52
+ })
53
+ const provider = new NewRelicLoggerProvider({
54
+ agent,
55
+ provider: otelProvider,
56
+ emitHandler: nrEmitHandler
57
+ })
58
+ logsApi.logs.setGlobalLoggerProvider(provider)
59
+
60
+ function nrEmitHandler(record) {
61
+ const level = severityToString(record.severityNumber ?? 0)
62
+ if (isMetricsEnabled(agent.config) === true) {
63
+ incrementLoggingLinesMetrics(level, agent.metrics)
64
+ }
65
+
66
+ // TODO: if we decide to support local decorating, implement it here
67
+
68
+ if (isLogForwardingEnabled(agent.config, agent) === true) {
69
+ const meta = agent.getLinkingMetadata(true)
70
+ const timestamp = normalizeTimestamp(record.timestamp)
71
+ const logData = {
72
+ message: record.body,
73
+ level,
74
+ timestamp,
75
+ ...record.attributes,
76
+ ...meta
77
+ }
78
+
79
+ agent.logs.add(logData)
80
+ }
81
+ }
82
+
83
+ agent.emit('otelLogsBootstrapped')
84
+ }
@@ -0,0 +1,25 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const { ExportResultCode } = require('@opentelemetry/core')
9
+
10
+ /**
11
+ * @see https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_sdk-node.logs.LogRecordExporter.html
12
+ */
13
+ class NewRelicNoOpExporter {
14
+ export(_, done) {
15
+ if (typeof done === 'function') {
16
+ done(ExportResultCode.SUCCESS)
17
+ }
18
+ }
19
+
20
+ shutdown() {
21
+ return Promise.resolve()
22
+ }
23
+ }
24
+
25
+ module.exports = NewRelicNoOpExporter
@@ -0,0 +1,59 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ module.exports = normalizeTimestamp
9
+
10
+ const {
11
+ isTimeInputHrTime
12
+ } = require('@opentelemetry/core')
13
+
14
+ /**
15
+ * Returns a normalized epoch in milliseconds based upon the value provided
16
+ * to the logger's `emit` method (if it was provided). The emit method receives
17
+ * a `LogRecord` instance. In the upstream code, a `LogRecord` is supposed to
18
+ * have a nanosecond epoch delta. But we are receiving it from user code. So
19
+ * it is possible for the value to be a variety of different kinds.
20
+ *
21
+ * @see https://github.com/open-telemetry/opentelemetry-js/blob/99dde77/api/src/common/Time.ts#L39
22
+ * @see https://github.com/open-telemetry/opentelemetry-js/blob/99dde77/packages/opentelemetry-core/src/common/time.ts#L61-L83
23
+ *
24
+ * @param {number[]|number|Date|undefined} input The value from the logger.
25
+ *
26
+ * @returns {number}
27
+ */
28
+ function normalizeTimestamp(input) {
29
+ if (typeof input === 'number' && input >= 10_000_000_000_000_000) {
30
+ // Looks like a nanosecond epoch delta.
31
+ return Math.floor(input / 1_000_000)
32
+ }
33
+
34
+ if (typeof input === 'number' && input >= 100_000_000_000_000) {
35
+ // Looks like a microsecond epoch delta.
36
+ return Math.floor(input / 1_000)
37
+ }
38
+
39
+ if (typeof input === 'number' && input >= 100_000_000_000) {
40
+ // Looks like a millisecond epoch delta.
41
+ return input
42
+ }
43
+
44
+ if (isTimeInputHrTime(input) === true) {
45
+ // It's unclear why upstream tries to support a `process.hrtime()` value.
46
+ // Such tuples are relative to an arbitrary point in time, not the Unix
47
+ // epoch. So there's no way to determine an actual wall clock time and date
48
+ // from such input. For lack of a better solution, we'll return the
49
+ // current epoch in this case.
50
+ return Date.now()
51
+ }
52
+
53
+ if (Object.getPrototypeOf(input) === Date.prototype) {
54
+ return input.getTime()
55
+ }
56
+
57
+ // We don't know what they've given us, so just return the current time.
58
+ return Date.now()
59
+ }
@@ -0,0 +1,46 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const symLogPatched = Symbol('nr.log.patched')
9
+
10
+ /**
11
+ * Provides a logger provider that proxies to another logger provider.
12
+ * This allows us to intercept loggers as they are retrieved so that we can
13
+ * patch their `emit` method to do what we need.
14
+ *
15
+ * @see https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api-logs.LoggerProvider.html
16
+ */
17
+ class NewRelicLoggerProvider {
18
+ #provider
19
+ #emitHandler
20
+
21
+ constructor({ provider, emitHandler }) {
22
+ this.#provider = provider
23
+ this.#emitHandler = emitHandler
24
+ }
25
+
26
+ getLogger(name, version, options) {
27
+ const logger = this.#provider.getLogger(name, version, options)
28
+ const emit = logger.emit
29
+ const handler = this.#emitHandler
30
+
31
+ // Logger has already been patched, so just return it.
32
+ if (logger[symLogPatched] === true) {
33
+ return logger
34
+ }
35
+
36
+ logger.emit = function nrEmitWrapper(record) {
37
+ handler(record)
38
+ return emit.apply(this, arguments)
39
+ }
40
+
41
+ logger[symLogPatched] = true
42
+ return logger
43
+ }
44
+ }
45
+
46
+ module.exports = NewRelicLoggerProvider
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ module.exports = severityToString
9
+
10
+ const SEV_UNKNOWN = 'unknown'
11
+ const SEV_TRACE = 'trace'
12
+ const SEV_DEBUG = 'debug'
13
+ const SEV_INFO = 'info'
14
+ const SEV_WARN = 'warn'
15
+ const SEV_ERROR = 'error'
16
+ const SEV_FATAL = 'fatal'
17
+ const severityMap = new Map([
18
+ [0, SEV_UNKNOWN],
19
+ [1, SEV_TRACE],
20
+ [2, SEV_TRACE],
21
+ [3, SEV_TRACE],
22
+ [4, SEV_TRACE],
23
+ [5, SEV_DEBUG],
24
+ [6, SEV_DEBUG],
25
+ [7, SEV_DEBUG],
26
+ [8, SEV_DEBUG],
27
+ [9, SEV_INFO],
28
+ [10, SEV_INFO],
29
+ [11, SEV_INFO],
30
+ [12, SEV_INFO],
31
+ [13, SEV_WARN],
32
+ [14, SEV_WARN],
33
+ [15, SEV_WARN],
34
+ [16, SEV_WARN],
35
+ [17, SEV_ERROR],
36
+ [18, SEV_ERROR],
37
+ [19, SEV_ERROR],
38
+ [20, SEV_ERROR],
39
+ [21, SEV_FATAL],
40
+ [22, SEV_FATAL],
41
+ [23, SEV_FATAL],
42
+ [24, SEV_FATAL]
43
+ ])
44
+
45
+ /**
46
+ * Converts the OTEL severity number to a recognizable string.
47
+ *
48
+ * @see https://github.com/open-telemetry/opentelemetry-specification/blob/c041658/specification/logs/data-model.md?plain=1#L292-L299
49
+ *
50
+ * @param {number} severityNumber The number to convert.
51
+ *
52
+ * @returns {string} The known string.
53
+ */
54
+ function severityToString(severityNumber) {
55
+ return severityMap.get(severityNumber) ?? SEV_UNKNOWN
56
+ }
@@ -52,7 +52,7 @@ function bootstrapOtelMetrics(agent) {
52
52
 
53
53
  const meter = getMeter.apply(provider, args)
54
54
  const proto = Object.getPrototypeOf(meter)
55
- const methods = Object.getOwnPropertyNames(proto).filter(name => name.startsWith('create'))
55
+ const methods = Object.getOwnPropertyNames(proto).filter((name) => name.startsWith('create'))
56
56
  const originals = {}
57
57
  for (const method of methods) {
58
58
  originals[method] = meter[method]
package/lib/otel/setup.js CHANGED
@@ -6,6 +6,7 @@
6
6
  'use strict'
7
7
 
8
8
  const opentelemetry = require('@opentelemetry/api')
9
+ const logsApi = require('@opentelemetry/api-logs')
9
10
  const { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base')
10
11
 
11
12
  const NrSpanProcessor = require('./span-processor')
@@ -16,6 +17,7 @@ const defaultLogger = require('../logger').child({ component: 'opentelemetry-bri
16
17
  const createOtelLogger = require('./logger')
17
18
  const interceptSpanKey = require('./span-key-interceptor')
18
19
  const bootstrapMetrics = require('./metrics/bootstrap-metrics')
20
+ const bootstrapLogs = require('./logs/bootstrap-logs')
19
21
 
20
22
  function setupOtel(agent, logger = defaultLogger) {
21
23
  if (agent.config.opentelemetry_bridge.enabled !== true) {
@@ -25,8 +27,18 @@ function setupOtel(agent, logger = defaultLogger) {
25
27
  return
26
28
  }
27
29
 
30
+ // When bridge mode is enabled, our context manager must utilize the OTEL
31
+ // context manager. Otherwise, traces within, e.g. logs, will not be
32
+ // captured correctly. The internal OTEL logger also must be configured
33
+ // for the OTEL context manager to generate diagnostics messages that get
34
+ // delivered to the correct place.
35
+ createOtelLogger(logger, agent.config)
36
+ interceptSpanKey(agent)
37
+ opentelemetry.context.setGlobalContextManager(new ContextManager(agent))
38
+ opentelemetry.propagation.setGlobalPropagator(new TracePropagator(agent))
39
+
28
40
  if (agent.config.opentelemetry_bridge.traces.enabled === true) {
29
- bootstrapTraces(agent, logger)
41
+ bootstrapTraces(agent)
30
42
  } else {
31
43
  logger.debug('`opentelemetry_bridge.traces` is not enabled, skipping')
32
44
  }
@@ -37,14 +49,18 @@ function setupOtel(agent, logger = defaultLogger) {
37
49
  logger.debug('`opentelemetry_bridge.metrics` is not enabled, skipping')
38
50
  }
39
51
 
52
+ if (agent.config.opentelemetry_bridge.logs.enabled === true) {
53
+ bootstrapLogs({ agent })
54
+ } else {
55
+ logger.debug('`opentelemetry_bridge.logs` is not enabled, skipping')
56
+ }
57
+
40
58
  agent.metrics
41
59
  .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Setup')
42
60
  .incrementCallCount()
43
61
  }
44
62
 
45
- function bootstrapTraces(agent, logger) {
46
- createOtelLogger(logger, agent.config)
47
-
63
+ function bootstrapTraces(agent) {
48
64
  opentelemetry.trace.setGlobalTracerProvider(new BasicTracerProvider({
49
65
  sampler: new NrSampler(),
50
66
  spanProcessors: [new NrSpanProcessor(agent)],
@@ -53,10 +69,6 @@ function bootstrapTraces(agent, logger) {
53
69
  }
54
70
  }))
55
71
 
56
- interceptSpanKey(agent)
57
- opentelemetry.context.setGlobalContextManager(new ContextManager(agent))
58
- opentelemetry.propagation.setGlobalPropagator(new TracePropagator(agent))
59
-
60
72
  agent.metrics
61
73
  .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Traces')
62
74
  .incrementCallCount()
@@ -71,6 +83,7 @@ function teardownOtel(agent) {
71
83
  opentelemetry.context.disable()
72
84
  opentelemetry.propagation.disable()
73
85
  opentelemetry.diag.disable()
86
+ logsApi.logs.disable()
74
87
  }
75
88
 
76
89
  module.exports = {
@@ -0,0 +1,70 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const { create } = require('@apm-js-collab/code-transformer')
7
+ const Module = require('node:module')
8
+ const parse = require('module-details-from-path')
9
+ const getPackageVersion = require('./util/get-package-version')
10
+ const logger = require('./logger').child({ component: 'ModulePatch' })
11
+
12
+ class ModulePatch {
13
+ constructor({ packages = new Set(), instrumentations = [] } = {}) {
14
+ this.packages = packages
15
+ this.instrumentator = create(instrumentations)
16
+ this.transformers = new Map()
17
+ this.resolve = Module._resolveFilename
18
+ this.compile = Module.prototype._compile
19
+ }
20
+
21
+ /**
22
+ * Patches the Node.js module class methods that are responsible for resolving filePaths and compiling code.
23
+ * If a module is found that has an instrumentator, it will transform the code before compiling it
24
+ * with tracing channel methods.
25
+ */
26
+ patch() {
27
+ const self = this
28
+ Module._resolveFilename = function wrappedResolveFileName() {
29
+ const resolvedName = self.resolve.apply(this, arguments)
30
+ const resolvedModule = parse(resolvedName)
31
+ if (resolvedModule && self.packages.has(resolvedModule.name)) {
32
+ const version = getPackageVersion(resolvedModule.basedir, resolvedModule.name)
33
+ const transformer = self.instrumentator.getTransformer(resolvedModule.name, version, resolvedModule.path)
34
+ if (transformer) {
35
+ self.transformers.set(resolvedName, transformer)
36
+ }
37
+ }
38
+ return resolvedName
39
+ }
40
+
41
+ Module.prototype._compile = function wrappedCompile(...args) {
42
+ const [content, filename] = args
43
+ if (self.transformers.has(filename)) {
44
+ const transformer = self.transformers.get(filename)
45
+ try {
46
+ const transformedCode = transformer.transform(content, 'unknown')
47
+ args[0] = transformedCode
48
+ } catch (error) {
49
+ logger.error({ error }, `Error transforming module ${filename}`)
50
+ } finally {
51
+ transformer.free()
52
+ }
53
+ }
54
+
55
+ return self.compile.apply(this, args)
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Clears all the transformers and restores the original Module methods that were wrapped.
61
+ * **Note**: This is intended to be used in testing only.
62
+ */
63
+ unpatch() {
64
+ this.transformers.clear()
65
+ Module._resolveFilename = this.resolve
66
+ Module.prototype._compile = this.compile
67
+ }
68
+ }
69
+
70
+ module.exports = ModulePatch
@@ -54,9 +54,7 @@ class AwsLambda {
54
54
  }
55
55
 
56
56
  _detectEventType(event) {
57
- const pathMatch = (obj, path) => {
58
- return get(obj, path, null) !== null
59
- }
57
+ const pathMatch = (obj, path) => get(obj, path, null) !== null
60
58
 
61
59
  for (const typeInfo of Object.values(EVENT_SOURCE_INFO)) {
62
60
  if (typeInfo.required_keys.every((path) => pathMatch(event, path))) {
@@ -34,9 +34,7 @@ function wrapMounter(spec, shim, fn, fnName) {
34
34
  routeIdx = null
35
35
  route = null
36
36
  } else if (shim.isArray(route)) {
37
- route = route.map((routeArg) => {
38
- return shim._routeParser.call(this, shim, fn, fnName, routeArg)
39
- })
37
+ route = route.map((routeArg) => shim._routeParser.call(this, shim, fn, fnName, routeArg))
40
38
  } else {
41
39
  route = shim._routeParser.call(this, shim, fn, fnName, route)
42
40
  }
package/lib/shimmer.js CHANGED
@@ -23,7 +23,9 @@ let pkgsToHook = []
23
23
  const NAMES = require('./metrics/names')
24
24
  const symbols = require('./symbols')
25
25
  const { unsubscribe } = require('./instrumentation/undici')
26
- const { setupOtel, teardownOtel } = require('./otel/setup')
26
+ const subscriptions = require('./subscriber-configs')
27
+ const createSubscriberConfigs = require('./subscribers/create-config')
28
+ const ModulePatch = require('./patch-module')
27
29
 
28
30
  /**
29
31
  * Unwrapping is only likely to be used by test code, and is a fairly drastic
@@ -329,14 +331,32 @@ const shimmer = (module.exports = {
329
331
  }
330
332
 
331
333
  pkgsToHook = []
332
- teardownOtel(agent)
334
+ if (agent?.config?.opentelemetry_bridge?.enabled === true) {
335
+ // We must lazy load the method in order to support stripping of the
336
+ // `@opentelemetry` packages for slim builds.
337
+ require('./otel/setup').teardownOtel(agent)
338
+ }
333
339
  unsubscribe()
340
+ if (this._subscribers) {
341
+ shimmer.teardownSubscribers()
342
+ }
343
+ if (this._modulePatch) {
344
+ this._modulePatch.unpatch()
345
+ }
334
346
  },
335
347
 
336
348
  bootstrapInstrumentation: function bootstrapInstrumentation(agent) {
349
+ const subscriberConfigs = createSubscriberConfigs(subscriptions)
350
+ this._modulePatch = new ModulePatch(subscriberConfigs)
351
+ this._modulePatch.patch()
337
352
  shimmer.registerCoreInstrumentation(agent)
338
353
  shimmer.registerThirdPartyInstrumentation(agent)
339
- setupOtel(agent)
354
+ shimmer.setupSubscribers(agent)
355
+ if (agent?.config?.opentelemetry_bridge?.enabled === true) {
356
+ // We must lazy load the method in order to support stripping of the
357
+ // `@opentelemetry` packages for slim builds.
358
+ require('./otel/setup').setupOtel(agent)
359
+ }
340
360
  },
341
361
 
342
362
  registerInstrumentation: function registerInstrumentation(opts) {
@@ -371,6 +391,35 @@ const shimmer = (module.exports = {
371
391
 
372
392
  registeredInstrumentations: new InstrumentationTracker(),
373
393
 
394
+ setupSubscribers: function setupSubscribers(agent) {
395
+ this._subscribers = {}
396
+ for (const subscriberConfigList of Object.values(subscriptions)) {
397
+ for (const subscriberConfig of subscriberConfigList) {
398
+ const Subscriber = require(`./subscribers/${subscriberConfig.path}`)
399
+ const subscriber = new Subscriber({ agent, logger })
400
+ if (subscriber.enabled === false) {
401
+ logger.debug(
402
+ 'Skipping subscriber %s because it is disabled in the config',
403
+ subscriber.id
404
+ )
405
+ continue
406
+ }
407
+
408
+ subscriber.enable()
409
+ subscriber.subscribe()
410
+ this._subscribers[subscriber.id] = subscriber
411
+ }
412
+ }
413
+ },
414
+
415
+ teardownSubscribers: function teardownSubscribers() {
416
+ for (const subscriber of Object.values(this._subscribers)) {
417
+ subscriber.disable()
418
+ subscriber.unsubscribe()
419
+ }
420
+ this._subscribers = {}
421
+ },
422
+
374
423
  /**
375
424
  * NOT FOR USE IN PRODUCTION CODE
376
425
  *
@@ -432,8 +481,7 @@ const shimmer = (module.exports = {
432
481
  isInstrumented(moduleName, resolvedName) {
433
482
  const allItems = shimmer.registeredInstrumentations.getAllByName(moduleName)
434
483
  const items = allItems.filter(
435
- (item) =>
436
- item.instrumentation.resolvedName === resolvedName && item.meta.instrumented === true
484
+ (item) => item.instrumentation.resolvedName === resolvedName && item.meta.instrumented === true
437
485
  )
438
486
  return items.length === allItems.length
439
487
  },
@@ -669,12 +717,12 @@ function _firstPartyInstrumentation(agent, fileName, shim, nodule, moduleName) {
669
717
  * to load the module), as well as the `onRequire` and `onError` hooks to
670
718
  * attach to the module.
671
719
  *
672
- * @param {object} agent
720
+ * @param {object} agent agent instance
673
721
  * @param {object} nodule The newly loaded module.
674
722
  * @param {string} name The simple name used to load the module.
675
723
  * @param {string} resolvedName The full file system path to the module.
676
- * @param {object} [esmResolver]
677
- * @returns {*|Object|undefined}
724
+ * @param {object} [esmResolver] If the module was loaded via ESM
725
+ * @returns {*|Object|undefined} The instrumented module, or the original
678
726
  * @private
679
727
  */
680
728
  function _postLoad(agent, nodule, name, resolvedName, esmResolver) {
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ // The expected export of these files is:
9
+ // 'package-name': [ { path: 'subscriberPath', instrumentations: [] }, ... ]
10
+ const subscribers = {
11
+ ...require('./subscribers/elasticsearch/config'),
12
+ ...require('./subscribers/ioredis/config'),
13
+ ...require('./subscribers/mcp-sdk/config'),
14
+ ...require('./subscribers/pino/config'),
15
+ }
16
+
17
+ module.exports = subscribers