newrelic 9.4.0 → 9.6.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.
@@ -0,0 +1,108 @@
1
+ /*
2
+ * Copyright 2022 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const formatters = module.exports
8
+
9
+ /**
10
+ * Splits a string by a ',' and trims whitespace
11
+ *
12
+ * @param {string} val config setting
13
+ * @param {Array}
14
+ */
15
+ formatters.array = function array(val) {
16
+ return val.split(',').map((k) => k.trim())
17
+ }
18
+
19
+ /**
20
+ * Parses a config setting as an int
21
+ *
22
+ * @param {string} val config setting
23
+ * @returns {int}
24
+ */
25
+ formatters.int = function int(val) {
26
+ return parseInt(val, 10)
27
+ }
28
+
29
+ /**
30
+ * Parses a config setting as a float
31
+ *
32
+ * @param {string} val config setting
33
+ * @returns {float}
34
+ */
35
+ formatters.float = function float(val) {
36
+ return parseFloat(val, 10)
37
+ }
38
+
39
+ /**
40
+ * Parses a config setting as a boolean
41
+ *
42
+ * @param {string} val config setting
43
+ * @param setting
44
+ * @returns {boolean}
45
+ */
46
+ formatters.boolean = function boolean(setting) {
47
+ if (setting == null) {
48
+ return false
49
+ }
50
+
51
+ const normalized = setting.toString().toLowerCase()
52
+ switch (normalized) {
53
+ case 'false':
54
+ case 'f':
55
+ case 'no':
56
+ case 'n':
57
+ case 'disabled':
58
+ case '0':
59
+ return false
60
+
61
+ default:
62
+ return true
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Parses a config setting as an object
68
+ *
69
+ * @param {string} val config setting
70
+ * @param {logger} logger agent logger instance
71
+ * @returns {object}
72
+ */
73
+ formatters.object = function object(val, logger) {
74
+ try {
75
+ return JSON.parse(val)
76
+ } catch (error) {
77
+ logger.error('New Relic configurator could not deserialize object:')
78
+ logger.error(error.stack)
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Parse a config setting as a collection with 1 object
84
+ *
85
+ * @param {string} val config setting
86
+ * @param {logger} logger agent logger instance
87
+ * @returns {Array}
88
+ */
89
+ formatters.objectList = function objectList(val, logger) {
90
+ try {
91
+ return JSON.parse('[' + val + ']')
92
+ } catch (error) {
93
+ logger.error('New Relic configurator could not deserialize object list:')
94
+ logger.error(error.stack)
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Checks if a value is within an allow list. If not it assigns
100
+ * the first element in allow list as default
101
+ *
102
+ * @param {Array} list allowable values
103
+ * @param {string} val config setting
104
+ * @returns {string}
105
+ */
106
+ formatters.allowList = function allowList(list, val) {
107
+ return list.includes(val) ? val : list[0]
108
+ }
@@ -8,7 +8,7 @@
8
8
  const AttributeFilter = require('./attribute-filter')
9
9
  const CollectorResponse = require('../collector/response')
10
10
  const copy = require('../util/copy')
11
- const defaultConfig = require('./default').config
11
+ const { config: defaultConfig, definition, setNestedKey } = require('./default')
12
12
  const EventEmitter = require('events').EventEmitter
13
13
  const featureFlag = require('../feature_flags')
14
14
  const flatten = require('../util/flatten')
@@ -22,6 +22,8 @@ const util = require('util')
22
22
  const MergeServerConfig = require('./merge-server-config')
23
23
  const harvestConfigValidator = require('./harvest-config-validator')
24
24
  const mergeServerConfig = new MergeServerConfig()
25
+ const { boolean: isTruthular } = require('./formatters')
26
+ const configDefinition = definition()
25
27
 
26
28
  /**
27
29
  * CONSTANTS -- we gotta lotta 'em
@@ -33,11 +35,10 @@ const BASE_CONFIG_PATH = require.resolve('../../newrelic')
33
35
  const HAS_ARBITRARY_KEYS = new Set(['ignore_messages', 'expected_messages', 'labels'])
34
36
 
35
37
  const LASP_MAP = require('./lasp').LASP_MAP
36
- const ENV = require('./env')
37
38
  const HSM = require('./hsm')
38
39
  const REMOVE_BEFORE_SEND = new Set(['attributeFilter'])
39
40
 
40
- const exists = fs.existsSync || path.existsSync
41
+ const exists = fs.existsSync
41
42
  let logger = null // Lazy-loaded in `initialize`.
42
43
  let _configInstance = null
43
44
 
@@ -56,35 +57,6 @@ const getConfigFileLocations = () =>
56
57
  : undefined
57
58
  ].filter(Boolean)
58
59
 
59
- function isTruthular(setting) {
60
- if (setting == null) {
61
- return false
62
- }
63
-
64
- const normalized = setting.toString().toLowerCase()
65
- switch (normalized) {
66
- case 'false':
67
- case 'f':
68
- case 'no':
69
- case 'n':
70
- case 'disabled':
71
- case '0':
72
- return false
73
-
74
- default:
75
- return true
76
- }
77
- }
78
-
79
- function fromObjectList(setting) {
80
- try {
81
- return JSON.parse('[' + setting + ']')
82
- } catch (error) {
83
- logger.error('New Relic configurator could not deserialize object list:')
84
- logger.error(error.stack)
85
- }
86
- }
87
-
88
60
  function _findConfigFile() {
89
61
  const configFileCandidates = getConfigFileLocations().reduce((files, configPath) => {
90
62
  const configFiles = getConfigFileNames().map((filename) =>
@@ -145,11 +117,6 @@ function Config(config) {
145
117
  this.collect_errors = true
146
118
  this.collect_span_events = true
147
119
 
148
- // override options for utilization stats
149
- this.utilization.logical_processors = null
150
- this.utilization.total_ram_mib = null
151
- this.utilization.billing_hostname = null
152
-
153
120
  this.browser_monitoring.loader = 'rum'
154
121
  this.browser_monitoring.loader_version = ''
155
122
 
@@ -1057,66 +1024,77 @@ Config.prototype._featureFlagsFromEnv = function _featureFlagsFromEnv() {
1057
1024
  }
1058
1025
 
1059
1026
  /**
1060
- * Recursively visit the nodes of the constant containing the mapping between
1061
- * environment variable names, overriding any configuration values that are
1062
- * found in the environment. Operates purely via side effects.
1027
+ * Creates an env var mapper from the configuration path value
1063
1028
  *
1064
- * @param object metadata The current level of the mapping object. Should never
1065
- * need to set this yourself.
1066
- * @param object data The current level of the configuration object. Should
1067
- * never need to set this yourself.
1068
- * @param metadata
1069
- * @param data
1029
+ * @param {string} key of configuration value
1030
+ * @param {Array} paths list of leaf nodes leading to configuration value
1031
+ * @returns {string} formatted env var name
1070
1032
  */
1071
- Config.prototype._fromEnvironment = function _fromEnvironment(metadata, data) {
1072
- if (!metadata) {
1073
- metadata = ENV.ENV_MAPPING
1074
- }
1075
- if (!data) {
1076
- data = this
1077
- }
1033
+ function deriveEnvVar(key, paths) {
1034
+ let path = paths.join('_')
1035
+ path = path ? `${path}_` : path
1036
+ const envKey = `NEW_RELIC_${path.toUpperCase()}${key.toUpperCase()}`
1037
+ return envKey
1038
+ }
1078
1039
 
1079
- Object.keys(metadata).forEach(function applyEnvDefault(value) {
1080
- // if it's not in the config, it doesn't exist
1081
- if (data[value] === undefined) {
1082
- return
1083
- }
1040
+ /**
1041
+ * Assigns the value of an env var to its corresponding configuration path
1042
+ *
1043
+ * @param {object} params
1044
+ * @param {object} params.config agent config
1045
+ * @param {string} params.key key to assign value
1046
+ * @param {string} params.envVar name of env var
1047
+ * @param {Function} params.formatter function to coerce env var as they are all strings
1048
+ * @param {Array} params.paths list of leaf nodes leading to the configuration value
1049
+ */
1050
+ function setFromEnv({ config, key, envVar, formatter, paths }) {
1051
+ const setting = process.env[envVar]
1052
+ if (setting) {
1053
+ const formattedSetting = formatter ? formatter(setting, logger) : setting
1054
+ setNestedKey(config, [...paths, key], formattedSetting)
1055
+ }
1056
+ }
1084
1057
 
1085
- const node = metadata[value]
1086
- if (typeof node === 'string') {
1087
- const setting = process.env[node]
1088
- if (setting) {
1089
- logger.debug(`${node} environment variable set.`)
1090
-
1091
- if (ENV.LIST_VARS.has(node)) {
1092
- const split = ENV.LIST_VARS_CUSTOM_DELIMITERS[node] || /,/
1093
- data[value] = setting.split(split).map(function trimVal(k) {
1094
- return k.trim()
1095
- })
1096
- } else if (ENV.OBJECT_LIST_VARS.has(node)) {
1097
- data[value] = fromObjectList(setting)
1098
- } else if (ENV.BOOLEAN_VARS.has(node)) {
1099
- if (value === 'ssl' && !isTruthular(setting)) {
1100
- logger.warn('SSL config key can no longer be disabled, not updating.')
1101
- return
1102
- }
1103
- data[value] = isTruthular(setting)
1104
- } else if (ENV.FLOAT_VARS.has(node)) {
1105
- data[value] = parseFloat(setting, 10)
1106
- } else if (ENV.INT_VARS.has(node)) {
1107
- data[value] = parseInt(setting, 10)
1108
- } else {
1109
- data[value] = setting
1110
- }
1111
- }
1112
- } else {
1113
- // don't crash if the mapping has config keys the current config doesn't.
1114
- if (!data[value]) {
1115
- data[value] = Object.create(null)
1058
+ /**
1059
+ * Recursively visit the nodes of the config definition and look for environment variable names, overriding any configuration values that are found.
1060
+ *
1061
+ * @param {object} [config=this] The current level of the configuration object.
1062
+ * @param {object} [data=configDefinition] The current level of the config definition object.
1063
+ * @param {Array} [paths=[]] keeps track of the nested path to properly derive the env var
1064
+ * @param {int} [objectKeys=1] indicator of how many keys exist in current node to know when to remove current node after all keys are processed
1065
+ */
1066
+ Config.prototype._fromEnvironment = function _fromEnvironment(
1067
+ config = this,
1068
+ data = configDefinition,
1069
+ paths = [],
1070
+ objectKeys = 1
1071
+ ) {
1072
+ let keysSeen = 0
1073
+ Object.entries(data).forEach(([key, value]) => {
1074
+ const type = typeof value
1075
+ keysSeen++
1076
+ if (type === 'string') {
1077
+ const envVar = deriveEnvVar(key, paths)
1078
+ setFromEnv({ config, key, envVar, paths })
1079
+ } else if (type === 'object') {
1080
+ if (value.hasOwnProperty('env')) {
1081
+ setFromEnv({ config, key, envVar: value.env, paths, formatter: value.formatter })
1082
+ } else if (value.hasOwnProperty('default')) {
1083
+ const envVar = deriveEnvVar(key, paths)
1084
+ setFromEnv({ config, key, envVar, formatter: value.formatter, paths })
1085
+ } else {
1086
+ paths.push(key)
1087
+ const { length } = Object.keys(value)
1088
+ this._fromEnvironment(config, value, paths, length)
1116
1089
  }
1117
- this._fromEnvironment(node, data[value])
1118
1090
  }
1119
- }, this)
1091
+ })
1092
+
1093
+ // we have traversed every key in current object leaf node, remove wrapping key
1094
+ // to properly derive env vars of future leaf nodes
1095
+ if (keysSeen === objectKeys) {
1096
+ paths.pop()
1097
+ }
1120
1098
  }
1121
1099
 
1122
1100
  /**
@@ -5,4 +5,232 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- module.exports = require('../hapi')
8
+ const record = require('../../metrics/recorders/generic')
9
+ // This object defines all the events that we want to wrap extensions
10
+ // for, as they are the only ones associated with requests.
11
+ const ROUTE_EVENTS = {
12
+ onRequest: true,
13
+ onPreAuth: true,
14
+ onCredentials: true,
15
+ onPostAuth: true,
16
+ onPreHandler: true,
17
+ onPostHandler: true,
18
+ onPreResponse: true,
19
+
20
+ // Server events
21
+ onPreStart: false,
22
+ onPostStart: false,
23
+ onPreStop: false,
24
+ onPostStop: false
25
+ }
26
+
27
+ module.exports = function initialize(agent, hapi, moduleName, shim) {
28
+ if (!agent || !hapi || !shim) {
29
+ shim &&
30
+ shim.logger.debug(
31
+ 'Hapi instrumentation function called with incorrect arguments, not instrumenting.'
32
+ )
33
+ return false
34
+ }
35
+
36
+ shim.setFramework(shim.HAPI)
37
+
38
+ shim.setErrorPredicate(function hapiErrorPredicate(err) {
39
+ return err instanceof Error
40
+ })
41
+
42
+ // At this point, framework and error predicate have both already been set via ./hapi,
43
+ // so we only need to set the response predicate and wrap the server object
44
+ shim.setResponsePredicate(function hapiResponsePredicate(args, result) {
45
+ return !(result instanceof Error) && result !== args[1].continue
46
+ })
47
+
48
+ // 'Server' and 'server' both point to the same export,
49
+ // but we can't make any assumption about which will be used.
50
+ // Since we wrap the prototype, the second wrap should exit early.
51
+ shim.wrapReturn(hapi, 'server', serverFactoryWrapper)
52
+ shim.wrapReturn(hapi, 'Server', serverFactoryWrapper)
53
+ }
54
+
55
+ function serverFactoryWrapper(shim, fn, fnName, server) {
56
+ serverPostConstructor.call(server, shim)
57
+ }
58
+
59
+ function serverPostConstructor(shim) {
60
+ const proto = Object.getPrototypeOf(this)
61
+
62
+ if (shim.isWrapped(proto.decorate)) {
63
+ shim.logger.trace('Already wrapped Server proto, not wrapping again')
64
+ return
65
+ }
66
+
67
+ shim.wrap(proto, 'decorate', function wrapDecorate(shim, original) {
68
+ return function wrappedDecorate(type) {
69
+ // server.decorate also accepts 'request', 'toolkit', 'server' types,
70
+ // but we're only concerned with 'handler'
71
+ if (type !== 'handler') {
72
+ return original.apply(this, arguments)
73
+ }
74
+
75
+ // Convert arguments to usable array
76
+ const args = shim.argsToArray.apply(shim, arguments)
77
+
78
+ // Wrap the third server.decorate arg, the user-defined handler
79
+ shim.wrap(args, shim.THIRD, function wrapHandler(shim, fn) {
80
+ if (typeof fn !== 'function') {
81
+ return
82
+ }
83
+
84
+ if (fn.defaults) {
85
+ wrappedHandler.defaults = fn.defaults
86
+ }
87
+
88
+ return wrappedHandler
89
+
90
+ function wrappedHandler(route) {
91
+ const ret = fn.apply(this, arguments)
92
+
93
+ return typeof ret === 'function' ? wrapRouteHandler(shim, ret, route && route.path) : ret
94
+ }
95
+ })
96
+
97
+ return original.apply(this, args)
98
+ }
99
+ })
100
+
101
+ shim.wrap(proto, 'route', function wrapRoute(shim, original) {
102
+ return function wrappedRoute() {
103
+ const args = shim.argsToArray.apply(shim, arguments)
104
+
105
+ if (!shim.isObject(args[0])) {
106
+ return original.apply(this, args)
107
+ }
108
+
109
+ // If route is created via a plugin, pull prefix if it exists
110
+ const prefix =
111
+ (this.realm &&
112
+ this.realm.modifiers &&
113
+ this.realm.modifiers.route &&
114
+ this.realm.modifiers.route.prefix) ||
115
+ ''
116
+
117
+ _wrapRoute(shim, args[0])
118
+
119
+ return original.apply(this, args)
120
+
121
+ function _wrapRoute(shim, route) {
122
+ const routePath = prefix + route.path
123
+ if (shim.isArray(route)) {
124
+ for (let i = 0; i < route.length; ++i) {
125
+ _wrapRoute(shim, route[i])
126
+ }
127
+ return
128
+ } else if (route.options) {
129
+ // v17 now prefers `options` property...
130
+ if (route.options.pre) {
131
+ // config objects can also contain multiple OTHER handlers in a `pre` array
132
+ route.options.pre = wrapPreHandlers(shim, route.options.pre, routePath)
133
+ }
134
+ if (route.options.handler) {
135
+ _wrapRouteHandler(shim, route.options, routePath)
136
+ return
137
+ }
138
+ } else if (route.config) {
139
+ // ... but `config` still works
140
+ if (route.config.pre) {
141
+ route.config.pre = wrapPreHandlers(shim, route.config.pre, routePath)
142
+ }
143
+ if (route.config.handler) {
144
+ _wrapRouteHandler(shim, route.config, routePath)
145
+ return
146
+ }
147
+ }
148
+ _wrapRouteHandler(shim, route, routePath)
149
+ }
150
+
151
+ function _wrapRouteHandler(shim, container, path) {
152
+ if (typeof container.handler !== 'function') {
153
+ return
154
+ }
155
+ shim.wrap(container, 'handler', function wrapHandler(shim, handler) {
156
+ return wrapRouteHandler(shim, handler, path)
157
+ })
158
+ }
159
+ }
160
+ })
161
+
162
+ shim.wrap(proto, 'ext', function wrapExt(shim, original) {
163
+ return function wrappedExt(event, method) {
164
+ const args = shim.argsToArray.apply(shim, arguments)
165
+
166
+ if (shim.isArray(event)) {
167
+ for (let i = 0; i < event.length; i++) {
168
+ event[i].method = wrapMiddleware(shim, event[i].method, event[i].type)
169
+ }
170
+ } else if (shim.isObject(event)) {
171
+ event.method = wrapMiddleware(shim, event.method, event.type)
172
+ } else if (shim.isString(event)) {
173
+ args[1] = wrapMiddleware(shim, method, event)
174
+ } else {
175
+ shim.logger.debug('Unsupported event type %j', event)
176
+ return
177
+ }
178
+
179
+ return original.apply(this, args)
180
+ }
181
+ })
182
+ }
183
+
184
+ function wrapPreHandlers(shim, container, path) {
185
+ if (shim.isArray(container)) {
186
+ for (let i = 0; i < container.length; ++i) {
187
+ container[i] = wrapPreHandlers(shim, container[i], path)
188
+ }
189
+ return container
190
+ } else if (shim.isFunction(container)) {
191
+ return wrapPreHandler(shim, container, path)
192
+ } else if (container.method && shim.isFunction(container.method)) {
193
+ return shim.wrap(container, 'method', function wrapHandler(shim, handler) {
194
+ return wrapPreHandler(shim, handler, path)
195
+ })
196
+ }
197
+ }
198
+
199
+ function wrapPreHandler(shim, container, path) {
200
+ return shim.record(container, (shim) => {
201
+ return { name: [shim.HAPI, ' pre handler: ', '(', path, ')'].join(''), recorder: record }
202
+ })
203
+ }
204
+
205
+ function wrapRouteHandler(shim, handler, path) {
206
+ return shim.recordMiddleware(handler, {
207
+ route: path,
208
+ req: function getReq(shim, fn, fnName, args) {
209
+ const request = args[0]
210
+ if (request && request.raw) {
211
+ return request.raw.req
212
+ }
213
+ },
214
+ promise: true,
215
+ params: function getParams(shim, fn, fnName, args) {
216
+ const req = args[0]
217
+ return req && req.params
218
+ }
219
+ })
220
+ }
221
+
222
+ function wrapMiddleware(shim, middleware, event) {
223
+ if (!ROUTE_EVENTS[event]) {
224
+ return middleware
225
+ }
226
+
227
+ return shim.recordMiddleware(middleware, {
228
+ route: event,
229
+ type: event === 'onPreResponse' ? shim.ERRORWARE : shim.MIDDLEWARE,
230
+ promise: true,
231
+ req: function getReq(shim, fn, fnName, args) {
232
+ const req = args[0]
233
+ return req && req.raw && req.raw.req
234
+ }
235
+ })
236
+ }
@@ -38,7 +38,7 @@ function initialize(agent, shim) {
38
38
 
39
39
  function getPromiseResolveStyleHooks(segmentMap, agent, shim) {
40
40
  const hooks = {
41
- init: function initHook(id, type, triggerId, asyncResource) {
41
+ init: function initHook(id, type, triggerId) {
42
42
  if (type !== 'PROMISE') {
43
43
  return
44
44
  }
@@ -55,9 +55,6 @@ function getPromiseResolveStyleHooks(segmentMap, agent, shim) {
55
55
  }
56
56
 
57
57
  const activeSegment = shim.getActiveSegment() || parentSegment
58
- if (asyncResource && asyncResource.promise) {
59
- asyncResource.promise.__NR_id = id
60
- }
61
58
 
62
59
  segmentMap.set(id, activeSegment)
63
60
  },
@@ -6,6 +6,7 @@
6
6
  'use strict'
7
7
 
8
8
  const asyncHooks = require('./async_hooks')
9
+ const symbols = require('../../symbols')
9
10
 
10
11
  module.exports = initialize
11
12
 
@@ -40,9 +41,7 @@ function initialize(agent, nodule, name, shim) {
40
41
  if (ev === 'unhandledRejection' && error && !process.domain) {
41
42
  if (process.listenerCount('unhandledRejection') === 0) {
42
43
  // If there are no unhandledRejection handlers report the error.
43
- const segment = promise.__NR_id
44
- ? asyncHooks.segmentMap.get(promise.__NR_id)
45
- : promise.__NR_context && promise.__NR_context.getSegment()
44
+ const segment = promise[symbols.context] && promise[symbols.context].getSegment()
46
45
  const tx = segment && segment.transaction
47
46
  shim.logger.trace('Captured unhandled rejection for transaction %s', tx && tx.id)
48
47
  agent.errors.add(tx, error)
@@ -12,9 +12,9 @@ const logger = require('../../logger').child({ component: 'outbound' })
12
12
  const shimmer = require('../../shimmer')
13
13
  const url = require('url')
14
14
  const copy = require('../../util/copy')
15
+ const symbols = require('../../symbols')
15
16
 
16
17
  const NAMES = require('../../metrics/names')
17
- const SHIM_SYMBOLS = require('../../shim/constants').SYMBOLS
18
18
 
19
19
  const DEFAULT_HOST = 'localhost'
20
20
  const DEFAULT_HTTP_PORT = 80
@@ -85,7 +85,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
85
85
 
86
86
  // TODO: abstract header logic shared with TransactionShim#insertCATRequestHeaders
87
87
  if (agent.config.distributed_tracing.enabled) {
88
- if (opts.headers && opts.headers[SHIM_SYMBOLS.DISABLE_DT]) {
88
+ if (opts.headers && opts.headers[symbols.disableDT]) {
89
89
  logger.trace('Distributed tracing disabled by instrumentation.')
90
90
  } else {
91
91
  transaction.insertDistributedTraceHeaders(outboundHeaders)
@@ -113,9 +113,7 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) {
113
113
  const parsed = urltils.scrubAndParseParameters(request.path)
114
114
  const proto = parsed.protocol || opts.protocol || 'http:'
115
115
  segment.name += parsed.path
116
- Object.defineProperty(request, '__NR_segment', {
117
- value: segment
118
- })
116
+ request[symbols.segment] = segment
119
117
 
120
118
  if (parsed.parameters) {
121
119
  // Scrub and parse returns on object with a null prototype.