newrelic 7.5.2 → 8.3.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 (158) hide show
  1. package/NEWS.md +4989 -4886
  2. package/THIRD_PARTY_NOTICES.md +625 -216
  3. package/api.js +105 -174
  4. package/bin/test-naming-rules.js +38 -32
  5. package/bin/tracetractor +0 -1
  6. package/index.js +12 -18
  7. package/lib/agent.js +108 -111
  8. package/lib/aggregators/base-aggregator.js +1 -1
  9. package/lib/aggregators/event-aggregator.js +6 -6
  10. package/lib/attributes.js +12 -13
  11. package/lib/collector/api.js +32 -42
  12. package/lib/collector/facts.js +67 -65
  13. package/lib/collector/http-agents.js +30 -27
  14. package/lib/collector/parse-response.js +1 -2
  15. package/lib/collector/remote-method.js +43 -36
  16. package/lib/collector/serverless.js +13 -18
  17. package/lib/collector/ssl/certificates.js +1056 -1056
  18. package/lib/config/attribute-filter.js +90 -82
  19. package/lib/config/default.js +30 -18
  20. package/lib/config/env.js +17 -12
  21. package/lib/config/harvest-config-validator.js +1 -1
  22. package/lib/config/index.js +160 -177
  23. package/lib/config/merge-server-config.js +7 -5
  24. package/lib/custom-events/custom-event-aggregator.js +1 -1
  25. package/lib/db/parse-sql.js +6 -9
  26. package/lib/db/parsed-statement.js +6 -13
  27. package/lib/db/query-parsers/sql.js +6 -9
  28. package/lib/db/query-sample.js +7 -3
  29. package/lib/db/query-trace-aggregator.js +12 -12
  30. package/lib/db/utils.js +1 -1
  31. package/lib/environment.js +173 -138
  32. package/lib/errors/error-collector.js +18 -31
  33. package/lib/errors/error-event-aggregator.js +1 -1
  34. package/lib/errors/error-trace-aggregator.js +2 -3
  35. package/lib/errors/helper.js +21 -30
  36. package/lib/errors/index.js +16 -28
  37. package/lib/feature_flags.js +5 -8
  38. package/lib/grpc/connection/states.js +6 -6
  39. package/lib/grpc/connection.js +35 -46
  40. package/lib/header-attributes.js +49 -53
  41. package/lib/instrumentation/amqplib.js +8 -17
  42. package/lib/instrumentation/bluebird.js +61 -20
  43. package/lib/instrumentation/cassandra-driver.js +2 -2
  44. package/lib/instrumentation/connect.js +4 -3
  45. package/lib/instrumentation/core/async_hooks.js +11 -4
  46. package/lib/instrumentation/core/child_process.js +20 -28
  47. package/lib/instrumentation/core/crypto.js +1 -7
  48. package/lib/instrumentation/core/dns.js +3 -7
  49. package/lib/instrumentation/core/domain.js +2 -8
  50. package/lib/instrumentation/core/fs.js +22 -37
  51. package/lib/instrumentation/core/globals.js +3 -7
  52. package/lib/instrumentation/core/http-outbound.js +15 -40
  53. package/lib/instrumentation/core/http.js +29 -63
  54. package/lib/instrumentation/core/inspector.js +6 -10
  55. package/lib/instrumentation/core/net.js +3 -1
  56. package/lib/instrumentation/core/timers.js +17 -24
  57. package/lib/instrumentation/core/zlib.js +2 -10
  58. package/lib/instrumentation/express.js +4 -2
  59. package/lib/instrumentation/fastify/spec-builders.js +5 -0
  60. package/lib/instrumentation/fastify.js +37 -10
  61. package/lib/instrumentation/hapi/hapi-17.js +9 -10
  62. package/lib/instrumentation/hapi.js +15 -15
  63. package/lib/instrumentation/ioredis.js +1 -2
  64. package/lib/instrumentation/memcached.js +39 -43
  65. package/lib/instrumentation/mongodb/common.js +218 -0
  66. package/lib/instrumentation/mongodb/constants.js +87 -0
  67. package/lib/instrumentation/mongodb/v2-mongo.js +110 -0
  68. package/lib/instrumentation/mongodb/v3-mongo.js +80 -0
  69. package/lib/instrumentation/mongodb/v4-mongo.js +90 -0
  70. package/lib/instrumentation/mongodb.js +20 -312
  71. package/lib/instrumentation/mysql.js +33 -26
  72. package/lib/instrumentation/oracle.js +10 -14
  73. package/lib/instrumentation/pg.js +30 -21
  74. package/lib/instrumentation/promise.js +20 -17
  75. package/lib/instrumentation/redis.js +30 -30
  76. package/lib/instrumentation/when.js +9 -1
  77. package/lib/instrumentations.js +24 -24
  78. package/lib/logger.js +1 -1
  79. package/lib/metrics/index.js +37 -17
  80. package/lib/metrics/mapper.js +9 -9
  81. package/lib/metrics/metric-aggregator.js +5 -10
  82. package/lib/metrics/names.js +8 -7
  83. package/lib/metrics/normalizer/rule.js +6 -7
  84. package/lib/metrics/normalizer/tx_segment.js +17 -13
  85. package/lib/metrics/normalizer.js +30 -33
  86. package/lib/metrics/recorders/custom.js +3 -1
  87. package/lib/metrics/recorders/distributed-trace.js +3 -18
  88. package/lib/metrics/recorders/generic.js +3 -2
  89. package/lib/metrics/recorders/http.js +4 -6
  90. package/lib/metrics/recorders/http_external.js +1 -3
  91. package/lib/metrics/recorders/message-transaction.js +2 -12
  92. package/lib/metrics/recorders/other.js +2 -12
  93. package/lib/parse-proc-cpuinfo.js +11 -16
  94. package/lib/parse-proc-meminfo.js +5 -3
  95. package/lib/prioritized-attributes.js +12 -17
  96. package/lib/priority-queue.js +1 -1
  97. package/lib/proxy/grpc.js +1 -1
  98. package/lib/reservoir.js +9 -3
  99. package/lib/sampler.js +12 -13
  100. package/lib/serverless/api-gateway.js +3 -3
  101. package/lib/serverless/aws-lambda.js +28 -37
  102. package/lib/shim/conglomerate-shim.js +4 -4
  103. package/lib/shim/datastore-shim.js +29 -38
  104. package/lib/shim/index.js +8 -9
  105. package/lib/shim/message-shim.js +111 -113
  106. package/lib/shim/promise-shim.js +18 -13
  107. package/lib/shim/shim.js +72 -66
  108. package/lib/shim/specs/index.js +18 -19
  109. package/lib/shim/transaction-shim.js +26 -36
  110. package/lib/shim/webframework-shim.js +25 -35
  111. package/lib/shimmer.js +184 -166
  112. package/lib/spans/create-span-event-aggregator.js +24 -44
  113. package/lib/spans/span-context.js +4 -10
  114. package/lib/spans/span-event-aggregator.js +68 -13
  115. package/lib/spans/span-event.js +5 -7
  116. package/lib/spans/span-streamer.js +10 -18
  117. package/lib/spans/streaming-span-event-aggregator.js +1 -1
  118. package/lib/spans/streaming-span-event.js +4 -4
  119. package/lib/stats/apdex.js +5 -11
  120. package/lib/stats/index.js +12 -17
  121. package/lib/system-info.js +7 -3
  122. package/lib/timer.js +30 -16
  123. package/lib/transaction/dt-payload.js +1 -1
  124. package/lib/transaction/handle.js +19 -19
  125. package/lib/transaction/index.js +94 -112
  126. package/lib/transaction/name-state.js +5 -6
  127. package/lib/transaction/trace/aggregator.js +78 -73
  128. package/lib/transaction/trace/exclusive-time-calculator.js +5 -9
  129. package/lib/transaction/trace/index.js +26 -24
  130. package/lib/transaction/trace/segment.js +39 -56
  131. package/lib/transaction/tracecontext.js +37 -45
  132. package/lib/transaction/tracer/index.js +79 -44
  133. package/lib/transaction/transaction-event-aggregator.js +29 -31
  134. package/lib/uninstrumented.js +4 -5
  135. package/lib/util/attribute-types.js +1 -5
  136. package/lib/util/byte-limit.js +0 -1
  137. package/lib/util/cat.js +5 -13
  138. package/lib/util/codec.js +8 -4
  139. package/lib/util/deep-equal.js +48 -18
  140. package/lib/util/flatten.js +5 -2
  141. package/lib/util/hashes.js +2 -2
  142. package/lib/util/label-parser.js +7 -13
  143. package/lib/util/logger.js +31 -13
  144. package/lib/util/process-version.js +1 -1
  145. package/lib/util/properties.js +1 -1
  146. package/lib/util/sql/obfuscate.js +7 -17
  147. package/lib/util/stream-sink.js +2 -3
  148. package/lib/util/urltils.js +9 -13
  149. package/lib/utilization/aws-info.js +2 -3
  150. package/lib/utilization/azure-info.js +32 -30
  151. package/lib/utilization/common.js +5 -4
  152. package/lib/utilization/docker-info.js +4 -6
  153. package/lib/utilization/gcp-info.js +36 -35
  154. package/lib/utilization/index.js +1 -1
  155. package/lib/utilization/kubernetes-info.js +2 -2
  156. package/lib/utilization/pcf-info.js +4 -4
  157. package/package.json +32 -18
  158. package/stub_api.js +8 -6
@@ -29,51 +29,47 @@ module.exports = function initialize(agent, memcached, moduleName, shim) {
29
29
  }
30
30
 
31
31
  shim.setDatastore(shim.MEMCACHED)
32
- shim.recordOperation(
33
- proto,
34
- 'command',
35
- function commandWrapper(shim, original, name, args) {
36
- // The `command` method takes two arguments: a query generator and a server
37
- // address. The query generator returns a simple object describing the
38
- // memcached call. The server parameter is only provided for multi-calls.
39
- // When not provided, it can be derived from the key being interacted with.
40
- var metacall = args[0]()
41
- var server = args[1]
42
- var keys = wrapKeys(metacall)
43
- var parameters = Object.create(null)
44
- try {
45
- parameters.key = stringify(keys[0])
46
- } catch (err) {
47
- shim.logger.debug(err, 'Unable to stringify memcache key')
48
- parameters.key = '<unknown>'
49
- }
32
+ shim.recordOperation(proto, 'command', function commandWrapper(shim, original, name, args) {
33
+ // The `command` method takes two arguments: a query generator and a server
34
+ // address. The query generator returns a simple object describing the
35
+ // memcached call. The server parameter is only provided for multi-calls.
36
+ // When not provided, it can be derived from the key being interacted with.
37
+ var metacall = args[0]()
38
+ var server = args[1]
39
+ var keys = wrapKeys(metacall)
40
+ var parameters = Object.create(null)
41
+ try {
42
+ parameters.key = stringify(keys[0])
43
+ } catch (err) {
44
+ shim.logger.debug(err, 'Unable to stringify memcache key')
45
+ parameters.key = '<unknown>'
46
+ }
50
47
 
51
- // Capture connection info for datastore instance metric.
52
- var location = null
53
- if (typeof server === 'string') {
54
- location = server
55
- } else if (this.HashRing && this.HashRing.get && metacall.key) {
56
- location = this.HashRing.get(metacall.key)
57
- }
58
- if (location) {
59
- location = location.split(':')
60
- parameters.host = location[0]
61
- parameters.port_path_or_id = location[1]
62
- }
48
+ // Capture connection info for datastore instance metric.
49
+ var location = null
50
+ if (typeof server === 'string') {
51
+ location = server
52
+ } else if (this.HashRing && this.HashRing.get && metacall.key) {
53
+ location = this.HashRing.get(metacall.key)
54
+ }
55
+ if (location) {
56
+ location = location.split(':')
57
+ parameters.host = location[0]
58
+ parameters.port_path_or_id = location[1]
59
+ }
63
60
 
64
- // rewrap the metacall for the command object
65
- args[0] = function rewrapped() {
66
- return metacall
67
- }
61
+ // rewrap the metacall for the command object
62
+ args[0] = function rewrapped() {
63
+ return metacall
64
+ }
68
65
 
69
- // finally, execute the original command
70
- return {
71
- name: metacall.type || 'Unknown',
72
- callback: function wrapCallback(shim, fn, fnName, opSegment) {
73
- shim.bindCallbackSegment(metacall, 'callback', opSegment)
74
- },
75
- parameters: parameters
76
- }
66
+ // finally, execute the original command
67
+ return {
68
+ name: metacall.type || 'Unknown',
69
+ callback: function wrapCallback(shim, fn, fnName, opSegment) {
70
+ shim.bindCallbackSegment(metacall, 'callback', opSegment)
71
+ },
72
+ parameters: parameters
77
73
  }
78
- )
74
+ })
79
75
  }
@@ -0,0 +1,218 @@
1
+ /*
2
+ * Copyright 2021 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const { CURSOR_OPS, COLLECTION_OPS, DB_OPS } = require('./constants')
8
+ const common = module.exports
9
+ common.NR_ATTRS = Symbol('NR_ATTRS')
10
+
11
+ /**
12
+ * Instruments all methods from constants.CURSOR_OPS on a given
13
+ * cursor class
14
+ *
15
+ * @param {Shim} shim
16
+ * @param {Cursor} Cursor
17
+ */
18
+ common.instrumentCursor = function instrumentCursor(shim, Cursor) {
19
+ if (Cursor && Cursor.prototype) {
20
+ const proto = Cursor.prototype
21
+ for (let i = 0; i < CURSOR_OPS.length; i++) {
22
+ shim.recordQuery(proto, CURSOR_OPS[i], common.makeQueryDescFunc(shim, CURSOR_OPS[i]))
23
+ }
24
+
25
+ shim.recordQuery(proto, 'each', common.makeQueryDescFunc(shim, 'each'))
26
+ shim.recordOperation(proto, 'pipe', { opaque: true })
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Instruments all methods from constants.COLLECTION_OPS on
32
+ * the Collection class
33
+ *
34
+ * @param {Shim} shim
35
+ * @param {Collection} Collection
36
+ */
37
+ common.instrumentCollection = function instrumentCollection(shim, Collection) {
38
+ if (Collection && Collection.prototype) {
39
+ const proto = Collection.prototype
40
+ for (let i = 0; i < COLLECTION_OPS.length; i++) {
41
+ shim.recordQuery(proto, COLLECTION_OPS[i], common.makeQueryDescFunc(shim, COLLECTION_OPS[i]))
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Instruments all methods from constants.DB_OPS on
48
+ * the Db class.
49
+ *
50
+ * @param {Object} params
51
+ * @param {Shim} params.shim
52
+ * @param {Db} params.Db
53
+ */
54
+ common.instrumentDb = function instrumentDb(shim, Db) {
55
+ if (Db && Db.prototype) {
56
+ const proto = Db.prototype
57
+ shim.recordOperation(proto, DB_OPS, { callback: shim.LAST, opaque: true })
58
+ // link to client.connect(removed in v4.0)
59
+ shim.recordOperation(Db, 'connect', { callback: shim.LAST })
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Sets up the desc for all instrumented query methods
65
+ * @param {Shim} shim
66
+ * @param {string} methodName
67
+ */
68
+ common.makeQueryDescFunc = function makeQueryDescFunc(shim, methodName) {
69
+ if (methodName === 'each') {
70
+ return function eachDescFunc() {
71
+ const parameters = getInstanceAttributeParameters(shim, this)
72
+ return { query: methodName, parameters, rowCallback: shim.LAST, opaque: true }
73
+ }
74
+ }
75
+
76
+ return function queryDescFunc() {
77
+ // segment name does not actually use query string
78
+ // method name is set as query so the query parser has access to the op name
79
+ const parameters = getInstanceAttributeParameters(shim, this)
80
+ return { query: methodName, parameters, promise: true, callback: shim.LAST, opaque: true }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Sets up a listener for `started` on instrumenter(mongo APM). This applies to
86
+ * mongo <4. The listener adds the following attributes to the active segment:
87
+ * host, port_path_or_id, and database_name
88
+ *
89
+ * @param {Shim} shim
90
+ * @param {Instrumentation} instrumenter instance of mongo APM class
91
+ */
92
+ common.captureAttributesOnStarted = function captureAttributesOnStarted(shim, instrumenter) {
93
+ instrumenter.on('started', function onMongoEventStarted(evnt) {
94
+ // This assumes that this `started` event is fired _after_ our wrapper
95
+ // starts and creates the segment. We perform a check of the segment name
96
+ // out of an excess of caution.
97
+ const connId = evnt.connectionId
98
+ if (connId) {
99
+ // used in v3 when connection is a cluster pool
100
+ if (typeof connId === 'number') {
101
+ setHostPort(shim, evnt.address, evnt.databaseName, this.$MongoClient)
102
+ // used in v3 when connection is to 1 host
103
+ } else if (typeof connId === 'string') {
104
+ setHostPort(shim, connId, evnt.databaseName)
105
+ // v2 contains `domainSocket`, get socket connection from `host`
106
+ } else if (connId.domainSocket) {
107
+ shim.captureInstanceAttributes('localhost', connId.host, evnt.databaseName)
108
+ // v2 remote connection get `host` `port` from respective properties
109
+ } else {
110
+ shim.captureInstanceAttributes(connId.host, connId.port, evnt.databaseName)
111
+ }
112
+ }
113
+ })
114
+ }
115
+
116
+ /**
117
+ * Extracts the host and port from a connection string
118
+ * This also handles if connection string is a domain socket
119
+ * Mongo sticks the path to the domain socket in the "host" slot, but we
120
+ * want it in the "port", so if we have a domain socket we need to change
121
+ * the order of our parameters.
122
+ *
123
+ * @param {Shim} shim
124
+ * @param {string} connStr
125
+ * @param {string} db database name
126
+ * @param {Object} client mongo client instance
127
+ */
128
+ function setHostPort(shim, connStr, db, client) {
129
+ const parts = connStr.split(':')
130
+ // in v3 when running with a cluster of socket connections
131
+ // the address is `undefined:undefined`. we will instead attempt
132
+ // to get connection details from the client symbol NR_ATTRS
133
+ // added in `lib/instrumentation/mongodb/v3-mongo` when a client connects
134
+ // with a URL string
135
+ if (parts.includes('undefined')) {
136
+ try {
137
+ const attrs = client[common.NR_ATTRS]
138
+ const socket = decodeURIComponent(attrs.split(',')[0].split('mongodb://')[1])
139
+ shim.captureInstanceAttributes('localhost', socket, db)
140
+ } catch (err) {
141
+ shim.logger.debug(err, 'Could not extract host/port from mongo command')
142
+ }
143
+ // connected using domain socket but the "host"(e.g: /path/to/mongo-socket-port.sock)
144
+ } else if (parts.length && parts[0][0] === '/') {
145
+ shim.captureInstanceAttributes('localhost', parts[0], db)
146
+ } else {
147
+ shim.captureInstanceAttributes(parts[0], parts[1], db)
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Get the database_name, host, port_path_or_id
153
+ * for the query segment. v4 refactored where the toplogy is stored.
154
+ * You can now get the details via the client obj that's deeply nested
155
+ * See: https://github.com/mongodb/node-mongodb-native/pull/2594/files#diff-1d214e57ddda9095d296e5700ebce701333bfefcf417e234c584d14091b2f50dR168
156
+ */
157
+ function getInstanceAttributeParameters(shim, obj) {
158
+ if (obj.s && obj.s.db && obj.s.topology) {
159
+ shim.logger.trace('Adding datastore instance attributes from obj.s.db + obj.s.topology')
160
+ const databaseName = obj.s.db.databaseName || null
161
+ const topology = obj.s.topology
162
+ if (topology.s && topology.s.options) {
163
+ return doCapture(topology.s.options, databaseName)
164
+ }
165
+ } else if (
166
+ obj.s &&
167
+ obj.s.db &&
168
+ obj.s.db.s &&
169
+ obj.s.db.s.client &&
170
+ obj.s.db.s.client.s &&
171
+ obj.s.db.s.client.s.options &&
172
+ obj.s.db.s.client.s.options.hosts &&
173
+ obj.s.db.s.client.s.options.hosts.length
174
+ ) {
175
+ // hosts is an array but we will always pull the first for consistency
176
+ let [{ host, port, socketPath }] = obj.s.db.s.client.s.options.hosts
177
+
178
+ if (socketPath) {
179
+ port = socketPath
180
+ host = 'localhost'
181
+ }
182
+ return {
183
+ host,
184
+ port_path_or_id: port,
185
+ database_name: obj.s.db.databaseName
186
+ }
187
+ }
188
+
189
+ shim.logger.trace('Could not find datastore instance attributes.')
190
+ return {
191
+ host: null,
192
+ port_path_or_id: null,
193
+ database_name: null
194
+ }
195
+
196
+ function doCapture(conf, database) {
197
+ let host = null
198
+ let port = null
199
+
200
+ // servers is an array but we will always pull the first for consistency
201
+ if (conf.servers && conf.servers.length) {
202
+ ;[{ host, port }] = conf.servers
203
+ }
204
+
205
+ // host is a domain socket. set host as localhost and use the domain
206
+ // socket host as the port
207
+ if (host && host.endsWith('.sock')) {
208
+ port = host
209
+ host = 'localhost'
210
+ }
211
+
212
+ return {
213
+ host: host,
214
+ port_path_or_id: port,
215
+ database_name: database
216
+ }
217
+ }
218
+ }
@@ -0,0 +1,87 @@
1
+ /*
2
+ * Copyright 2020 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const DB_OPS = [
9
+ 'addUser',
10
+ 'authenticate',
11
+ 'collection',
12
+ 'collectionNames',
13
+ 'collections',
14
+ 'command',
15
+ 'createCollection',
16
+ 'createIndex',
17
+ 'cursorInfo',
18
+ 'dereference',
19
+ 'dropCollection',
20
+ 'dropDatabase',
21
+ 'dropIndex',
22
+ 'ensureIndex',
23
+ 'eval',
24
+ 'executeDbAdminCommand',
25
+ 'indexInformation',
26
+ 'logout',
27
+ 'open',
28
+ 'reIndex',
29
+ 'removeUser',
30
+ 'renameCollection',
31
+ 'stats',
32
+ '_executeInsertCommand',
33
+ '_executeQueryCommand'
34
+ ]
35
+
36
+ const COLLECTION_OPS = [
37
+ 'aggregate',
38
+ 'bulkWrite',
39
+ 'count',
40
+ 'createIndex',
41
+ 'deleteMany',
42
+ 'deleteOne',
43
+ 'distinct',
44
+ 'drop',
45
+ 'dropAllIndexes',
46
+ 'dropIndex',
47
+ 'ensureIndex',
48
+ 'findAndModify',
49
+ 'findAndRemove',
50
+ 'findOne',
51
+ 'findOneAndDelete',
52
+ 'findOneAndReplace',
53
+ 'findOneAndUpdate',
54
+ 'geoHaystackSearch',
55
+ 'geoNear',
56
+ 'group',
57
+ 'indexes',
58
+ 'indexExists',
59
+ 'indexInformation',
60
+ 'insert',
61
+ 'insertMany',
62
+ 'insertOne',
63
+ 'isCapped',
64
+ 'mapReduce',
65
+ 'options',
66
+ 'parallelCollectionScan',
67
+ 'reIndex',
68
+ 'remove',
69
+ 'rename',
70
+ 'replaceOne',
71
+ 'save',
72
+ 'stats',
73
+ 'update',
74
+ 'updateMany',
75
+ 'updateOne'
76
+ ]
77
+
78
+ const GRID_OPS = ['put', 'get', 'delete']
79
+
80
+ const CURSOR_OPS = ['nextObject', 'next', 'toArray', 'count', 'explain']
81
+
82
+ module.exports = {
83
+ COLLECTION_OPS,
84
+ CURSOR_OPS,
85
+ DB_OPS,
86
+ GRID_OPS
87
+ }
@@ -0,0 +1,110 @@
1
+ /*
2
+ * Copyright 2021 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const { captureAttributesOnStarted, makeQueryDescFunc } = require('./common')
9
+
10
+ /**
11
+ * parser used to grab the collection and operation
12
+ * from a running query
13
+ *
14
+ * @param {Object} operation
15
+ */
16
+ function queryParser(operation) {
17
+ let collection = this.collectionName || 'unknown'
18
+ if (this.ns) {
19
+ collection = this.ns.split(/\./)[1] || collection
20
+ }
21
+
22
+ return { operation, collection }
23
+ }
24
+ /**
25
+ * Registers relevant instrumentation for mongo <= 3.0.6
26
+ * and >= 2. This relies on the built-in "APM" hook points
27
+ * to instrument their provided objects as well as sets
28
+ * up a listener for when commands start to properly
29
+ * add necessary attributes to segments
30
+ *
31
+ * @param {Shim} shim
32
+ * @param {Object} mongodb resolved package
33
+ */
34
+ module.exports = function instrument(shim, mongodb) {
35
+ shim.setParser(queryParser)
36
+
37
+ const recordDesc = {
38
+ Gridstore: {
39
+ isQuery: false,
40
+ makeDescFunc: function makeGridDesc(opName) {
41
+ return { name: 'GridFS-' + opName, callback: shim.LAST }
42
+ }
43
+ },
44
+ OrderedBulkOperation: { isQuery: true, makeDescFunc: makeQueryDescFunc },
45
+ UnorderedBulkOperation: { isQuery: true, makeDescFunc: makeQueryDescFunc },
46
+ CommandCursor: { isQuery: true, makeDescFunc: makeQueryDescFunc },
47
+ AggregationCursor: { isQuery: true, makeDescFunc: makeQueryDescFunc },
48
+ Cursor: { isQuery: true, makeDescFunc: makeQueryDescFunc },
49
+ Collection: { isQuery: true, makeDescFunc: makeQueryDescFunc },
50
+ Db: {
51
+ isQuery: false,
52
+ makeDescFunc: function makeDbDesc() {
53
+ return { callback: shim.LAST }
54
+ }
55
+ }
56
+ }
57
+
58
+ // instrument using the apm api
59
+ const instrumenter = mongodb.instrument(Object.create(null), instrumentModules)
60
+ captureAttributesOnStarted(shim, instrumenter)
61
+
62
+ /**
63
+ * Every module groups instrumentations by their
64
+ * promise, callback, return permutations
65
+ * Iterate over permutations and properly
66
+ * wrap depending on the `recordDesc` above
67
+ * See: https://github.com/mongodb/node-mongodb-native/blob/v3.0.5/lib/collection.js#L384
68
+ */
69
+ function instrumentModules(_, modules) {
70
+ modules.forEach((module) => {
71
+ const { obj, instrumentations, name } = module
72
+ instrumentations.forEach((meta) => {
73
+ applyInstrumentation(name, obj, meta)
74
+ })
75
+ })
76
+ }
77
+
78
+ /**
79
+ * Iterate over methods on object and lookup in `recordDesc` to decide
80
+ * if it needs to be wrapped as an operation or query
81
+ *
82
+ * @param {string} objectName name of class getting instrumented
83
+ * @param {Object} object reference to the class getting instrumented
84
+ * @param {Define} meta describes the methods and if they are callbacks
85
+ * promises, and return values
86
+ */
87
+ function applyInstrumentation(objectName, object, meta) {
88
+ const { methods, options } = meta
89
+ if (options.callback) {
90
+ methods.forEach((method) => {
91
+ const { isQuery, makeDescFunc } = recordDesc[objectName]
92
+ const proto = object.prototype
93
+ if (isQuery) {
94
+ shim.recordQuery(proto, method, makeDescFunc(shim, method))
95
+ } else if (isQuery === false) {
96
+ // could be unset
97
+ shim.recordOperation(proto, method, makeDescFunc(shim, method))
98
+ } else {
99
+ shim.logger.trace('No wrapping method found for %s', objectName)
100
+ }
101
+ })
102
+ }
103
+
104
+ // the cursor object implements Readable stream and internally calls nextObject on
105
+ // each read, in which case we do not want to record each nextObject() call
106
+ if (/Cursor$/.test(objectName)) {
107
+ shim.recordOperation(object.prototype, 'pipe')
108
+ }
109
+ }
110
+ }
@@ -0,0 +1,80 @@
1
+ /*
2
+ * Copyright 2021 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const {
9
+ captureAttributesOnStarted,
10
+ instrumentCollection,
11
+ instrumentCursor,
12
+ instrumentDb,
13
+ NR_ATTRS
14
+ } = require('./common')
15
+
16
+ /**
17
+ * parser used to grab the collection and operation
18
+ * on every mongo operation
19
+ *
20
+ * @param {Object} operation
21
+ */
22
+ function queryParser(operation) {
23
+ let collection = this.collectionName || 'unknown'
24
+ // in v3.3.0 aggregate commands added the collection
25
+ // to target
26
+ if (this.operation && this.operation.target) {
27
+ collection = this.operation.target
28
+ } else if (this.ns) {
29
+ collection = this.ns.split(/\./)[1] || collection
30
+ }
31
+ return { operation, collection }
32
+ }
33
+
34
+ /**
35
+ * Records the `mongo.MongoClient.connect` operations. It also adds the first arg of connect(url)
36
+ * to a Symbol on the MongoClient to be used later to extract the host/port in cases where the topology
37
+ * is a cluster of domain sockets
38
+ *
39
+ * @param {Shim} shim
40
+ * @param {Object} mongodb resolved package
41
+ */
42
+ function instrumentClient(shim, mongodb) {
43
+ shim.recordOperation(mongodb.MongoClient, 'connect', function wrappedConnect(shim, _, __, args) {
44
+ // Add the connection url to the MongoClient to retrieve later in the `lib/instrumentation/mongo/common`
45
+ // captureAttributesOnStarted listener
46
+ this[NR_ATTRS] = args[0]
47
+ return { callback: shim.LAST }
48
+ })
49
+ }
50
+
51
+ /**
52
+ * Registers relevant instrumentation for mongo >= 3.0.6
53
+ * In 3.0.6 they refactored their "APM" module which removed
54
+ * a lot of niceities around instrumentation classes.
55
+ * see: https://github.com/mongodb/node-mongodb-native/pull/1675/files
56
+ * This reverts back to instrumenting pre-canned methods on classes
57
+ * as well as sets up a listener for when commands start to properly
58
+ * add necessary attributes to segments
59
+ *
60
+ * @param {Shim} shim
61
+ * @param {Object} mongodb resolved package
62
+ */
63
+ module.exports = function instrument(shim, mongodb) {
64
+ shim.setParser(queryParser)
65
+ instrumentClient(shim, mongodb)
66
+ const instrumenter = mongodb.instrument(Object.create(null), () => {})
67
+ captureAttributesOnStarted(shim, instrumenter)
68
+ instrumentCursor(shim, mongodb.Cursor)
69
+ instrumentCursor(shim, shim.require('./lib/aggregation_cursor'))
70
+ instrumentCursor(shim, shim.require('./lib/command_cursor'))
71
+ instrumentCollection(shim, mongodb.Collection)
72
+ instrumentDb(shim, mongodb.Db)
73
+
74
+ // calling instrument sets up listeners for a few events
75
+ // we should restore this on unload to avoid leaking
76
+ // event emitters
77
+ shim.agent.once('unload', function uninstrumentMongo() {
78
+ instrumenter.uninstrument()
79
+ })
80
+ }
@@ -0,0 +1,90 @@
1
+ /*
2
+ * Copyright 2021 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const { instrumentCollection, instrumentCursor, instrumentDb } = require('./common')
9
+
10
+ /**
11
+ * parser used to grab the collection and operation
12
+ * from a running query
13
+ *
14
+ * @param {Object} operation
15
+ */
16
+ function queryParser(operation) {
17
+ let collection = this.collectionName || 'unknown'
18
+
19
+ // cursor methods have collection on namespace.collection
20
+ if (this.namespace && this.namespace.collection) {
21
+ collection = this.namespace.collection
22
+ }
23
+
24
+ return { operation, collection }
25
+ }
26
+
27
+ /**
28
+ * `commandStarted` handler used to
29
+ * update host, port and database_name
30
+ * on segment attributes
31
+ *
32
+ * @param {Shim} shim
33
+ * @param {CommandStartedEvent} evnt
34
+ */
35
+ function cmdStartedHandler(shim, evnt) {
36
+ if (evnt.connectionId) {
37
+ let [host, port] = evnt.address.split(':')
38
+
39
+ // connection via socket get port from 1st host
40
+ // socketPath property
41
+ // which looks like mongodb:///tmp/mongodb-27017.sock"
42
+ if (['undefined'].includes(host, port)) {
43
+ host = 'localhost'
44
+ const hosts = this.s.options.hosts
45
+ if (hosts && hosts.length && hosts[0].socketPath) {
46
+ port = hosts[0].socketPath
47
+ }
48
+ } else if (host === '127.0.0.1') {
49
+ host = 'localhost'
50
+ }
51
+
52
+ shim.captureInstanceAttributes(host, port, evnt.databaseName)
53
+ }
54
+ }
55
+
56
+ /**
57
+ * function executed when client.connect is called
58
+ * enable APM(monitorCommands) and add the
59
+ * `commandStarted` listener
60
+ *
61
+ * @param {Shim} shim
62
+ */
63
+ function wrapConnect(shim) {
64
+ this.monitorCommands = true
65
+ this.on('commandStarted', cmdStartedHandler.bind(this, shim))
66
+ return { callback: shim.LAST }
67
+ }
68
+
69
+ /**
70
+ * Wraps connect to record as operation but also to add a listener
71
+ * for `commandStarted`. This will be emitted before every command starts
72
+ * so we can properly update the segment attributes with a more accurate
73
+ * host/port/database name
74
+ *
75
+ * @param {Shim} shim
76
+ * @param {MongoClient} MongoClient reference
77
+ */
78
+ function instrumentMongoClient(shim, MongoClient) {
79
+ shim.recordOperation(MongoClient.prototype, 'connect', wrapConnect)
80
+ }
81
+
82
+ module.exports = function instrument(shim, mongodb) {
83
+ shim.setParser(queryParser)
84
+ instrumentMongoClient(shim, mongodb.MongoClient)
85
+ instrumentCursor(shim, mongodb.AbstractCursor)
86
+ instrumentCursor(shim, mongodb.FindCursor)
87
+ instrumentCursor(shim, mongodb.AggregationCursor)
88
+ instrumentCollection(shim, mongodb.Collection)
89
+ instrumentDb(shim, mongodb.Db)
90
+ }