newrelic 11.19.0 → 11.21.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/NEWS.md +53 -0
- package/api.js +10 -10
- package/lib/config/attribute-filter.js +1 -1
- package/lib/config/default.js +3 -3
- package/lib/instrumentation/aws-sdk/util.js +1 -1
- package/lib/llm-events/aws-bedrock/bedrock-command.js +12 -2
- package/lib/llm-events/aws-bedrock/bedrock-response.js +12 -1
- package/lib/llm-events/aws-bedrock/stream-handler.js +28 -0
- package/lib/serverless/api-gateway.js +1 -1
- package/lib/shim/datastore-shim.js +2 -2
- package/lib/transaction/name-state.js +1 -1
- package/lib/util/hashes.js +3 -2
- package/lib/utilization/docker-info.js +124 -21
- package/lib/utilization/gcp-info.js +4 -2
- package/package.json +2 -2
package/NEWS.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
+
### v11.21.0 (2024-06-25)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* Added support for getting container ids from ECS metadata API ([#2292](https://github.com/newrelic/node-newrelic/pull/2292)) ([dbca830](https://github.com/newrelic/node-newrelic/commit/dbca830deb6c9420427b60df4875ba71939508c4))
|
|
6
|
+
|
|
7
|
+
#### Bug fixes
|
|
8
|
+
|
|
9
|
+
* Handled assigning headers in LambdaProxyWebRequest when there are no headers present ([#2293](https://github.com/newrelic/node-newrelic/pull/2293)) ([e4d22f3](https://github.com/newrelic/node-newrelic/commit/e4d22f38b70cee061b345d6dbc94a2783a164b76))
|
|
10
|
+
|
|
11
|
+
#### Documentation
|
|
12
|
+
|
|
13
|
+
* Updated compatibility report ([#2290](https://github.com/newrelic/node-newrelic/pull/2290)) ([b5fc893](https://github.com/newrelic/node-newrelic/commit/b5fc8932ed5a0372f8be3f0e53c6d9fa0cf12855))
|
|
14
|
+
|
|
15
|
+
#### Tests
|
|
16
|
+
|
|
17
|
+
* Fixed log error stack message truncating and failing equality test ([#2294](https://github.com/newrelic/node-newrelic/pull/2294)) ([8e06f0f](https://github.com/newrelic/node-newrelic/commit/8e06f0f77762922e7862446bdf85a32eef3f9096))
|
|
18
|
+
* Updated shimmer method in benchmark tests ([#2281](https://github.com/newrelic/node-newrelic/pull/2281)) ([1528d68](https://github.com/newrelic/node-newrelic/commit/1528d685c7fd8e9af23ea91bd66124b82dcdb523))
|
|
19
|
+
|
|
20
|
+
### v11.20.0 (2024-06-24)
|
|
21
|
+
|
|
22
|
+
#### Features
|
|
23
|
+
|
|
24
|
+
* Added support for Anthropic Claude 3 messages API ([#2278](https://github.com/newrelic/node-newrelic/pull/2278)) ([7e3cab9](https://github.com/newrelic/node-newrelic/commit/7e3cab9739a9924b904ce78380fd70b1e30cd89f))
|
|
25
|
+
|
|
26
|
+
#### Code refactoring
|
|
27
|
+
|
|
28
|
+
* Replaced instances of `substr()` with `substring()` ([#2274](https://github.com/newrelic/node-newrelic/pull/2274)) ([8f96c73](https://github.com/newrelic/node-newrelic/commit/8f96c734862d42f459dd4de8ed9d498ef7d693de))
|
|
29
|
+
|
|
30
|
+
#### Documentation
|
|
31
|
+
|
|
32
|
+
* Fixed typos in API jsdoc ([#2287](https://github.com/newrelic/node-newrelic/pull/2287)) ([7b3c8d1](https://github.com/newrelic/node-newrelic/commit/7b3c8d1445ea9876b36eda31d979dac9b65dad14))
|
|
33
|
+
* Updated compatibility report ([#2285](https://github.com/newrelic/node-newrelic/pull/2285)) ([b1b5e3e](https://github.com/newrelic/node-newrelic/commit/b1b5e3e54074cc8b535927f4edad07925618260c))
|
|
34
|
+
|
|
35
|
+
#### Miscellaneous chores
|
|
36
|
+
|
|
37
|
+
* Added a missing library we instrument to be picked up by the compatibility report ([#2261](https://github.com/newrelic/node-newrelic/pull/2261)) ([6c964b2](https://github.com/newrelic/node-newrelic/commit/6c964b2029874353908486228113a468959b1597))
|
|
38
|
+
* Added a script that generates Dashboard json for reporting on libraries by version ([#2267](https://github.com/newrelic/node-newrelic/pull/2267)) ([d2877c1](https://github.com/newrelic/node-newrelic/commit/d2877c17c7c2f4432b2ce4056c94200a2e7334b2))
|
|
39
|
+
* Added AI support docs for automation ([#2249](https://github.com/newrelic/node-newrelic/pull/2249)) ([e2efc1f](https://github.com/newrelic/node-newrelic/commit/e2efc1fb202a3e87be710f352546a00df6bf6cef))
|
|
40
|
+
* Fixed comment about transaction_tracer.transaction_threshold ([#2258](https://github.com/newrelic/node-newrelic/pull/2258)) ([7cd6aef](https://github.com/newrelic/node-newrelic/commit/7cd6aef7ecc32d8647f0313b36be368bfa14a0e9))
|
|
41
|
+
* Made elastic versioned tests work on Node 16 ([#2284](https://github.com/newrelic/node-newrelic/pull/2284)) ([143b475](https://github.com/newrelic/node-newrelic/commit/143b4757c27d497a25d1b3c7fd56b32bcede619f))
|
|
42
|
+
* Updated docker compose configuration ([#2268](https://github.com/newrelic/node-newrelic/pull/2268)) ([eb1cce9](https://github.com/newrelic/node-newrelic/commit/eb1cce9143fe87eed1bdd077471942f85cf14f21))
|
|
43
|
+
* Updated GitHub Actions versions ([#2272](https://github.com/newrelic/node-newrelic/pull/2272)) ([07a841b](https://github.com/newrelic/node-newrelic/commit/07a841b9acf0a0dcb1a495c5cb0ee8f77e283fc9))
|
|
44
|
+
* Updated mysql tests to use MySQL 8.3 ([#2280](https://github.com/newrelic/node-newrelic/pull/2280)) ([11e0f8c](https://github.com/newrelic/node-newrelic/commit/11e0f8cf63a5f792419382635496a29c0286c737))
|
|
45
|
+
* Updated mysql2 tests to fix CI issue ([#2282](https://github.com/newrelic/node-newrelic/pull/2282)) ([5a37971](https://github.com/newrelic/node-newrelic/commit/5a37971b03fe8d69b39db66d97ecfc1176c42902))
|
|
46
|
+
* Updated versioned security agent workflow with simpler setup ([#2259](https://github.com/newrelic/node-newrelic/pull/2259)) ([a886187](https://github.com/newrelic/node-newrelic/commit/a886187bf1bf29d2430e38223d0215436e006540))
|
|
47
|
+
|
|
48
|
+
#### Continuous integration
|
|
49
|
+
|
|
50
|
+
* Added ability to run a build step in create release ([#2273](https://github.com/newrelic/node-newrelic/pull/2273)) ([4c2f24c](https://github.com/newrelic/node-newrelic/commit/4c2f24cb6e94dd05f269998eeb763196c3fac274))
|
|
51
|
+
* Updated security agent workflow to use large runners when available ([#2271](https://github.com/newrelic/node-newrelic/pull/2271)) ([181ec1b](https://github.com/newrelic/node-newrelic/commit/181ec1bfa8d420514fca3d6de6c3989e796a29db))
|
|
52
|
+
* Updated security agent workflow to use large runners when available ([#2270](https://github.com/newrelic/node-newrelic/pull/2270)) ([761fcd5](https://github.com/newrelic/node-newrelic/commit/761fcd5ca5ae32ba922d0880979dbfff97604641))
|
|
53
|
+
|
|
1
54
|
### v11.19.0 (2024-06-06)
|
|
2
55
|
|
|
3
56
|
#### Features
|
package/api.js
CHANGED
|
@@ -244,14 +244,14 @@ API.prototype.setControllerName = function setControllerName(name, action) {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
|
-
* Add a custom attribute to the current transaction. Some attributes are
|
|
247
|
+
* Add a custom attribute to the current transaction and span. Some attributes are
|
|
248
248
|
* reserved (see CUSTOM_DENYLIST for the current, very short list), and
|
|
249
249
|
* as with most API methods, this must be called in the context of an
|
|
250
250
|
* active transaction. Most recently set value wins.
|
|
251
251
|
*
|
|
252
252
|
* @param {string} key The key you want displayed in the RPM UI.
|
|
253
253
|
* @param {string} value The value you want displayed. Must be serializable.
|
|
254
|
-
* @returns {false|undefined}
|
|
254
|
+
* @returns {false|undefined} Returns false when disabled/errored, otherwise undefined
|
|
255
255
|
*/
|
|
256
256
|
API.prototype.addCustomAttribute = function addCustomAttribute(key, value) {
|
|
257
257
|
const metric = this.agent.metrics.getOrCreateMetric(
|
|
@@ -298,7 +298,7 @@ API.prototype.addCustomAttribute = function addCustomAttribute(key, value) {
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
/**
|
|
301
|
-
* Adds all custom attributes in an object to the current transaction.
|
|
301
|
+
* Adds all custom attributes in an object to the current transaction and span.
|
|
302
302
|
*
|
|
303
303
|
* See documentation for newrelic.addCustomAttribute for more information on
|
|
304
304
|
* setting custom attributes.
|
|
@@ -360,7 +360,7 @@ API.prototype.addCustomSpanAttributes = function addCustomSpanAttributes(atts) {
|
|
|
360
360
|
*
|
|
361
361
|
* @param {string} key The key you want displayed in the RPM UI.
|
|
362
362
|
* @param {string} value The value you want displayed. Must be serializable.
|
|
363
|
-
* @returns {false|undefined}
|
|
363
|
+
* @returns {false|undefined} Returns false when disabled/errored, otherwise undefined
|
|
364
364
|
*/
|
|
365
365
|
API.prototype.addCustomSpanAttribute = function addCustomSpanAttribute(key, value) {
|
|
366
366
|
const metric = this.agent.metrics.getOrCreateMetric(
|
|
@@ -807,7 +807,7 @@ API.prototype.getBrowserTimingHeader = function getBrowserTimingHeader(options =
|
|
|
807
807
|
* Only the first 13 chars of the license should be used for hashing with
|
|
808
808
|
* the transaction name.
|
|
809
809
|
*/
|
|
810
|
-
const key = config.license_key.
|
|
810
|
+
const key = config.license_key.substring(0, 13)
|
|
811
811
|
rumHash.transactionName = hashes.obfuscateNameUsingKey(name, key)
|
|
812
812
|
|
|
813
813
|
rumHash.queueTime = trans.queueTime
|
|
@@ -938,7 +938,7 @@ API.prototype.startSegment = function startSegment(name, record, handler, callba
|
|
|
938
938
|
* })
|
|
939
939
|
* @param {string} url
|
|
940
940
|
* The URL of the transaction. It is used to name and group related transactions in APM,
|
|
941
|
-
* so it should be a generic name and not
|
|
941
|
+
* so it should be a generic name and not include any variable parameters.
|
|
942
942
|
* @param {Function} handle
|
|
943
943
|
* Function that represents the transaction work.
|
|
944
944
|
* @returns {null|*} Returns null if handle is not a function, otherwise the return value of handle
|
|
@@ -1026,7 +1026,7 @@ API.prototype.startBackgroundTransaction = startBackgroundTransaction
|
|
|
1026
1026
|
* })
|
|
1027
1027
|
* @param {string} name
|
|
1028
1028
|
* The name of the transaction. It is used to name and group related
|
|
1029
|
-
* transactions in APM, so it should be a generic name and not
|
|
1029
|
+
* transactions in APM, so it should be a generic name and not include any
|
|
1030
1030
|
* variable parameters.
|
|
1031
1031
|
* @param {string} [group]
|
|
1032
1032
|
* Optional, used for grouping background transactions in APM. For more
|
|
@@ -1297,14 +1297,14 @@ API.prototype.recordCustomEvent = function recordCustomEvent(eventType, attribut
|
|
|
1297
1297
|
// Filter all object type valued attributes out
|
|
1298
1298
|
const filteredAttributes = _filterAttributes(attributes, `${eventType} custom event`)
|
|
1299
1299
|
|
|
1300
|
-
const
|
|
1300
|
+
const intrinsics = {
|
|
1301
1301
|
type: eventType,
|
|
1302
1302
|
timestamp: Date.now()
|
|
1303
1303
|
}
|
|
1304
1304
|
|
|
1305
1305
|
const tx = this.agent.getTransaction()
|
|
1306
1306
|
const priority = (tx && tx.priority) || Math.random()
|
|
1307
|
-
this.agent.customEventAggregator.add([
|
|
1307
|
+
this.agent.customEventAggregator.add([intrinsics, filteredAttributes], priority)
|
|
1308
1308
|
}
|
|
1309
1309
|
|
|
1310
1310
|
/**
|
|
@@ -1342,7 +1342,7 @@ API.prototype.instrument = function instrument(moduleName, onRequire, onError) {
|
|
|
1342
1342
|
* Registers an instrumentation function.
|
|
1343
1343
|
*
|
|
1344
1344
|
* - `newrelic.instrumentConglomerate(moduleName, onRequire [, onError])`
|
|
1345
|
-
* - `newrelic.
|
|
1345
|
+
* - `newrelic.instrumentConglomerate(options)`
|
|
1346
1346
|
*
|
|
1347
1347
|
* @param {string|object} moduleName The module name given to require to load the module, or the instrumentation specification
|
|
1348
1348
|
* @param {string} moduleName.moduleName The module name given to require to load the module
|
package/lib/config/default.js
CHANGED
|
@@ -519,12 +519,12 @@ defaultConfig.definition = () => ({
|
|
|
519
519
|
env: 'NEW_RELIC_TRACER_ENABLED'
|
|
520
520
|
},
|
|
521
521
|
/**
|
|
522
|
-
*
|
|
523
|
-
* transaction trace.
|
|
522
|
+
* Sets the time, in seconds, for a transaction to be considered slow.
|
|
523
|
+
* When a transaction exceeds this threshold, a transaction trace will be recorded. When set to 'apdex_f', the threshold will be set to
|
|
524
524
|
* 4 * apdex_t, which with a default apdex_t value of 500 milliseconds will
|
|
525
525
|
* be 2 seconds.
|
|
526
526
|
*
|
|
527
|
-
* If a
|
|
527
|
+
* If a number is provided, it is set in seconds.
|
|
528
528
|
*/
|
|
529
529
|
transaction_threshold: {
|
|
530
530
|
formatter: float,
|
|
@@ -35,7 +35,7 @@ class BedrockCommand {
|
|
|
35
35
|
result = this.#body.maxTokens
|
|
36
36
|
} else if (this.isClaude() === true) {
|
|
37
37
|
result = this.#body.max_tokens_to_sample
|
|
38
|
-
} else if (this.isCohere() === true) {
|
|
38
|
+
} else if (this.isClaude3() === true || this.isCohere() === true) {
|
|
39
39
|
result = this.#body.max_tokens
|
|
40
40
|
} else if (this.isLlama2() === true) {
|
|
41
41
|
result = this.#body.max_gen_length
|
|
@@ -83,6 +83,11 @@ class BedrockCommand {
|
|
|
83
83
|
this.isLlama2() === true
|
|
84
84
|
) {
|
|
85
85
|
result = this.#body.prompt
|
|
86
|
+
} else if (this.isClaude3() === true) {
|
|
87
|
+
result = this.#body?.messages?.reduce((acc, curr) => {
|
|
88
|
+
acc += curr?.content ?? ''
|
|
89
|
+
return acc
|
|
90
|
+
}, '')
|
|
86
91
|
}
|
|
87
92
|
return result
|
|
88
93
|
}
|
|
@@ -96,6 +101,7 @@ class BedrockCommand {
|
|
|
96
101
|
result = this.#body.textGenerationConfig?.temperature
|
|
97
102
|
} else if (
|
|
98
103
|
this.isClaude() === true ||
|
|
104
|
+
this.isClaude3() === true ||
|
|
99
105
|
this.isAi21() === true ||
|
|
100
106
|
this.isCohere() === true ||
|
|
101
107
|
this.isLlama2() === true
|
|
@@ -110,7 +116,11 @@ class BedrockCommand {
|
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
isClaude() {
|
|
113
|
-
return this.#modelId.startsWith('anthropic.claude')
|
|
119
|
+
return this.#modelId.startsWith('anthropic.claude-v')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
isClaude3() {
|
|
123
|
+
return this.#modelId.startsWith('anthropic.claude-3')
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
isCohere() {
|
|
@@ -30,10 +30,12 @@ class BedrockResponse {
|
|
|
30
30
|
#completions = []
|
|
31
31
|
#id
|
|
32
32
|
|
|
33
|
+
/* eslint-disable sonarjs/cognitive-complexity */
|
|
33
34
|
/**
|
|
34
35
|
* @param {object} params
|
|
35
36
|
* @param {AwsBedrockMiddlewareResponse} params.response
|
|
36
37
|
* @param {BedrockCommand} params.bedrockCommand
|
|
38
|
+
* @param params.isError
|
|
37
39
|
*/
|
|
38
40
|
constructor({ response, bedrockCommand, isError = false }) {
|
|
39
41
|
this.#innerResponse = isError ? response.$response : response.response
|
|
@@ -57,6 +59,14 @@ class BedrockResponse {
|
|
|
57
59
|
} else if (cmd.isClaude() === true) {
|
|
58
60
|
// TODO: can we make this thing give more than one completion?
|
|
59
61
|
body.completion && this.#completions.push(body.completion)
|
|
62
|
+
} else if (cmd.isClaude3() === true) {
|
|
63
|
+
if (body?.type === 'message_stop') {
|
|
64
|
+
// Streamed response
|
|
65
|
+
this.#completions = body.completions
|
|
66
|
+
} else {
|
|
67
|
+
this.#completions = body?.content?.map((c) => c.text)
|
|
68
|
+
}
|
|
69
|
+
this.#id = body.id
|
|
60
70
|
} else if (cmd.isCohere() === true) {
|
|
61
71
|
this.#completions = body.generations?.map((g) => g.text) ?? []
|
|
62
72
|
this.#id = body.id
|
|
@@ -66,6 +76,7 @@ class BedrockResponse {
|
|
|
66
76
|
this.#completions = body.results?.map((r) => r.outputText) ?? []
|
|
67
77
|
}
|
|
68
78
|
}
|
|
79
|
+
/* eslint-enable sonarjs/cognitive-complexity */
|
|
69
80
|
|
|
70
81
|
/**
|
|
71
82
|
* The prompt responses returned by the model.
|
|
@@ -92,7 +103,7 @@ class BedrockResponse {
|
|
|
92
103
|
const cmd = this.#command
|
|
93
104
|
if (cmd.isAi21() === true) {
|
|
94
105
|
result = this.#parsedBody.completions?.[0]?.finishReason.reason
|
|
95
|
-
} else if (cmd.isClaude() === true) {
|
|
106
|
+
} else if (cmd.isClaude() === true || cmd.isClaude3() === true) {
|
|
96
107
|
result = this.#parsedBody.stop_reason
|
|
97
108
|
} else if (cmd.isCohere() === true) {
|
|
98
109
|
result = this.#parsedBody.generations?.find((r) => r.finish_reason !== null)?.finish_reason
|
|
@@ -105,6 +105,9 @@ class StreamHandler {
|
|
|
105
105
|
if (bedrockCommand.isClaude() === true) {
|
|
106
106
|
this.stopReasonKey = 'stop_reason'
|
|
107
107
|
this.generator = handleClaude
|
|
108
|
+
} else if (bedrockCommand.isClaude3() === true) {
|
|
109
|
+
this.stopReasonKey = 'stop_reason'
|
|
110
|
+
this.generator = handleClaude3
|
|
108
111
|
} else if (bedrockCommand.isCohere() === true) {
|
|
109
112
|
this.stopReasonKey = 'generations.0.finish_reason'
|
|
110
113
|
this.generator = handleCohere
|
|
@@ -207,6 +210,31 @@ async function* handleClaude() {
|
|
|
207
210
|
}
|
|
208
211
|
}
|
|
209
212
|
|
|
213
|
+
async function* handleClaude3() {
|
|
214
|
+
let currentBody = {}
|
|
215
|
+
let stopReason
|
|
216
|
+
const completions = []
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
for await (const event of this.stream) {
|
|
220
|
+
yield event
|
|
221
|
+
const parsed = this.parseEvent(event)
|
|
222
|
+
this.updateHeaders(parsed)
|
|
223
|
+
currentBody = parsed
|
|
224
|
+
if (parsed.type === 'content_block_delta') {
|
|
225
|
+
completions.push(parsed.delta.text)
|
|
226
|
+
} else if (parsed.type === 'message_delta') {
|
|
227
|
+
stopReason = parsed.delta.stop_reason
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} finally {
|
|
231
|
+
currentBody.completions = completions
|
|
232
|
+
currentBody.stop_reason = stopReason
|
|
233
|
+
this.response.output.body = currentBody
|
|
234
|
+
this.finish()
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
210
238
|
async function* handleCohere() {
|
|
211
239
|
let currentBody = {}
|
|
212
240
|
const generations = []
|
|
@@ -376,10 +376,10 @@ function parseQuery(query, nodule) {
|
|
|
376
376
|
// strip enclosing special characters from collection (table) name
|
|
377
377
|
if (typeof collection === 'string' && collection.length > 2) {
|
|
378
378
|
if (/^[\[{'"`]/.test(collection)) {
|
|
379
|
-
collection = collection.
|
|
379
|
+
collection = collection.substring(1)
|
|
380
380
|
}
|
|
381
381
|
if (/[\]}'"`]$/.test(collection)) {
|
|
382
|
-
collection = collection.
|
|
382
|
+
collection = collection.substring(0, collection.length - 1)
|
|
383
383
|
}
|
|
384
384
|
}
|
|
385
385
|
|
|
@@ -187,7 +187,7 @@ NameState.prototype.getPath = function getPath() {
|
|
|
187
187
|
if (a[0] !== '/' && path[path.length - 1] !== '/') {
|
|
188
188
|
path += '/'
|
|
189
189
|
} else if (a[0] === '/' && path[path.length - 1] === '/') {
|
|
190
|
-
a = a.
|
|
190
|
+
a = a.substring(1)
|
|
191
191
|
}
|
|
192
192
|
path += a
|
|
193
193
|
}
|
package/lib/util/hashes.js
CHANGED
|
@@ -41,7 +41,8 @@ function calculatePathHash(appName, pathName, referingPathHash) {
|
|
|
41
41
|
const result = (rotated ^ hash) >>> 0
|
|
42
42
|
|
|
43
43
|
// This is a trick to pad it out to 8 chars regardless of length.
|
|
44
|
-
|
|
44
|
+
const str = '00000000' + result.toString(16)
|
|
45
|
+
return str.substring(str.length - 8)
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
function getHash(appName, txName) {
|
|
@@ -78,7 +79,7 @@ function int32ToByteArray(int32) {
|
|
|
78
79
|
// Lookup table for converting byte values to hex
|
|
79
80
|
const byteToHex = []
|
|
80
81
|
for (let i = 0; i < 256; ++i) {
|
|
81
|
-
byteToHex[i] = (i + 0x100).toString(16).
|
|
82
|
+
byteToHex[i] = (i + 0x100).toString(16).substring(1)
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
function makeId(length = 16) {
|
|
@@ -5,52 +5,152 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const fs = require('node:fs')
|
|
9
|
+
const http = require('node:http')
|
|
10
|
+
const log = require('../logger').child({ component: 'docker-info' })
|
|
9
11
|
const common = require('./common')
|
|
10
12
|
const NAMES = require('../metrics/names')
|
|
11
13
|
const os = require('os')
|
|
12
14
|
let vendorInfo = null
|
|
15
|
+
|
|
13
16
|
const CGROUPS_V1_PATH = '/proc/self/cgroup'
|
|
14
17
|
const CGROUPS_V2_PATH = '/proc/self/mountinfo'
|
|
18
|
+
const BOOT_ID_PROC_FILE = '/proc/sys/kernel/random/boot_id'
|
|
15
19
|
|
|
16
20
|
module.exports.getVendorInfo = fetchDockerVendorInfo
|
|
17
21
|
module.exports.clearVendorCache = function clearDockerVendorCache() {
|
|
18
22
|
vendorInfo = null
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
module.exports.getBootId = function getBootId(agent, callback) {
|
|
25
|
+
module.exports.getBootId = function getBootId(agent, callback, logger = log) {
|
|
22
26
|
if (!/linux/i.test(os.platform())) {
|
|
23
27
|
logger.debug('Platform is not a flavor of linux, omitting boot info')
|
|
24
28
|
return setImmediate(callback, null, null)
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
+
fs.access(BOOT_ID_PROC_FILE, fs.constants.F_OK, (err) => {
|
|
32
|
+
if (err == null) {
|
|
33
|
+
// The boot id proc file exists, so use it to get the container id.
|
|
34
|
+
return common.readProc(BOOT_ID_PROC_FILE, (_, data, cbAgent = agent) => {
|
|
35
|
+
readProcBootId({ data, agent: cbAgent, callback })
|
|
36
|
+
})
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
const asciiData = Buffer.from(data, 'ascii').toString()
|
|
39
|
+
logger.debug('Container boot id is not available in cgroups info')
|
|
35
40
|
|
|
36
|
-
if (
|
|
37
|
-
|
|
41
|
+
if (hasAwsContainerApi() === false) {
|
|
42
|
+
// We don't seem to have a recognized location for getting the container
|
|
43
|
+
// identifier.
|
|
44
|
+
logger.debug('Container is not in a recognized ECS container, omitting boot info')
|
|
45
|
+
recordBootIdError(agent)
|
|
38
46
|
return callback(null, null)
|
|
39
47
|
}
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
getEcsContainerId({ agent, callback, logger })
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Queries the AWS ECS metadata API to get the boot id.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} params Function parameters.
|
|
57
|
+
* @param {object} params.agent Newrelic agent instance.
|
|
58
|
+
* @param {Function} params.callback Typical error first callback. The second
|
|
59
|
+
* parameter is the boot id as a string.
|
|
60
|
+
* @param {object} [params.logger] Internal logger instance.
|
|
61
|
+
*/
|
|
62
|
+
function getEcsContainerId({ agent, callback, logger }) {
|
|
63
|
+
const ecsApiUrl =
|
|
64
|
+
process.env.ECS_CONTAINER_METADATA_URI_V4 || process.env.ECS_CONTAINER_METADATA_URI
|
|
65
|
+
const req = http.request(ecsApiUrl, (res) => {
|
|
66
|
+
let body = Buffer.alloc(0)
|
|
67
|
+
res.on('data', (chunk) => {
|
|
68
|
+
body = Buffer.concat([body, chunk])
|
|
69
|
+
})
|
|
70
|
+
res.on('end', () => {
|
|
71
|
+
try {
|
|
72
|
+
const json = body.toString('utf8')
|
|
73
|
+
const data = JSON.parse(json)
|
|
74
|
+
if (data.DockerId == null) {
|
|
75
|
+
logger.debug('Failed to find DockerId in response, omitting boot info')
|
|
76
|
+
recordBootIdError(agent)
|
|
77
|
+
return callback(null, null)
|
|
78
|
+
}
|
|
79
|
+
callback(null, data.DockerId)
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger.debug('Failed to process ECS API response, omitting boot info: ' + error.message)
|
|
82
|
+
recordBootIdError(agent)
|
|
83
|
+
callback(null, null)
|
|
45
84
|
}
|
|
46
|
-
}
|
|
85
|
+
})
|
|
86
|
+
})
|
|
47
87
|
|
|
48
|
-
|
|
88
|
+
req.on('error', () => {
|
|
89
|
+
logger.debug('Failed to query ECS endpoint, omitting boot info')
|
|
90
|
+
recordBootIdError(agent)
|
|
91
|
+
callback(null, null)
|
|
49
92
|
})
|
|
50
93
|
|
|
51
|
-
|
|
52
|
-
|
|
94
|
+
req.end()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Inspects the running environment to determine if the AWS ECS metadata API
|
|
99
|
+
* is available.
|
|
100
|
+
*
|
|
101
|
+
* @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ec2-metadata.html
|
|
102
|
+
*
|
|
103
|
+
* @returns {boolean}
|
|
104
|
+
*/
|
|
105
|
+
function hasAwsContainerApi() {
|
|
106
|
+
if (process.env.ECS_CONTAINER_METADATA_URI_V4 != null) {
|
|
107
|
+
return true
|
|
53
108
|
}
|
|
109
|
+
return process.env.ECS_CONTAINER_METADATA_URI != null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Increments a supportability metric to indicate that there was an error
|
|
114
|
+
* while trying to read the boot id from the system.
|
|
115
|
+
*
|
|
116
|
+
* @param {object} agent Newrelic agent instance.
|
|
117
|
+
*/
|
|
118
|
+
function recordBootIdError(agent) {
|
|
119
|
+
agent.metrics.getOrCreateMetric(NAMES.UTILIZATION.BOOT_ID_ERROR).incrementCallCount()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Utility function to parse a Docker boot id from a cgroup proc file.
|
|
124
|
+
*
|
|
125
|
+
* @param {Buffer} data The information from the proc file.
|
|
126
|
+
* @param {object} agent Newrelic agent instance.
|
|
127
|
+
* @param {Function} callback Typical error first callback. Second parameter
|
|
128
|
+
* is the boot id as a string.
|
|
129
|
+
*
|
|
130
|
+
* @returns {*}
|
|
131
|
+
*/
|
|
132
|
+
function readProcBootId({ data, agent, callback }) {
|
|
133
|
+
if (!data) {
|
|
134
|
+
recordBootIdError(agent)
|
|
135
|
+
return callback(null, null)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
data = data.trim()
|
|
139
|
+
const asciiData = Buffer.from(data, 'ascii').toString()
|
|
140
|
+
|
|
141
|
+
if (data !== asciiData) {
|
|
142
|
+
recordBootIdError(agent)
|
|
143
|
+
return callback(null, null)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (data.length !== 36) {
|
|
147
|
+
recordBootIdError(agent)
|
|
148
|
+
if (data.length > 128) {
|
|
149
|
+
data = data.substring(0, 128)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return callback(null, data)
|
|
54
154
|
}
|
|
55
155
|
|
|
56
156
|
/**
|
|
@@ -58,8 +158,9 @@ module.exports.getBootId = function getBootId(agent, callback) {
|
|
|
58
158
|
*
|
|
59
159
|
* @param {object} agent NR instance
|
|
60
160
|
* @param {Function} callback function to call when done
|
|
161
|
+
* @param {object} [logger] internal logger instance
|
|
61
162
|
*/
|
|
62
|
-
function fetchDockerVendorInfo(agent, callback) {
|
|
163
|
+
function fetchDockerVendorInfo(agent, callback, logger = log) {
|
|
63
164
|
if (!agent.config.utilization || !agent.config.utilization.detect_docker) {
|
|
64
165
|
return callback(null, null)
|
|
65
166
|
}
|
|
@@ -93,8 +194,9 @@ function fetchDockerVendorInfo(agent, callback) {
|
|
|
93
194
|
*
|
|
94
195
|
* @param {string} data file contents
|
|
95
196
|
* @param {Function} callback function to call when done
|
|
197
|
+
* @param {object} [logger] internal logger instance
|
|
96
198
|
*/
|
|
97
|
-
function parseCGroupsV2(data, callback) {
|
|
199
|
+
function parseCGroupsV2(data, callback, logger = log) {
|
|
98
200
|
const containerLine = new RegExp('/docker/containers/([0-9a-f]{64})/')
|
|
99
201
|
const line = containerLine.exec(data)
|
|
100
202
|
if (line) {
|
|
@@ -110,8 +212,9 @@ function parseCGroupsV2(data, callback) {
|
|
|
110
212
|
* e.g. - `4:cpu:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee`
|
|
111
213
|
*
|
|
112
214
|
* @param {Function} callback function to call when done
|
|
215
|
+
* @param {object} [logger] internal logger instance
|
|
113
216
|
*/
|
|
114
|
-
function findCGroupsV1(callback) {
|
|
217
|
+
function findCGroupsV1(callback, logger = log) {
|
|
115
218
|
common.readProc(CGROUPS_V1_PATH, function getCGroup(err, data) {
|
|
116
219
|
if (!data) {
|
|
117
220
|
logger.debug(`${CGROUPS_V1_PATH} not found, exiting parsing containerId.`)
|
|
@@ -54,8 +54,10 @@ function fetchGCPInfo(agent, callback) {
|
|
|
54
54
|
agent.metrics.getOrCreateMetric(NAMES.UTILIZATION.GCP_ERROR).incrementCallCount()
|
|
55
55
|
} else {
|
|
56
56
|
// normalize
|
|
57
|
-
results.machineType = results.machineType.
|
|
58
|
-
|
|
57
|
+
results.machineType = results.machineType.substring(
|
|
58
|
+
results.machineType.lastIndexOf('/') + 1
|
|
59
|
+
)
|
|
60
|
+
results.zone = results.zone.substring(results.zone.lastIndexOf('/') + 1)
|
|
59
61
|
|
|
60
62
|
resultDict = results
|
|
61
63
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newrelic",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.21.0",
|
|
4
4
|
"author": "New Relic Node.js agent team <nodejs@newrelic.com>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"contributors": [
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"lint:fix": "eslint --fix, ./*.{js,mjs} lib test bin examples",
|
|
164
164
|
"public-docs": "jsdoc -c ./jsdoc-conf.jsonc && cp examples/shim/*.png out/",
|
|
165
165
|
"publish-docs": "./bin/publish-docs.sh",
|
|
166
|
-
"services": "docker compose up -d --wait",
|
|
166
|
+
"services": "DOCKER_PLATFORM=linux/$(uname -m) docker compose up -d --wait",
|
|
167
167
|
"services:stop": "docker compose down",
|
|
168
168
|
"smoke": "npm run ssl && time tap test/smoke/**/**/*.tap.js --timeout=180 --no-coverage",
|
|
169
169
|
"ssl": "./bin/ssl.sh",
|