newrelic 10.6.2 → 11.0.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.
package/index.js CHANGED
@@ -8,6 +8,7 @@
8
8
  // Record opening times before loading any other files.
9
9
  const preAgentTime = process.uptime()
10
10
  const agentStart = Date.now()
11
+ const { isMainThread } = require('worker_threads')
11
12
 
12
13
  // Load unwrapped core now to ensure it gets the freshest properties.
13
14
  require('./lib/util/unwrapped-core')
@@ -16,8 +17,14 @@ const featureFlags = require('./lib/feature_flags').prerelease
16
17
  const psemver = require('./lib/util/process-version')
17
18
  let logger = require('./lib/logger') // Gets re-loaded after initialization.
18
19
  const NAMES = require('./lib/metrics/names')
20
+ const isESMSupported = psemver.satisfies('>=16.2.0')
19
21
 
20
22
  const pkgJSON = require('./package.json')
23
+ if (!isMainThread) {
24
+ logger.warn('Using New Relic for Node.js in worker_threads is not supported. Not starting!')
25
+ return
26
+ }
27
+
21
28
  logger.info(
22
29
  'Using New Relic for Node.js. Agent version: %s; Node version: %s.',
23
30
  pkgJSON.version,
@@ -150,12 +157,12 @@ function createAgent(config) {
150
157
  }
151
158
 
152
159
  const shimmer = require('./lib/shimmer')
153
- shimmer.patchModule(agent)
154
160
  shimmer.bootstrapInstrumentation(agent)
155
161
 
156
162
  // Check for already loaded modules and warn about them.
157
163
  const uninstrumented = require('./lib/uninstrumented')
158
164
  uninstrumented.check(shimmer.registeredInstrumentations)
165
+ shimmer.registerHooks(agent)
159
166
 
160
167
  agent.start(function afterStart(error) {
161
168
  if (error) {
@@ -221,22 +228,33 @@ function recordFeatureFlagMetrics(agent) {
221
228
  * 3. require('newrelic')
222
229
  *
223
230
  * Then a supportability metric is loaded to decide.
224
- * Note: We already take care of scenario #2 in newrelic/esm-loader.mjs
225
231
  *
226
232
  * @param {Agent} agent active NR agent
227
233
  */
228
234
  function recordLoaderMetric(agent) {
229
- const isESM = agent.metrics.getMetric(NAMES.FEATURES.ESM.LOADER)
230
235
  let isDashR = false
231
236
 
232
237
  process.execArgv.forEach((arg, index) => {
233
238
  if (arg === '-r' && process.execArgv[index + 1] === 'newrelic') {
234
239
  agent.metrics.getOrCreateMetric(NAMES.FEATURES.CJS.PRELOAD).incrementCallCount()
235
240
  isDashR = true
241
+ } else if (
242
+ (arg === '--loader' || arg === '--experimental-loader') &&
243
+ process.execArgv[index + 1] === 'newrelic/esm-loader.mjs'
244
+ ) {
245
+ if (isESMSupported) {
246
+ agent.metrics.getOrCreateMetric(NAMES.FEATURES.ESM.LOADER).incrementCallCount()
247
+ } else {
248
+ agent.metrics.getOrCreateMetric(NAMES.FEATURES.ESM.UNSUPPORTED_LOADER)
249
+ logger.warn(
250
+ 'New Relic for Node.js ESM loader requires a version of Node >= v16.12.0; your version is %s. Instrumentation will not be registered.',
251
+ process.version
252
+ )
253
+ }
236
254
  }
237
255
  })
238
256
 
239
- if (!isESM && !isDashR) {
257
+ if (!isDashR) {
240
258
  agent.metrics.getOrCreateMetric(NAMES.FEATURES.CJS.REQUIRE).incrementCallCount()
241
259
  }
242
260
  }
@@ -738,14 +738,6 @@ defaultConfig.definition = () => ({
738
738
  formatter: boolean,
739
739
  default: true,
740
740
  env: 'NEW_RELIC_API_NOTICE_ERROR'
741
- },
742
- /**
743
- * Sets the path to custom ESM instrumentation
744
- */
745
- esm: {
746
- custom_instrumentation_entrypoint: {
747
- default: null
748
- }
749
741
  }
750
742
  },
751
743
  /**
@@ -5,8 +5,6 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const semver = require('semver')
9
-
10
8
  const logger = require('../logger')
11
9
 
12
10
  /**
@@ -18,16 +16,11 @@ const logger = require('../logger')
18
16
  * the current configuration.
19
17
  */
20
18
  function createContextManager(config) {
21
- if (config.feature_flag.async_local_context) {
22
- // TODO: Remove >=16 check when only support 16+. AsyncLocal became stable in 16.4.0
23
- if (semver.satisfies(process.version, '>=16.4.0')) {
24
- return createAsyncLocalContextManager(config)
25
- }
26
-
27
- logger.warn('The AsyncLocalContextManager is only supported on Node version 16.4.0 and later.')
19
+ if (config.feature_flag.legacy_context_manager) {
20
+ return createLegacyContextManager(config)
28
21
  }
29
22
 
30
- return createLegacyContextManager(config)
23
+ return createAsyncLocalContextManager(config)
31
24
  }
32
25
 
33
26
  function createAsyncLocalContextManager(config) {
@@ -38,13 +31,6 @@ function createAsyncLocalContextManager(config) {
38
31
  }
39
32
 
40
33
  function createLegacyContextManager(config) {
41
- if (config.logging.diagnostics) {
42
- logger.info('Using LegacyDiagnosticContextManager')
43
-
44
- const LegacyDiagnosticContextManager = require('./diagnostics/legacy-diagnostic-context-manager')
45
- return new LegacyDiagnosticContextManager(config)
46
- }
47
-
48
34
  logger.info('Using LegacyContextManager')
49
35
 
50
36
  const LegacyContextManager = require('./legacy-context-manager')
@@ -13,7 +13,7 @@ exports.prerelease = {
13
13
  undici_instrumentation: false,
14
14
  undici_async_tracking: true,
15
15
  unresolved_promise_cleanup: true,
16
- async_local_context: false
16
+ legacy_context_manager: false
17
17
  }
18
18
 
19
19
  // flags that are no longer used for released features
@@ -32,7 +32,8 @@ exports.released = [
32
32
  'dt_format_w3c',
33
33
  'fastify_instrumentation',
34
34
  'await_support',
35
- 'certificate_bundle'
35
+ 'certificate_bundle',
36
+ 'async_local_context'
36
37
  ]
37
38
 
38
39
  // flags that are no longer used for unreleased features
@@ -13,41 +13,6 @@ const parseSql = require('../../db/query-parsers/sql')
13
13
  const RAW_COMMANDS = ['executeRaw', 'queryRaw']
14
14
 
15
15
  const semver = require('semver')
16
- const { getSchema } = require('@mrleebo/prisma-ast')
17
-
18
- /**
19
- * The library we use to parse the prisma schema retains double quotes around
20
- * strings, and they need to be stripped
21
- *
22
- * @param {string} [str=''] string to strip double-quotes from
23
- * @returns {string} stripped string
24
- */
25
- function trimQuotes(str = '') {
26
- return str.match(/"(.*)"/)[1]
27
- }
28
-
29
- /**
30
- * You can set the connection string in schema as raw string,
31
- * env var mapping, or an override at client instantiation time.
32
- *
33
- * @param {*} url string/object value of url in datsource stanza of schema
34
- * @param {string} overrideUrl value of url in overrides at client instantiation
35
- * @returns {string} properly parsed connection url
36
- */
37
- function parseDataModelUrl(url, overrideUrl) {
38
- let parsedUrl = ''
39
-
40
- if (overrideUrl) {
41
- parsedUrl = overrideUrl
42
- } else if (typeof url === 'string') {
43
- parsedUrl = trimQuotes(url)
44
- } else if (url.name && url.name === 'env') {
45
- const envVar = trimQuotes(url.params[0])
46
- parsedUrl = process.env[envVar]
47
- }
48
-
49
- return parsedUrl
50
- }
51
16
 
52
17
  /**
53
18
  * Parses a connection string. Most database engines in prisma are SQL and all
@@ -148,22 +113,34 @@ function queryParser(query) {
148
113
  }
149
114
 
150
115
  /**
151
- * Extracts the prisma connection information from the engine. This used to use
152
- * prisma functions available on engine `getConfig` but that's no longer accessible.
153
- * Instead we went the route of parsing the schema DSL.
116
+ * Extracts connection string from parse datasource config
117
+ *
118
+ * @param {object} datasource active datasource
119
+ * @returns {string} connection string
120
+ */
121
+ function extractConnectionString(datasource = {}) {
122
+ return process.env[datasource?.url?.fromEnvVar] || datasource?.url?.value
123
+ }
124
+
125
+ /**
126
+ * Extracts the prisma connection information from the engine. This calls an internal
127
+ * package used to parse the prisma schema config.
154
128
  *
155
129
  * @param {object} client prisma client instance
156
- * @returns {Promise} returns prisma datasource connection configuration { provider, url }
130
+ * @returns {object} returns prisma datasource connection configuration { provider, url }
157
131
  */
158
132
  function extractPrismaDatasource(client) {
159
- const { datamodel, datasourceOverrides: overrides } = client._engine
160
- const schema = getSchema(datamodel)
161
- const datasource = schema.list.filter(({ type }) => type === 'datasource')[0]
162
- const urlData = datasource.assignments.filter(({ key }) => key === 'url')[0].value
163
- const url = parseDataModelUrl(urlData, overrides[datasource.name])
133
+ const { get_config: getConfig } = require('@prisma/prisma-fmt-wasm')
134
+ const options = JSON.stringify({
135
+ prismaSchema: client?._engine?.datamodel,
136
+ ignoreEnvVarErrors: true
137
+ })
138
+ const config = JSON.parse(getConfig(options))
139
+ const activeDatasource = config?.datasources?.[0]
140
+
164
141
  return {
165
- provider: trimQuotes(datasource.assignments.filter(({ key }) => key === 'provider')[0].value),
166
- url
142
+ provider: activeDatasource.provider,
143
+ url: extractConnectionString(activeDatasource)
167
144
  }
168
145
  }
169
146
 
@@ -7,34 +7,6 @@
7
7
 
8
8
  const url = require('url')
9
9
 
10
- module.exports.selfRegister = function selfRegister(shimmer) {
11
- shimmer.registerInstrumentation({
12
- moduleName: 'amqplib',
13
- type: 'message',
14
- onRequire: instrumentChannelAPI
15
- })
16
- shimmer.registerInstrumentation({
17
- moduleName: 'amqplib/channel_api',
18
- type: 'message',
19
- onRequire: instrumentChannelAPI
20
- })
21
- shimmer.registerInstrumentation({
22
- moduleName: 'amqplib/channel_api.js',
23
- type: 'message',
24
- onRequire: instrumentChannelAPI
25
- })
26
- shimmer.registerInstrumentation({
27
- moduleName: 'amqplib/callback_api',
28
- type: 'message',
29
- onRequire: instrumentCallbackAPI
30
- })
31
- shimmer.registerInstrumentation({
32
- moduleName: 'amqplib/callback_api.js',
33
- type: 'message',
34
- onRequire: instrumentCallbackAPI
35
- })
36
- }
37
-
38
10
  module.exports.instrumentPromiseAPI = instrumentChannelAPI
39
11
  module.exports.instrumentCallbackAPI = instrumentCallbackAPI
40
12
 
@@ -0,0 +1,20 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const amqplib = require('./amqplib')
8
+
9
+ module.exports = [
10
+ {
11
+ moduleName: 'amqplib/callback_api',
12
+ type: 'message',
13
+ onRequire: amqplib.instrumentCallbackAPI
14
+ },
15
+ {
16
+ moduleName: 'amqplib/channel_api',
17
+ type: 'message',
18
+ onRequire: amqplib.instrumentPromiseAPI
19
+ }
20
+ ]
@@ -11,7 +11,7 @@ const asyncHooks = require('async_hooks')
11
11
  module.exports = initialize
12
12
 
13
13
  function initialize(agent, shim) {
14
- if (agent.config.feature_flag.async_local_context) {
14
+ if (!agent.config.feature_flag.legacy_context_manager) {
15
15
  logger.debug(
16
16
  'New AsyncLocalStorage context enabled. Not enabling manual async_hooks or promise instrumentation'
17
17
  )
@@ -11,9 +11,9 @@ const Timers = require('timers')
11
11
  module.exports = initialize
12
12
 
13
13
  function initialize(agent, timers, _moduleName, shim) {
14
- const isAsyncLocalContext = agent.config.feature_flag.async_local_context
14
+ const isLegacyContext = agent.config.feature_flag.legacy_context_manager
15
15
 
16
- if (!isAsyncLocalContext) {
16
+ if (isLegacyContext) {
17
17
  instrumentProcessMethods(shim, process)
18
18
  instrumentSetImmediate(shim, [timers, global])
19
19
  }
@@ -11,27 +11,23 @@ const { DESTINATIONS } = require('../../config/attribute-filter')
11
11
  const DESTINATION = DESTINATIONS.TRANS_EVENT | DESTINATIONS.ERROR_EVENT
12
12
  const semver = require('semver')
13
13
 
14
- module.exports = function instrument(shim) {
15
- const grpcVersion = shim.require('./package.json').version
16
- const genericShim = shim.makeSpecializedShim(shim.GENERIC, 'grpc-client-interceptor')
17
-
18
- if (semver.gte(grpcVersion, '1.8.0')) {
19
- const resolvingCall = genericShim.require('./build/src/resolving-call')
20
- genericShim.wrap(resolvingCall.ResolvingCall.prototype, 'start', wrapStart)
21
- } else {
22
- const callStream = genericShim.require('./build/src/call-stream')
23
- genericShim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
24
- }
14
+ module.exports.wrapStartResolve = function wrappedClient(shim, resolvingCall) {
15
+ shim.wrap(resolvingCall.ResolvingCall.prototype, 'start', wrapStart)
16
+ }
25
17
 
26
- const webFrameworkShim = shim.makeSpecializedShim(shim.WEB_FRAMEWORK, 'server')
18
+ module.exports.wrapStartCall = function wrappedClient(shim, callStream) {
19
+ shim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
20
+ }
21
+
22
+ module.exports.wrapServer = function wrapServer(shim, server) {
23
+ const grpcVersion = shim.require('./package.json').version
27
24
  if (semver.lt(grpcVersion, '1.4.0')) {
28
25
  shim.logger.debug('gRPC server-side instrumentation only supported on grpc-js >=1.4.0')
29
26
  return
30
27
  }
31
28
 
32
- const server = webFrameworkShim.require('./build/src/server')
33
- webFrameworkShim.setFramework('gRPC')
34
- webFrameworkShim.wrap(server.Server.prototype, 'register', wrapRegister)
29
+ shim.setFramework('gRPC')
30
+ shim.wrap(server.Server.prototype, 'register', wrapRegister)
35
31
  }
36
32
 
37
33
  /**
@@ -7,13 +7,25 @@
7
7
  const grpc = require('./grpc')
8
8
 
9
9
  /**
10
- * Need to use nr-hooks style for grpc because we're using the onResolved hook
11
- * to register instrumentation.
10
+ * Need to use nr-hooks style for grpc because we're instrumentation a submodule.
12
11
  */
13
12
  module.exports = [
14
13
  {
15
- type: 'conglomerate', // generic shim for client, web framework shim for server
16
- moduleName: '@grpc/grpc-js',
17
- onResolved: grpc
14
+ type: 'generic',
15
+ moduleName: '@grpc/grpc-js/build/src/resolving-call',
16
+ isEsm: true,
17
+ onRequire: grpc.wrapStartResolve
18
+ },
19
+ {
20
+ type: 'generic',
21
+ moduleName: '@grpc/grpc-js/build/src/call-stream',
22
+ isEsm: true,
23
+ onRequire: grpc.wrapStartCall
24
+ },
25
+ {
26
+ type: 'web-framework',
27
+ moduleName: '@grpc/grpc-js/build/src/server',
28
+ isEsm: true,
29
+ onRequire: grpc.wrapServer
18
30
  }
19
31
  ]
@@ -7,13 +7,12 @@
7
7
  const pino = require('./pino')
8
8
 
9
9
  /**
10
- * Need to use nr-hooks style for pino because we're using the onResolved hook
11
- * to register instrumentation.
10
+ * Need to use nr-hooks style because we are instrumenting a submodule.
12
11
  */
13
12
  module.exports = [
14
13
  {
15
14
  type: 'generic',
16
- moduleName: 'pino',
17
- onResolved: pino
15
+ moduleName: 'pino/lib/tools',
16
+ onRequire: pino
18
17
  }
19
18
  ]
@@ -15,7 +15,7 @@ const {
15
15
  } = require('../../util/application-logging')
16
16
  const semver = require('semver')
17
17
 
18
- module.exports = function instrument(shim) {
18
+ module.exports = function instrument(shim, tools) {
19
19
  const pinoVersion = shim.require('./package.json').version
20
20
 
21
21
  if (semver.lt(pinoVersion, '7.0.0')) {
@@ -23,7 +23,6 @@ module.exports = function instrument(shim) {
23
23
  return
24
24
  }
25
25
 
26
- const tools = shim.require('./lib/tools')
27
26
  const agent = shim.agent
28
27
  const config = agent.config
29
28
 
@@ -35,18 +34,6 @@ module.exports = function instrument(shim) {
35
34
  const metrics = agent.metrics
36
35
  createModuleUsageMetric('pino', metrics)
37
36
 
38
- const levelUtils = shim.require('./lib/levels')
39
-
40
- /**
41
- * Creates an object where the keys are the level labels and values are the level number.
42
- * Pino passes in level as number but our spec needs it to be the label
43
- */
44
- const levelMap = Object.entries(levelUtils.levels).reduce((levels, level) => {
45
- const [label, number] = level
46
- levels[number] = label
47
- return levels
48
- }, {})
49
-
50
37
  const symbols = shim.require('./lib/symbols')
51
38
 
52
39
  shim.wrap(tools, 'asJson', function wrapJson(shim, asJson) {
@@ -62,10 +49,10 @@ module.exports = function instrument(shim) {
62
49
  */
63
50
  return function wrappedAsJson() {
64
51
  const args = shim.argsToArray.apply(shim, arguments)
52
+ const level = this?.levels?.labels?.[args[2]]
65
53
 
66
54
  if (isMetricsEnabled(config)) {
67
- const level = args[2]
68
- incrementLoggingLinesMetrics(levelMap[level], metrics)
55
+ incrementLoggingLinesMetrics(level, metrics)
69
56
  }
70
57
 
71
58
  if (isLocalDecoratingEnabled(config)) {
@@ -86,7 +73,7 @@ module.exports = function instrument(shim) {
86
73
  logLine,
87
74
  agent,
88
75
  chindings,
89
- levelMap,
76
+ level,
90
77
  logger: shim.logger
91
78
  })
92
79
 
@@ -110,10 +97,10 @@ module.exports = function instrument(shim) {
110
97
  * @param logLine.agent
111
98
  * @param logLine.chindings
112
99
  * @param logLine.msg
113
- * @param logLine.levelMap
100
+ * @param logLine.level
114
101
  * @param logLine.logger
115
102
  */
116
- function reformatLogLine({ logLine, msg, agent, chindings = '', levelMap, logger }) {
103
+ function reformatLogLine({ logLine, msg, agent, chindings = '', level, logger }) {
117
104
  const metadata = agent.getLinkingMetadata()
118
105
 
119
106
  /**
@@ -145,7 +132,7 @@ function reformatLogLine({ logLine, msg, agent, chindings = '', levelMap, logger
145
132
  reformatError(formattedLog)
146
133
  }
147
134
  Object.assign(formattedLog, agentMeta)
148
- formattedLog.level = levelMap[formattedLog.level]
135
+ formattedLog.level = level
149
136
  delete formattedLog.time
150
137
  delete formattedLog.msg
151
138
  return formattedLog
@@ -11,7 +11,7 @@ const MODULE_TYPE = require('./shim/constants').MODULE_TYPE
11
11
  module.exports = function instrumentations() {
12
12
  return {
13
13
  'aws-sdk': { module: '@newrelic/aws-sdk' },
14
- 'amqplib': { type: MODULE_TYPE.MESSAGE },
14
+ 'amqplib': { module: './instrumentation/amqplib' },
15
15
  'cassandra-driver': { type: MODULE_TYPE.DATASTORE },
16
16
  'connect': { type: MODULE_TYPE.WEB_FRAMEWORK },
17
17
  'bluebird': { type: MODULE_TYPE.PROMISE },
@@ -272,8 +272,7 @@ const INFINITE_TRACING = {
272
272
  const FEATURES = {
273
273
  ESM: {
274
274
  LOADER: `${SUPPORTABILITY.FEATURES}/ESM/Loader`,
275
- UNSUPPORTED_LOADER: `${SUPPORTABILITY.FEATURES}/ESM/UnsupportedLoader`,
276
- CUSTOM_INSTRUMENTATION: `${SUPPORTABILITY.FEATURES}/ESM/CustomInstrumentation`
275
+ UNSUPPORTED_LOADER: `${SUPPORTABILITY.FEATURES}/ESM/UnsupportedLoader`
277
276
  },
278
277
  CJS: {
279
278
  PRELOAD: `${SUPPORTABILITY.FEATURES}/CJS/Preload`,
@@ -736,7 +736,7 @@ function recordSubscribedConsume(nodule, properties, spec) {
736
736
  }
737
737
  }
738
738
  if (msgDesc.headers) {
739
- shim.handleCATHeaders(msgDesc.headers, tx.baseSegment, shim._transportType)
739
+ shim.handleMqTracingHeaders(msgDesc.headers, tx.baseSegment, shim._transportType)
740
740
  }
741
741
 
742
742
  shim.logger.trace('Started message transaction %s named %s', tx.id, txName)
@@ -66,7 +66,7 @@ TransactionShim.prototype.bindCreateTransaction = bindCreateTransaction
66
66
  TransactionShim.prototype.pushTransactionName = pushTransactionName
67
67
  TransactionShim.prototype.popTransactionName = popTransactionName
68
68
  TransactionShim.prototype.setTransactionName = setTransactionName
69
- TransactionShim.prototype.handleCATHeaders = handleCATHeaders
69
+ TransactionShim.prototype.handleMqTracingHeaders = handleMqTracingHeaders
70
70
  TransactionShim.prototype.insertCATReplyHeader = insertCATReplyHeader
71
71
  TransactionShim.prototype.insertCATRequestHeaders = insertCATRequestHeaders
72
72
 
@@ -204,7 +204,7 @@ function setTransactionName(name) {
204
204
  /**
205
205
  * Retrieves whatever CAT headers may be in the given headers.
206
206
  *
207
- * - `handleCATHeaders(headers [, segment [, transportType]])`
207
+ * - `handleMqTracingHeaders(headers [, segment [, transportType]])`
208
208
  *
209
209
  * @memberof TransactionShim.prototype
210
210
  *
@@ -218,8 +218,8 @@ function setTransactionName(name) {
218
218
  * @param {string} [transportType='Unknown']
219
219
  * The transport type that brought the headers. Usually `HTTP` or `HTTPS`.
220
220
  */
221
- function handleCATHeaders(headers, segment, transportType) {
222
- // TODO: rename function or replace functionality when CAT fully removed.
221
+ function handleMqTracingHeaders(headers, segment, transportType) {
222
+ // TODO: replace functionality when CAT fully removed.
223
223
 
224
224
  if (!headers) {
225
225
  this.logger.debug('No headers for CAT or DT processing.')