newrelic 14.1.2 → 14.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.
@@ -5,15 +5,68 @@
5
5
 
6
6
  'use strict'
7
7
  const BaseProfiler = require('./base')
8
+ const semver = require('semver')
8
9
 
10
+ /**
11
+ * (CPED) Key under which each segment caches its sample context, so the same
12
+ * span always maps to the same object identity.
13
+ */
14
+ const PROFILING_CONTEXT = Symbol('NewRelicProfilingContext')
15
+
16
+ /**
17
+ * (non-CPED) Empty holder for when no span is active. Frozen so it can't be
18
+ * mutated in place — pprof reads holders by reference, so we swap in a fresh one
19
+ * per sample.
20
+ */
21
+ const DEFAULT_CONTEXT = Object.freeze({ current: {} })
22
+
23
+ /**
24
+ * Produces a wall time stack profile via `pprof`.
25
+ * The native sampler fires at a fixed rate against whatever JS is currently on
26
+ * the stack, and each sample is tagged with the active transaction's trace/span
27
+ * ids. We capture those ids by hooking the agent's `AsyncLocalStorage`; pprof
28
+ * then carries them per sample via CPED when `AsyncContextFrame` is active, or
29
+ * in a holder we swap per sample otherwise.
30
+ *
31
+ * The wall-clock (or just wall) time for a function measures the time elapsed
32
+ * between entering and exiting a function. Wall time includes all wait time,
33
+ * including that for locks and thread synchronization.
34
+ */
9
35
  class CpuProfiler extends BaseProfiler {
10
36
  #pprof
37
+ #tracer
11
38
  #durationMillis
12
39
  #intervalMicros = (1e3 / 99) * 1000 // samples at 99hz(99 times per second)
13
- constructor({ logger, samplingInterval }) {
40
+ #kSampleCount
41
+ #useCPED
42
+ /**
43
+ * (non-CPED) `pprof` captures this holder by reference at sample time, so
44
+ * we keep `current` pointed at the active transaction's trace/span ids.
45
+ */
46
+ #context = DEFAULT_CONTEXT
47
+ #state
48
+ #lastSampleCount = 0
49
+ /**
50
+ * The agent's `AsyncLocalStorage` instance.
51
+ */
52
+ #store
53
+ // Saved ALS methods, restored by `#unhookContext` on stop.
54
+ #origRun
55
+ #origEnterWith
56
+ /**
57
+ * (non-CPED) Async hook that refreshes context on async resumption.
58
+ */
59
+ #asyncHook
60
+
61
+ constructor({ logger, samplingInterval, tracer }) {
14
62
  super({ logger })
15
63
  this.#pprof = require('@datadog/pprof')
64
+ this.#kSampleCount = this.#pprof.time.constants.kSampleCount
65
+ this.#tracer = tracer
66
+ this.#store = tracer._contextManager._asyncLocalStorage
16
67
  this.#durationMillis = samplingInterval
68
+ // CPED requires AsyncContextFrame which is on by default with node >=24
69
+ this.#useCPED = semver.satisfies(process.versions.node, '>=24.0.0')
17
70
  }
18
71
 
19
72
  start() {
@@ -25,8 +78,21 @@ class CpuProfiler extends BaseProfiler {
25
78
  this.logger.trace(`Starting CpuProfiler, sample every ${this.#intervalMicros}hz for ${this.#durationMillis} ms.`)
26
79
  this.#pprof.time.start({
27
80
  durationMillis: this.#durationMillis,
28
- intervalMicros: this.#intervalMicros
81
+ intervalMicros: this.#intervalMicros,
82
+ // needed for trace_id + span_id label
83
+ withContexts: true,
84
+ // carry the sample context per async-context-frame when ACF is active
85
+ useCPED: this.#useCPED
29
86
  })
87
+
88
+ if (this.#useCPED) {
89
+ this.#hookEnterWith()
90
+ } else {
91
+ this.#state = this.#pprof.time.getState()
92
+ this.#resetContext()
93
+ this.#hookRun()
94
+ this.#hookAsyncResume()
95
+ }
30
96
  }
31
97
 
32
98
  stop() {
@@ -35,13 +101,169 @@ class CpuProfiler extends BaseProfiler {
35
101
  return
36
102
  }
37
103
 
104
+ // Restore the wrapped ALS methods before stopping.
105
+ this.#unhookContext()
38
106
  this.#pprof.time.stop(false)
39
107
  }
40
108
 
41
109
  async collect() {
42
- const profile = this.#pprof.time.stop(true)
110
+ if (!this.#useCPED) {
111
+ // Flush the active span before sampling restarts.
112
+ this.#updateContext()
113
+ }
114
+ const profile = this.#pprof.time.stop(true, this.#generateLabels)
115
+ if (!this.#useCPED) {
116
+ // `stop(true)` restarts sampling, so start a fresh holder.
117
+ this.#resetContext()
118
+ }
43
119
  return this.#pprof.encode(profile)
44
120
  }
121
+
122
+ /**
123
+ * Callback that returns the `span_id` and `trace_id`
124
+ * labels active when each sample was taken.
125
+ *
126
+ * @param {object} params params from pprof
127
+ * @param {object} params.context the captured time profile node context
128
+ * @returns {object} label set applied to the pprof sample
129
+ */
130
+ #generateLabels = ({ context }) => {
131
+ const capturedContext = context?.context
132
+ // With CPED the captured value is the label itself; without it,
133
+ // the label lives in the holder's `current`.
134
+ const { traceId, spanId } = (this.#useCPED ? capturedContext : capturedContext?.current) ?? {}
135
+ // A span id only exists when a trace id does, so trace_id anchors the label set:
136
+ // no trace id means no active span, and span_id is added only when present.
137
+ if (!traceId) {
138
+ return {}
139
+ }
140
+ return spanId ? { span_id: spanId, trace_id: traceId } : { trace_id: traceId }
141
+ }
142
+
143
+ /**
144
+ * Refreshes the pprof sample context with the active span. With CPED, the label
145
+ * is written straight into the current async-context-frame; otherwise a fresh
146
+ * holder is swapped in once a sample has landed (so earlier samples keep their
147
+ * ids) before reassigning its `current`.
148
+ */
149
+ #updateContext = () => {
150
+ if (this.#useCPED) {
151
+ this.#updateCpedContext()
152
+ return
153
+ }
154
+
155
+ const { 'trace.id': traceId, 'span.id': spanId } = this.#tracer.agent.getLinkingMetadata(true)
156
+ const spanContext = { traceId, spanId }
157
+ const sampleCount = this.#state?.[this.#kSampleCount]
158
+ if (sampleCount !== this.#lastSampleCount || this.#context === DEFAULT_CONTEXT) {
159
+ this.#lastSampleCount = sampleCount
160
+ this.#context = { current: spanContext }
161
+ this.#pprof.time.setContext(this.#context)
162
+ } else {
163
+ this.#context.current = spanContext
164
+ }
165
+ }
166
+
167
+ /**
168
+ * (CPED) Installs the active span's context into the current async-context-frame.
169
+ * Each segment's `{ traceId, spanId }` is built once and cached on the segment
170
+ * (its ids never change), and `setContext` is only called when the frame isn't
171
+ * already holding that object, skipping the redundant native write pprof would
172
+ * otherwise do every time a frame is entered or restored.
173
+ */
174
+ #updateCpedContext() {
175
+ const segment = this.#tracer.getSegment()
176
+ let spanContext = Object.freeze({})
177
+ if (segment) {
178
+ spanContext = segment[PROFILING_CONTEXT]
179
+ if (spanContext === undefined) {
180
+ const { 'trace.id': traceId, 'span.id': spanId } = this.#tracer.agent.getLinkingMetadata(true)
181
+ spanContext = segment[PROFILING_CONTEXT] = { traceId, spanId }
182
+ }
183
+ }
184
+
185
+ if (this.#pprof.time.getContext() !== spanContext) {
186
+ this.#pprof.time.setContext(spanContext)
187
+ }
188
+ }
189
+
190
+ /**
191
+ * (non-CPED) Points the pprof time profiler at a fresh `#context`
192
+ * and resyncs the sample counter.
193
+ */
194
+ #resetContext() {
195
+ this.#context = DEFAULT_CONTEXT
196
+ this.#lastSampleCount = this.#state?.[this.#kSampleCount] ?? 0
197
+ this.#pprof.time.setContext(this.#context)
198
+ }
199
+
200
+ /**
201
+ * (CPED) Wraps `enterWith` on the agent's `AsyncLocalStorage`. Since `run`
202
+ * delegates to `enterWith` under ACF, this one hook covers every context
203
+ * switch; we then `setContext` the active span into the current frame, which
204
+ * pprof propagates to continuations and restores on unwind.
205
+ */
206
+ #hookEnterWith() {
207
+ const self = this
208
+ this.#origEnterWith = this.#store.enterWith
209
+ const origEnterWith = this.#origEnterWith
210
+ this.#store.enterWith = function (store) {
211
+ const retVal = origEnterWith.call(this, store)
212
+ self.#updateContext()
213
+ return retVal
214
+ }
215
+ }
216
+
217
+ /**
218
+ * (non-CPED) Wraps `run` on the agent's `AsyncLocalStorage` to refresh the
219
+ * holder on `run` entry and exit (so a sibling doesn't inherit a returned
220
+ * child's span). Wraps the instance, not the prototype.
221
+ */
222
+ #hookRun() {
223
+ const self = this
224
+ this.#origRun = this.#store.run
225
+ const origRun = this.#origRun
226
+ this.#store.run = function (store, callback, ...args) {
227
+ function wrapped(...cbArgs) {
228
+ self.#updateContext()
229
+ return callback.apply(this, cbArgs)
230
+ }
231
+ try {
232
+ return origRun.call(this, store, wrapped, ...args)
233
+ } finally {
234
+ self.#updateContext()
235
+ }
236
+ }
237
+ }
238
+
239
+ /**
240
+ * (non-CPED) Async resumptions (timer, promise, I/O) restore the active store
241
+ * without a `run` call, leaving the holder frozen at the last `run`'s span. A
242
+ * `before` hook refreshes it so those samples get the right span.
243
+ */
244
+ #hookAsyncResume() {
245
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
246
+ const { createHook } = require('async_hooks')
247
+ this.#asyncHook = createHook({ before: () => this.#updateContext() })
248
+ this.#asyncHook.enable()
249
+ }
250
+
251
+ /**
252
+ * Restores the original `run`/`enterWith` and disables the async hook so none
253
+ * fire once the profiler has stopped.
254
+ */
255
+ #unhookContext() {
256
+ if (this.#origRun) {
257
+ this.#store.run = this.#origRun
258
+ this.#origRun = null
259
+ }
260
+ if (this.#origEnterWith) {
261
+ this.#store.enterWith = this.#origEnterWith
262
+ this.#origEnterWith = null
263
+ }
264
+ this.#asyncHook?.disable()
265
+ this.#asyncHook = null
266
+ }
45
267
  }
46
268
 
47
269
  module.exports = CpuProfiler
@@ -32,6 +32,7 @@ const subscribers = {
32
32
  ...require('./subscribers/langgraph/config'),
33
33
  ...require('./subscribers/mcp-sdk/config'),
34
34
  ...require('./subscribers/memcached/config'),
35
+ ...require('./subscribers/mongodb/config'),
35
36
  ...require('./subscribers/mysql/config'),
36
37
  ...require('./subscribers/mysql2/config'),
37
38
  ...require('./subscribers/nestjs/config'),
@@ -188,11 +188,12 @@ class Subscriber {
188
188
  * @param {object} [params.recorder] - Optional recorder for the segment
189
189
  * @param {Context} params.ctx - The context containing the parent segment and
190
190
  * transaction
191
+ * @param {object} [params.attributes] See {@link #addAttributes}.
191
192
  *
192
193
  * @returns {AsyncContext} - The updated context with the new segment or
193
194
  * existing context if segment creation fails
194
195
  */
195
- createSegment({ name, recorder, ctx }) {
196
+ createSegment({ name, recorder, ctx, attributes = null }) {
196
197
  const parent = ctx?.segment
197
198
 
198
199
  if (this.shouldCreateSegment(parent) === false) {
@@ -212,7 +213,7 @@ class Subscriber {
212
213
  segment.shimId = this.packageName
213
214
  segment.start()
214
215
  this.logger.trace('Created segment %s, returning new context', name)
215
- this.addAttributes(segment)
216
+ this.addAttributes(segment, attributes)
216
217
  return ctx.enterSegment({ segment })
217
218
  } else {
218
219
  this.logger.trace('Failed to create segment for %s, returning existing context', name)
@@ -221,13 +222,19 @@ class Subscriber {
221
222
  }
222
223
 
223
224
  /**
224
- * By default this is a no-op, but can be overridden by subclasses
225
- * @param {Segment} segment - The segment to which attributes will be added
225
+ * By default, this is a no-op, but can be overridden by subclasses.
226
+ *
227
+ * @param {Segment} segment The segment to which attributes will be added
228
+ * @param {object} [attributes] A set of attributes to add to the
229
+ * segment. Some subclasses, e.g. the {@link DbSubscriber} subclass, have
230
+ * a mechanism to supply these as an object property
231
+ * (`subscriber.parameters`). But there are cases where the attributes must
232
+ * be calculated during the transaction, and are thus easier to pass through
233
+ * via this parameter.
234
+ *
226
235
  * @returns {void}
227
236
  */
228
- addAttributes(segment) {
229
-
230
- }
237
+ addAttributes(segment, attributes) {}
231
238
 
232
239
  /**
233
240
  * Not all subscribers need to change the context on an event.
@@ -14,6 +14,8 @@ const urltils = require('../util/urltils')
14
14
  * - `database_name`: The name of the database.
15
15
  * - `port_path_or_id`: The database port, path, or ID.
16
16
  * @property {string} system The database system being used (e.g., MySQL, MongoDB).
17
+ *
18
+ * @class DbSubscriber
17
19
  */
18
20
  class DbSubscriber extends Subscriber {
19
21
  /**
@@ -50,9 +52,16 @@ class DbSubscriber extends Subscriber {
50
52
  /**
51
53
  * Adds `this.parameters` to the active segment.
52
54
  * @param {object} segment the current segment
55
+ * @param {object} [attributes] Attributes to add. Defaults to
56
+ * `this.parameters`. If both are defined, this value takes precedence.
53
57
  */
54
- addAttributes(segment) {
55
- for (let [key, value] of Object.entries(this.parameters ?? {})) {
58
+ addAttributes(segment, attributes = {}) {
59
+ const parameters = Object.assign(
60
+ {},
61
+ this.parameters ?? {},
62
+ attributes
63
+ )
64
+ for (let [key, value] of Object.entries(parameters)) {
56
65
  if (this.instanceKeys.includes(key) && !this.instanceReporting) {
57
66
  continue
58
67
  }
@@ -0,0 +1,79 @@
1
+ /*
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const Wrapper = require('./wrapper.js')
9
+ const getHostDetails = require('./utils/get-host-details.js')
10
+
11
+ const {
12
+ DB: DB_METRIC_CONSTANTS,
13
+ MONGODB: MONGODB_METRIC_CONSTANTS
14
+ } = require('#agentlib/metrics/names.js')
15
+
16
+ const ADMIN_COMMANDS = require('./utils/admin-commands.js')
17
+
18
+ /**
19
+ * This list is a list of all methods we are interested in instrumenting,
20
+ * regardless of client version. As we iterate through the list we will perform
21
+ * a presence check for each method.
22
+ *
23
+ * @type {string[]}
24
+ */
25
+ const CURSOR_METHODS = [
26
+ 'execute'
27
+ ]
28
+
29
+ module.exports = class BulkSubscriber extends Wrapper {
30
+ constructor({ agent, logger }) {
31
+ super({
32
+ agent,
33
+ logger,
34
+ channelName: 'nr_bulk',
35
+ packageName: 'mongodb',
36
+ system: MONGODB_METRIC_CONSTANTS.PREFIX
37
+ })
38
+ }
39
+
40
+ end(data, ctx) {
41
+ const { self: bulkOperation, arguments: args } = data
42
+ const [collection] = args
43
+ const collectionName = collection.collectionName
44
+ const details = getHostDetails(collection)
45
+ this.parameters = {
46
+ host: details.host,
47
+ port_path_or_id: details.port_path_or_id,
48
+ database_name: details.database_name,
49
+ product: this.system
50
+ }
51
+
52
+ for (const method of CURSOR_METHODS) {
53
+ if (typeof bulkOperation[method] !== 'function') {
54
+ continue
55
+ }
56
+
57
+ this.wrapDatabaseMethod(bulkOperation, method, {
58
+ getSegmentName: () => {
59
+ const operation = bulkOperation.isOrdered ? 'orderedBulk' : 'unorderedBulk'
60
+ return `${DB_METRIC_CONSTANTS.STATEMENT}/${this.system}/${collectionName}/${operation}/batch`
61
+ },
62
+ getRecorderContext: () => {
63
+ return {
64
+ operation: bulkOperation.isOrdered ? 'orderedBulk' : 'unorderedBulk',
65
+ collection: collectionName,
66
+ type: this.type
67
+ }
68
+ },
69
+ getSegmentAttributes: (method) => (
70
+ ADMIN_COMMANDS.includes(method)
71
+ ? { database_name: 'admin' }
72
+ : {}
73
+ )
74
+ })
75
+ }
76
+
77
+ return ctx
78
+ }
79
+ }
@@ -0,0 +1,113 @@
1
+ /*
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const Wrapper = require('./wrapper.js')
9
+ const getHostDetails = require('./utils/get-host-details.js')
10
+
11
+ const {
12
+ DB: DB_METRIC_CONSTANTS,
13
+ MONGODB: MONGODB_METRIC_CONSTANTS
14
+ } = require('#agentlib/metrics/names.js')
15
+
16
+ const ADMIN_COMMANDS = require('./utils/admin-commands.js')
17
+
18
+ /**
19
+ * This list is a list of all methods we are interested in instrumenting,
20
+ * regardless of client version. As we iterate through the list we will perform
21
+ * a presence check for each method.
22
+ *
23
+ * @type {string[]}
24
+ */
25
+ const CURSOR_METHODS = [
26
+ 'aggregate',
27
+ 'bulkWrite',
28
+ 'count',
29
+ 'createIndex',
30
+ 'deleteMany',
31
+ 'deleteOne',
32
+ 'distinct',
33
+ 'drop',
34
+ 'dropAllIndexes',
35
+ 'dropIndex',
36
+ 'ensureIndex',
37
+ 'findAndModify',
38
+ 'findAndRemove',
39
+ 'findOne',
40
+ 'findOneAndDelete',
41
+ 'findOneAndReplace',
42
+ 'findOneAndUpdate',
43
+ 'geoHaystackSearch',
44
+ 'geoNear',
45
+ 'group',
46
+ 'indexExists',
47
+ 'indexInformation',
48
+ 'indexes',
49
+ 'insert',
50
+ 'insertMany',
51
+ 'insertOne',
52
+ 'isCapped',
53
+ 'mapReduce',
54
+ 'options',
55
+ 'parallelCollectionScan',
56
+ 'reIndex',
57
+ 'remove',
58
+ 'rename',
59
+ 'replaceOne',
60
+ 'save',
61
+ 'stats',
62
+ 'update',
63
+ 'updateMany',
64
+ 'updateOne'
65
+ ]
66
+
67
+ module.exports = class CollectionSubscriber extends Wrapper {
68
+ constructor({ agent, logger }) {
69
+ super({
70
+ agent,
71
+ logger,
72
+ channelName: 'nr_collection',
73
+ packageName: 'mongodb',
74
+ system: MONGODB_METRIC_CONSTANTS.PREFIX
75
+ })
76
+ }
77
+
78
+ end(data, ctx) {
79
+ const { self: dbClient, arguments: args } = data
80
+ const [dbInstance, collectionName] = args
81
+ const details = getHostDetails(dbInstance)
82
+ this.parameters = {
83
+ host: details.host,
84
+ port_path_or_id: details.port_path_or_id,
85
+ database_name: details.database_name,
86
+ product: this.system
87
+ }
88
+
89
+ for (const method of CURSOR_METHODS) {
90
+ if (typeof dbClient[method] !== 'function') {
91
+ continue
92
+ }
93
+
94
+ this.wrapDatabaseMethod(dbClient, method, {
95
+ getSegmentName: (method) => `${DB_METRIC_CONSTANTS.STATEMENT}/${this.system}/${collectionName}/${method}`,
96
+ getRecorderContext: (method) => {
97
+ return {
98
+ operation: method,
99
+ collection: collectionName,
100
+ type: this.type
101
+ }
102
+ },
103
+ getSegmentAttributes: (method) => (
104
+ ADMIN_COMMANDS.includes(method)
105
+ ? { database_name: 'admin' }
106
+ : {}
107
+ )
108
+ })
109
+ }
110
+
111
+ return ctx
112
+ }
113
+ }
@@ -0,0 +1,72 @@
1
+ /*
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const modName = 'mongodb'
9
+
10
+ module.exports = {
11
+ [modName]: [
12
+ {
13
+ path: './mongodb/db.js',
14
+ instrumentations: [{
15
+ module: {
16
+ name: modName,
17
+ filePath: 'lib/db.js',
18
+ versionRange: '>=4.1.4'
19
+ },
20
+ channelName: 'nr_db',
21
+ functionQuery: {
22
+ className: 'Db'
23
+ }
24
+ }]
25
+ },
26
+
27
+ {
28
+ path: './mongodb/cursor.js',
29
+ instrumentations: [{
30
+ module: {
31
+ name: modName,
32
+ filePath: 'lib/cursor/abstract_cursor.js',
33
+ versionRange: '>=4.1.4'
34
+ },
35
+ channelName: 'nr_cursor',
36
+ functionQuery: {
37
+ className: 'AbstractCursor'
38
+ }
39
+ }]
40
+ },
41
+
42
+ {
43
+ path: './mongodb/collection.js',
44
+ instrumentations: [{
45
+ module: {
46
+ name: modName,
47
+ filePath: 'lib/collection.js',
48
+ versionRange: '>=4.1.4'
49
+ },
50
+ channelName: 'nr_collection',
51
+ functionQuery: {
52
+ className: 'Collection'
53
+ }
54
+ }]
55
+ },
56
+
57
+ {
58
+ path: './mongodb/bulk.js',
59
+ instrumentations: [{
60
+ module: {
61
+ name: modName,
62
+ filePath: 'lib/bulk/common.js',
63
+ versionRange: '>=4.1.4'
64
+ },
65
+ channelName: 'nr_bulk',
66
+ functionQuery: {
67
+ className: 'BulkOperationBase'
68
+ }
69
+ }]
70
+ }
71
+ ]
72
+ }
@@ -0,0 +1,76 @@
1
+ /*
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const Wrapper = require('./wrapper.js')
9
+ const getHostDetails = require('./utils/get-host-details.js')
10
+
11
+ const {
12
+ DB: DB_METRIC_CONSTANTS,
13
+ MONGODB: MONGODB_METRIC_CONSTANTS
14
+ } = require('#agentlib/metrics/names.js')
15
+
16
+ /**
17
+ * This list is a list of all methods we are interested in instrumenting,
18
+ * regardless of client version. As we iterate through the list we will perform
19
+ * a presence check for each method.
20
+ *
21
+ * @type {string[]}
22
+ */
23
+ const CURSOR_METHODS = [
24
+ 'count',
25
+ 'explain',
26
+ // `forEach` is for mongodb@4 through mongodb@7. It is slated to be
27
+ // removed in some version after 7.
28
+ 'forEach',
29
+ 'next',
30
+ 'nextObject',
31
+ 'toArray'
32
+ ]
33
+
34
+ module.exports = class CursorSubscriber extends Wrapper {
35
+ constructor({ agent, logger }) {
36
+ super({
37
+ agent,
38
+ logger,
39
+ channelName: 'nr_cursor',
40
+ packageName: 'mongodb',
41
+ system: MONGODB_METRIC_CONSTANTS.PREFIX
42
+ })
43
+ }
44
+
45
+ end(data, ctx) {
46
+ const { self: dbClient, arguments: args } = data
47
+ const [mongoClient, dbNamespace] = args
48
+ const { collection } = dbNamespace
49
+ const details = getHostDetails(mongoClient)
50
+ this.parameters = {
51
+ host: details.host,
52
+ port_path_or_id: details.port_path_or_id,
53
+ database_name: dbNamespace.db,
54
+ product: this.system
55
+ }
56
+
57
+ for (const method of CURSOR_METHODS) {
58
+ if (typeof dbClient[method] !== 'function') {
59
+ continue
60
+ }
61
+
62
+ this.wrapDatabaseMethod(dbClient, method, {
63
+ getSegmentName: (method) => `${DB_METRIC_CONSTANTS.STATEMENT}/${this.system}/${collection}/${method}`,
64
+ getRecorderContext: (method) => {
65
+ return {
66
+ operation: method,
67
+ collection,
68
+ type: this.type
69
+ }
70
+ }
71
+ })
72
+ }
73
+
74
+ return ctx
75
+ }
76
+ }