newrelic 13.1.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 (58) hide show
  1. package/NEWS.md +46 -0
  2. package/README.md +2 -4
  3. package/THIRD_PARTY_NOTICES.md +3 -3
  4. package/esm-loader.mjs +9 -1
  5. package/index.js +2 -2
  6. package/lib/agent.js +1 -2
  7. package/lib/config/build-instrumentation-config.js +3 -0
  8. package/lib/config/default.js +1397 -1395
  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/kafkajs/producer.js +13 -19
  17. package/lib/instrumentation/openai.js +2 -2
  18. package/lib/instrumentation/undici.js +48 -40
  19. package/lib/instrumentations.js +14 -5
  20. package/lib/llm-events/aws-bedrock/utils.js +1 -1
  21. package/lib/metrics/names.js +8 -0
  22. package/lib/metrics/normalizer.js +1 -3
  23. package/lib/otel/context.js +18 -14
  24. package/lib/otel/metrics/bootstrap-metrics.js +1 -1
  25. package/lib/patch-module.js +70 -0
  26. package/lib/serverless/aws-lambda.js +1 -3
  27. package/lib/shim/webframework-shim/middleware-mounter.js +1 -3
  28. package/lib/shimmer.js +56 -8
  29. package/lib/subscriber-configs.js +17 -0
  30. package/lib/subscribers/application-logs.js +55 -0
  31. package/lib/subscribers/base.js +177 -0
  32. package/lib/subscribers/create-config.js +27 -0
  33. package/lib/subscribers/db-operation.js +21 -0
  34. package/lib/subscribers/db-query.js +54 -0
  35. package/lib/subscribers/db.js +57 -0
  36. package/lib/subscribers/elasticsearch/config.js +49 -0
  37. package/lib/subscribers/elasticsearch/elasticsearch.js +42 -0
  38. package/lib/subscribers/elasticsearch/opensearch.js +15 -0
  39. package/lib/subscribers/elasticsearch/transport.js +14 -0
  40. package/lib/subscribers/ioredis/config.js +37 -0
  41. package/lib/subscribers/ioredis/index.js +48 -0
  42. package/lib/subscribers/mcp-sdk/client-prompt.js +23 -0
  43. package/lib/subscribers/mcp-sdk/client-resource.js +24 -0
  44. package/lib/subscribers/mcp-sdk/client-tool.js +23 -0
  45. package/lib/subscribers/mcp-sdk/client.js +29 -0
  46. package/lib/subscribers/mcp-sdk/config.js +104 -0
  47. package/lib/subscribers/pino/config.js +20 -0
  48. package/lib/subscribers/pino/index.js +102 -0
  49. package/lib/transaction/trace/aggregator.js +7 -9
  50. package/lib/util/camel-case.js +1 -3
  51. package/lib/util/get-package-version.js +34 -0
  52. package/lib/w3c/tracestate.js +3 -3
  53. package/package.json +7 -5
  54. package/lib/instrumentation/@elastic/elasticsearch.js +0 -62
  55. package/lib/instrumentation/@opensearch-project/opensearch.js +0 -66
  56. package/lib/instrumentation/ioredis.js +0 -50
  57. package/lib/instrumentation/pino/nr-hooks.js +0 -19
  58. package/lib/instrumentation/pino/pino.js +0 -168
@@ -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
@@ -0,0 +1,55 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const Subscriber = require('./base')
7
+ const { isApplicationLoggingEnabled, isLocalDecoratingEnabled, isLogForwardingEnabled, isMetricsEnabled, createModuleUsageMetric, incrementLoggingLinesMetrics } = require('../util/application-logging')
8
+
9
+ class ApplicationLogsSubscriber extends Subscriber {
10
+ constructor({ agent, logger, packageName, channelName, }) {
11
+ super({ agent, logger, packageName, channelName, })
12
+ this.requireActiveTx = false
13
+ this.libMetricCreated = false
14
+ }
15
+
16
+ get enabled() {
17
+ return super.enabled && isApplicationLoggingEnabled(this.config)
18
+ }
19
+
20
+ /**
21
+ * The intent of this method is to create a module usage metric
22
+ * but only once. You should call this in your handler.
23
+ * We cannot recreate creating this metric on require of a logging library
24
+ * because we no longer monkey patch but subscribe to events.
25
+ * @param lib
26
+ */
27
+ createModuleUsageMetric(lib) {
28
+ if (this.libMetricCreated === false) {
29
+ createModuleUsageMetric(lib, this.agent.metrics)
30
+ this.libMetricCreated = true
31
+ }
32
+ }
33
+
34
+ decorateLogLine() {
35
+ if (isLocalDecoratingEnabled(this.config)) {
36
+ return this.agent.getNRLinkingMetadata()
37
+ }
38
+ }
39
+
40
+ incrementLinesMetric(level) {
41
+ if (isMetricsEnabled(this.config)) {
42
+ incrementLoggingLinesMetrics(level, this.agent.metrics)
43
+ }
44
+ }
45
+
46
+ forwardLogLine(data) {
47
+ if (isLogForwardingEnabled(this.config, this.agent)) {
48
+ const ctx = this.agent.tracer.getContext()
49
+ const formatLogLine = this.reformatLogLine(data, ctx)
50
+ this.agent.logs.add(formatLogLine)
51
+ }
52
+ }
53
+ }
54
+
55
+ module.exports = ApplicationLogsSubscriber
@@ -0,0 +1,177 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
8
+ const { tracingChannel } = require('node:diagnostics_channel')
9
+
10
+ /**
11
+ * The baseline parameters available to all subscribers.
12
+ *
13
+ * @typedef {object} SubscriberParams
14
+ * @property {object} agent A New Relic Node.js agent instance.
15
+ * @property {object} logger An agent logger instance.
16
+ * @property {string} packageName The npm installable name for the package
17
+ * being instrumented. This is what a developer would provide to the `require`
18
+ * function.
19
+ * @property {string} channelName A unique name for the diagnostics channel
20
+ * that will be created and monitored.
21
+ */
22
+
23
+ /**
24
+ * @property {object} agent A New Relic Node.js agent instance.
25
+ * @property {object} logger An agent logger instance.
26
+ * @property {object} config The agent configuration object.
27
+ * @property {string} packageName The name of the module being instrumented.
28
+ * This is the same string one would pass to the `require` function.
29
+ * @property {string} channelName A unique name for the diagnostics channel
30
+ * that will be registered.
31
+ * @property {string[]} [events=[]] Set of tracing channel event names to
32
+ * register handlers for. For any name in the set, a corresponding method
33
+ * must exist on the subscriber instance. The method will be passed the
34
+ * event object. Possible event names are `start`, `end`, `asyncStart`,
35
+ * `asyncEnd`, and `error`. @link https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel
36
+ * @property {boolean} [opaque=false] If true, any children segments will not be created
37
+ * @property {boolean} [internal=false] If true, any children segments from the same library will not be created
38
+ * @property {string} [prefix='orchestrion:'] String to prepend to diagnostics
39
+ * channel event names. This provides a namespace for the events we are
40
+ * injecting into a module.
41
+ * @property {boolean} [requireActiveTx=true] If true, the subscriber will only handle events when there is an active transaction.
42
+ * @property {string} id A unique identifier for the subscriber, combining the prefix, package name, and channel name.
43
+ * @property {TracingChannel} channel The tracing channel instance this subscriber will be monitoring.
44
+ * @property {AsyncLocalStorage} store The async local storage instance used for context management.
45
+ */
46
+ class Subscriber {
47
+ constructor({ agent, logger, packageName, channelName }) {
48
+ this.agent = agent
49
+ this.logger = logger.child({ component: `${packageName}-subscriber` })
50
+ this.config = agent.config
51
+ this.packageName = packageName
52
+ this.channelName = channelName
53
+ this.events = []
54
+ this.opaque = false
55
+ this.internal = false
56
+ this.prefix = 'orchestrion:'
57
+ this.requireActiveTx = true
58
+ this.id = `${this.prefix}${this.packageName}:${this.channelName}`
59
+ this.channel = tracingChannel(this.id)
60
+ this.store = agent.tracer._contextManager._asyncLocalStorage
61
+ }
62
+
63
+ shouldCreateSegment(parent) {
64
+ return !(parent?.opaque ||
65
+ (this.internal && this.packageName === parent?.shimId)
66
+ )
67
+ }
68
+
69
+ /**
70
+ * Creates a segment with a name, parent, transaction and optional recorder.
71
+ * If the segment is successfully created, it will be started and added to the context.
72
+ * @param {Object} params - Parameters for creating the segment
73
+ * @param {string} params.name - The name of the segment
74
+ * @param {Object} params.recorder - Optional recorder for the segment
75
+ * @param {Context} params.ctx - The context containing the parent segment and transaction
76
+ * @returns {Context} - The updated context with the new segment or existing context if segment creation fails
77
+ */
78
+ createSegment({ name, recorder, ctx }) {
79
+ const parent = ctx?.segment
80
+
81
+ if (this.shouldCreateSegment(parent) === false) {
82
+ this.logger.trace('Skipping segment creation for %s, %s(parent) is of the same package: %s and incoming segment is marked as internal', name, parent?.name, this.packageName)
83
+ return ctx
84
+ }
85
+
86
+ const segment = this.agent.tracer.createSegment({
87
+ name,
88
+ parent,
89
+ recorder,
90
+ transaction: ctx?.transaction,
91
+ })
92
+
93
+ if (segment) {
94
+ segment.opaque = this.opaque
95
+ segment.shimId = this.packageName
96
+ segment.start()
97
+ this.logger.trace('Created segment %s', name)
98
+ this.addAttributes(segment)
99
+ const newCtx = ctx.enterSegment({ segment })
100
+ return newCtx
101
+ } else {
102
+ this.logger.trace('Failed to create segment for %s', name)
103
+ return ctx
104
+ }
105
+ }
106
+
107
+ /**
108
+ * By default this is a no-op, but can be overridden by subclasses
109
+ * @param {Segment} segment - The segment to which attributes will be added
110
+ * @returns {void}
111
+ */
112
+ addAttributes(segment) {
113
+
114
+ }
115
+
116
+ /**
117
+ * Checks if the subscriber is enabled based on the agent's configuration.
118
+ */
119
+ get enabled() {
120
+ return this.config.instrumentation[this.packageName].enabled === true
121
+ }
122
+
123
+ /**
124
+ * Enables the subscriber by binding the store to the channel and setting up the handler.
125
+ * If the subscriber requires an active transaction, it will check the context before passing the event to the handler.
126
+ * @returns {Context} - The context after processing the event
127
+ */
128
+ enable() {
129
+ this.channel.start.bindStore(this.store, (data) => {
130
+ const ctx = this.agent.tracer.getContext()
131
+ if (this.requireActiveTx && !ctx?.transaction?.isActive()) {
132
+ this.logger.debug('Not recording event for %s, transaction is not active', this.package)
133
+ return ctx
134
+ }
135
+
136
+ return this.handler(data, ctx)
137
+ })
138
+ }
139
+
140
+ /**
141
+ * Disables the subscriber by unbinding the store from the channel.
142
+ */
143
+ disable() {
144
+ this.channel.start.unbindStore(this.store)
145
+ }
146
+
147
+ /**
148
+ * Common handler for when async events end.
149
+ * It gets the context and touches the segment if it exists.
150
+ */
151
+ asyncEnd() {
152
+ const ctx = this.agent.tracer.getContext()
153
+ ctx?.segment?.touch()
154
+ }
155
+
156
+ /*
157
+ * Subscribes to the events defined in the `events` array.
158
+ */
159
+ subscribe() {
160
+ this.subscriptions = this.events.reduce((events, curr) => {
161
+ events[curr] = this[curr].bind(this)
162
+ return events
163
+ }, {})
164
+
165
+ this.channel.subscribe(this.subscriptions)
166
+ }
167
+
168
+ /**
169
+ * Unsubscribes from the events defined in the `events` array..
170
+ */
171
+ unsubscribe() {
172
+ this.channel.unsubscribe(this.subscriptions)
173
+ this.subscriptions = null
174
+ }
175
+ }
176
+
177
+ module.exports = Subscriber
@@ -0,0 +1,27 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ /**
9
+ * Builds a set of packages and instrumentaions from a list of subscriber configurations.
10
+ *
11
+ * @param {Array} subscribers - An array of subscriber objects, each containing a package name and an array of instrumentations.
12
+ * @returns {Object} An object containing a Set of unique package names and an array of instrumentations.
13
+ */
14
+ function createSubscribersConfig (subscribers = []) {
15
+ const packages = new Set()
16
+ const instrumentations = []
17
+ for (const [packageName, subscriberList] of Object.entries(subscribers)) {
18
+ packages.add(packageName)
19
+ for (const subscriber of subscriberList) {
20
+ instrumentations.push(...subscriber.instrumentations)
21
+ }
22
+ }
23
+
24
+ return { packages, instrumentations }
25
+ }
26
+
27
+ module.exports = createSubscribersConfig
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const DbSubscriber = require('./db')
7
+ const recordOperationMetrics = require('../metrics/recorders/database-operation')
8
+ const { DB } = require('../metrics/names')
9
+
10
+ class DbOperationSubscriber extends DbSubscriber {
11
+ handler(data, ctx) {
12
+ const name = `${DB.OPERATION}/${this.system}/${this.operation}`
13
+ return this.createSegment({
14
+ name,
15
+ ctx,
16
+ recorder: recordOperationMetrics.bind(this),
17
+ })
18
+ }
19
+ }
20
+
21
+ module.exports = DbOperationSubscriber
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const DbSubscriber = require('./db')
7
+ const recordQueryMetrics = require('../metrics/recorders/database')
8
+ const { DB } = require('../metrics/names')
9
+ const ParsedStatement = require('../db/parsed-statement')
10
+ const parseSql = require('../db/query-parsers/sql')
11
+
12
+ class DbQuerySubscriber extends DbSubscriber {
13
+ handler(data, ctx) {
14
+ const queryString = this.queryString
15
+ const parsed = this.parseQueryString(queryString)
16
+ const name = `${DB.STATEMENT}/${this.system}/${parsed.collection}/${parsed.operation}`
17
+ return this.createSegment({
18
+ name,
19
+ ctx,
20
+ recorder: recordQueryMetrics.bind(parsed),
21
+ })
22
+ }
23
+
24
+ parseQueryString(queryString) {
25
+ const parsed = this.parseQuery(queryString)
26
+ let collection = parsed.collection
27
+ // strip enclosing special characters from collection (table) name
28
+ if (typeof collection === 'string' && collection.length > 2) {
29
+ if (/^[[{'"`]/.test(collection)) {
30
+ collection = collection.substring(1)
31
+ }
32
+ if (/[\]}'"`]$/.test(collection)) {
33
+ collection = collection.substring(0, collection.length - 1)
34
+ }
35
+ }
36
+
37
+ const queryRecorded =
38
+ this.config.transaction_tracer.record_sql === 'raw' ||
39
+ this.config.transaction_tracer.record_sql === 'obfuscated'
40
+
41
+ return new ParsedStatement(
42
+ this._metrics.PREFIX,
43
+ parsed.operation,
44
+ collection,
45
+ queryRecorded ? parsed.query : null
46
+ )
47
+ }
48
+
49
+ parseQuery(queryString) {
50
+ return parseSql(queryString)
51
+ }
52
+ }
53
+
54
+ module.exports = DbQuerySubscriber
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const Subscriber = require('./base')
7
+ const { ALL, DB } = require('../metrics/names')
8
+ const urltils = require('../util/urltils')
9
+
10
+ class DbSubscriber extends Subscriber {
11
+ constructor({ agent, logger, packageName, channelName, system }) {
12
+ super({ agent, logger, packageName, channelName })
13
+ this.system = system
14
+ // must be prefixed with `_` as that's what the metrics recorder expects
15
+ this._metrics = {
16
+ PREFIX: this.system,
17
+ ALL: `${DB.PREFIX}${this.system}/${ALL}`
18
+ }
19
+ this.instanceKeys = ['host', 'port_path_or_id']
20
+ this.hostKey = 'host'
21
+ this.dbNameKey = 'database_name'
22
+ }
23
+
24
+ get instanceReporting() {
25
+ return this.config.datastore_tracer.instance_reporting.enabled
26
+ }
27
+
28
+ get dbNameReporting() {
29
+ return this.config.datastore_tracer.database_name_reporting.enabled
30
+ }
31
+
32
+ addAttributes(segment) {
33
+ for (let [key, value] of Object.entries(this.parameters)) {
34
+ if (this.instanceKeys.includes(key) && !this.instanceReporting) {
35
+ continue
36
+ }
37
+
38
+ if (key === this.dbNameKey && !this.dbNameReporting) {
39
+ continue
40
+ }
41
+
42
+ if (key === this.hostKey && urltils.isLocalhost(value)) {
43
+ // eslint-disable-next-line sonarjs/updated-loop-counter
44
+ value = this.config.getHostnameSafe()
45
+ }
46
+
47
+ if (key === this.dbNameKey && typeof value === 'number') {
48
+ // eslint-disable-next-line sonarjs/updated-loop-counter
49
+ value = String(value)
50
+ }
51
+
52
+ segment.addAttribute(key, value)
53
+ }
54
+ }
55
+ }
56
+
57
+ module.exports = DbSubscriber
@@ -0,0 +1,49 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ module.exports = {
7
+ '@elastic/elasticsearch': [{
8
+ path: './elasticsearch/elasticsearch.js',
9
+ instrumentations: [
10
+ {
11
+ channelName: 'nr_request',
12
+ module: { name: '@elastic/elasticsearch', versionRange: '>=7.16.0', filePath: 'lib/Transport.js' },
13
+ functionQuery: {
14
+ className: 'Transport',
15
+ methodName: 'request',
16
+ kind: 'Async'
17
+ }
18
+ }
19
+ ]
20
+ }],
21
+ '@elastic/transport': [{
22
+ path: './elasticsearch/transport.js',
23
+ instrumentations: [
24
+ {
25
+ channelName: 'nr_request',
26
+ module: { name: '@elastic/transport', versionRange: '>=8.0.0', filePath: 'lib/Transport.js' },
27
+ functionQuery: {
28
+ className: 'Transport',
29
+ methodName: 'request',
30
+ kind: 'Async'
31
+ }
32
+ }
33
+ ]
34
+ }],
35
+ '@opensearch-project/opensearch': [{
36
+ path: './elasticsearch/opensearch.js',
37
+ instrumentations: [
38
+ {
39
+ channelName: 'nr_request',
40
+ module: { name: '@opensearch-project/opensearch', versionRange: '>=2.1.0', filePath: 'lib/Transport.js' },
41
+ functionQuery: {
42
+ className: 'Transport',
43
+ methodName: 'request',
44
+ kind: 'Async'
45
+ }
46
+ }
47
+ ]
48
+ }]
49
+ }