newrelic 12.2.0 → 12.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.
package/NEWS.md CHANGED
@@ -1,3 +1,32 @@
1
+ ### v12.3.0 (2024-08-27)
2
+
3
+ #### Features
4
+
5
+ * Added new API method `withLlmCustomAttributes` to run a function in a LLM context ([#2437](https://github.com/newrelic/node-newrelic/pull/2437)) ([57e6be9](https://github.com/newrelic/node-newrelic/commit/57e6be9f4717fde3caada0e3ca3680959180f928))
6
+ * The context will be used to assign custom attributes to every LLM event produced within the function
7
+
8
+ #### Bug fixes
9
+
10
+ * Improved AWS Lambda event detection ([#2498](https://github.com/newrelic/node-newrelic/pull/2498)) ([5e8b260](https://github.com/newrelic/node-newrelic/commit/5e8b2608d9914e2a4282f7c9c42ff17dfa9f793e))
11
+
12
+ #### Documentation
13
+
14
+ * Updated compatibility report ([#2493](https://github.com/newrelic/node-newrelic/pull/2493)) ([0448927](https://github.com/newrelic/node-newrelic/commit/0448927a49254b5b3c7ed9ff072cec24449fc558))
15
+
16
+ #### Miscellaneous chores
17
+ * Fixed linting scripts ([#2497](https://github.com/newrelic/node-newrelic/pull/2497)) ([c395779](https://github.com/newrelic/node-newrelic/commit/c395779f499cca0ec7f915342c23b2d2381b0163))
18
+ * Removed examples/shim ([#2484](https://github.com/newrelic/node-newrelic/pull/2484)) ([40d1f5c](https://github.com/newrelic/node-newrelic/commit/40d1f5ccc50d49805fc68946806fc9f74179673b))
19
+ * Updated test-utils dependency and added matrix-count only ([#2494](https://github.com/newrelic/node-newrelic/pull/2494)) ([5e04c76](https://github.com/newrelic/node-newrelic/commit/5e04c76600b8e6b7bfe331c2bec1b6cfa05ab922))
20
+
21
+ #### Tests
22
+
23
+ * Converted the api unit tests to `node:test` ([#2516](https://github.com/newrelic/node-newrelic/pull/2516)) ([ab91576](https://github.com/newrelic/node-newrelic/commit/ab91576fa949161f902b1604752a7fc38e7f2a74))
24
+ * Converted context-manager unit tests to `node:test` ([#2508](https://github.com/newrelic/node-newrelic/pull/2508)) ([9363eb0](https://github.com/newrelic/node-newrelic/commit/9363eb08ce8a13e67f94e5378ca95f32a562d504))
25
+
26
+ #### Continuous integration
27
+
28
+ * Updated codecov action sha to post coverage from forks. Added flag to fail ci if it fails to upload report ([#2490](https://github.com/newrelic/node-newrelic/pull/2490)) ([12fbe56](https://github.com/newrelic/node-newrelic/commit/12fbe56ca2581b3dd5cc5e2c1eceade46a8d191d))
29
+
1
30
  ### v12.2.0 (2024-08-19)
2
31
 
3
32
  #### Features
@@ -16,7 +16,6 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
16
16
 
17
17
  * [@grpc/grpc-js](#grpcgrpc-js)
18
18
  * [@grpc/proto-loader](#grpcproto-loader)
19
- * [@newrelic/ritm](#newrelicritm)
20
19
  * [@newrelic/security-agent](#newrelicsecurity-agent)
21
20
  * [@tyriar/fibonacci-heap](#tyriarfibonacci-heap)
22
21
  * [concat-stream](#concat-stream)
@@ -510,36 +509,6 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
510
509
 
511
510
  ```
512
511
 
513
- ### @newrelic/ritm
514
-
515
- This product includes source derived from [@newrelic/ritm](https://github.com/newrelic-forks/require-in-the-middle) ([v7.2.0](https://github.com/newrelic-forks/require-in-the-middle/tree/v7.2.0)), distributed under the [MIT License](https://github.com/newrelic-forks/require-in-the-middle/blob/v7.2.0/LICENSE):
516
-
517
- ```
518
- The MIT License (MIT)
519
-
520
- Copyright (c) 2016-2019, Thomas Watson Steen
521
- Copyright (c) 2019-2023, Elasticsearch B.V.
522
-
523
- Permission is hereby granted, free of charge, to any person obtaining a copy
524
- of this software and associated documentation files (the "Software"), to deal
525
- in the Software without restriction, including without limitation the rights
526
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
527
- copies of the Software, and to permit persons to whom the Software is
528
- furnished to do so, subject to the following conditions:
529
-
530
- The above copyright notice and this permission notice shall be included in all
531
- copies or substantial portions of the Software.
532
-
533
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
534
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
535
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
536
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
537
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
538
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
539
- SOFTWARE.
540
-
541
- ```
542
-
543
512
  ### @newrelic/security-agent
544
513
 
545
514
  This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v1.5.0](https://github.com/newrelic/csec-node-agent/tree/v1.5.0)), distributed under the [UNKNOWN License](https://github.com/newrelic/csec-node-agent/blob/v1.5.0/LICENSE):
@@ -1107,7 +1076,7 @@ SOFTWARE.
1107
1076
 
1108
1077
  ### @aws-sdk/client-s3
1109
1078
 
1110
- This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.631.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.631.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.631.0/LICENSE):
1079
+ This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.632.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.632.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.632.0/LICENSE):
1111
1080
 
1112
1081
  ```
1113
1082
  Apache License
@@ -1316,7 +1285,7 @@ This product includes source derived from [@aws-sdk/client-s3](https://github.co
1316
1285
 
1317
1286
  ### @aws-sdk/s3-request-presigner
1318
1287
 
1319
- This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.631.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.631.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.631.0/LICENSE):
1288
+ This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.632.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.632.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.632.0/LICENSE):
1320
1289
 
1321
1290
  ```
1322
1291
  Apache License
@@ -1971,7 +1940,7 @@ This product includes source derived from [@newrelic/newrelic-oss-cli](https://g
1971
1940
 
1972
1941
  ### @newrelic/test-utilities
1973
1942
 
1974
- This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v8.7.0](https://github.com/newrelic/node-test-utilities/tree/v8.7.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v8.7.0/LICENSE):
1943
+ This product includes source derived from [@newrelic/test-utilities](https://github.com/newrelic/node-test-utilities) ([v9.1.0](https://github.com/newrelic/node-test-utilities/tree/v9.1.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-test-utilities/blob/v9.1.0/LICENSE):
1975
1944
 
1976
1945
  ```
1977
1946
  Apache License
@@ -2210,7 +2179,7 @@ THE SOFTWARE.
2210
2179
 
2211
2180
  ### @slack/bolt
2212
2181
 
2213
- This product includes source derived from [@slack/bolt](https://github.com/slackapi/bolt) ([v3.21.0](https://github.com/slackapi/bolt/tree/v3.21.0)), distributed under the [MIT License](https://github.com/slackapi/bolt/blob/v3.21.0/LICENSE):
2182
+ This product includes source derived from [@slack/bolt](https://github.com/slackapi/bolt) ([v3.21.1](https://github.com/slackapi/bolt/tree/v3.21.1)), distributed under the [MIT License](https://github.com/slackapi/bolt/blob/v3.21.1/LICENSE):
2214
2183
 
2215
2184
  ```
2216
2185
  The MIT License (MIT)
@@ -2714,7 +2683,7 @@ THE SOFTWARE.
2714
2683
 
2715
2684
  ### aws-sdk
2716
2685
 
2717
- This product includes source derived from [aws-sdk](https://github.com/aws/aws-sdk-js) ([v2.1675.0](https://github.com/aws/aws-sdk-js/tree/v2.1675.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js/blob/v2.1675.0/LICENSE.txt):
2686
+ This product includes source derived from [aws-sdk](https://github.com/aws/aws-sdk-js) ([v2.1677.0](https://github.com/aws/aws-sdk-js/tree/v2.1677.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js/blob/v2.1677.0/LICENSE.txt):
2718
2687
 
2719
2688
  ```
2720
2689
 
@@ -4057,7 +4026,7 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
4057
4026
 
4058
4027
  ### self-cert
4059
4028
 
4060
- This product includes source derived from [self-cert](https://github.com/jsumners/self-cert) ([v2.0.0](https://github.com/jsumners/self-cert/tree/v2.0.0)), distributed under the [MIT License](https://github.com/jsumners/self-cert/blob/v2.0.0/Readme.md):
4029
+ This product includes source derived from [self-cert](https://github.com/jsumners/self-cert) ([v2.0.1](https://github.com/jsumners/self-cert/tree/v2.0.1)), distributed under the [MIT License](https://github.com/jsumners/self-cert/blob/v2.0.1/Readme.md):
4061
4030
 
4062
4031
  ```
4063
4032
  MIT License
package/api.js CHANGED
@@ -32,6 +32,7 @@ const obfuscate = require('./lib/util/sql/obfuscate')
32
32
  const { DESTINATIONS } = require('./lib/config/attribute-filter')
33
33
  const parse = require('module-details-from-path')
34
34
  const { isSimpleObject } = require('./lib/util/objects')
35
+ const { AsyncLocalStorage } = require('async_hooks')
35
36
 
36
37
  /*
37
38
  *
@@ -1902,4 +1903,52 @@ API.prototype.ignoreApdex = function ignoreApdex() {
1902
1903
  transaction.ignoreApdex = true
1903
1904
  }
1904
1905
 
1906
+ /**
1907
+ * Run a function with the passed in LLM context as the active context and return its return value.
1908
+ *
1909
+ * An example of setting a custom attribute:
1910
+ *
1911
+ * newrelic.withLlmCustomAttributes({'llm.someAttribute': 'someValue'}, () => {
1912
+ * return;
1913
+ * })
1914
+ * @param {Object} context LLM custom attributes context
1915
+ * @param {Function} callback The function to execute in context.
1916
+ */
1917
+ API.prototype.withLlmCustomAttributes = function withLlmCustomAttributes(context, callback) {
1918
+ context = context || {}
1919
+ const metric = this.agent.metrics.getOrCreateMetric(
1920
+ NAMES.SUPPORTABILITY.API + '/withLlmCustomAttributes'
1921
+ )
1922
+ metric.incrementCallCount()
1923
+
1924
+ const transaction = this.agent.tracer.getTransaction()
1925
+
1926
+ if (!callback || typeof callback !== 'function') {
1927
+ logger.warn('withLlmCustomAttributes must be used with a valid callback')
1928
+ return
1929
+ }
1930
+
1931
+ if (!transaction) {
1932
+ logger.warn('withLlmCustomAttributes must be called within the scope of a transaction.')
1933
+ return callback()
1934
+ }
1935
+
1936
+ for (const [key, value] of Object.entries(context)) {
1937
+ if (typeof value === 'object' || typeof value === 'function') {
1938
+ logger.warn(`Invalid attribute type for ${key}. Skipped.`)
1939
+ delete context[key]
1940
+ } else if (key.indexOf('llm.') !== 0) {
1941
+ logger.warn(`Invalid attribute name ${key}. Renamed to "llm.${key}".`)
1942
+ delete context[key]
1943
+ context[`llm.${key}`] = value
1944
+ }
1945
+ }
1946
+
1947
+ transaction._llmContextManager = transaction._llmContextManager || new AsyncLocalStorage()
1948
+ const parentContext = transaction._llmContextManager.getStore() || {}
1949
+
1950
+ const fullContext = Object.assign({}, parentContext, context)
1951
+ return transaction._llmContextManager.run(fullContext, callback)
1952
+ }
1953
+
1905
1954
  module.exports = API
@@ -18,6 +18,7 @@ const { DESTINATIONS } = require('../../../config/attribute-filter')
18
18
  const { AI } = require('../../../metrics/names')
19
19
  const { RecorderSpec } = require('../../../shim/specs')
20
20
  const InstrumentationDescriptor = require('../../../instrumentation-descriptor')
21
+ const { extractLlmContext } = require('../../../util/llm-utils')
21
22
 
22
23
  let TRACKING_METRIC
23
24
 
@@ -55,7 +56,12 @@ function isStreamingEnabled({ commandName, config }) {
55
56
  */
56
57
  function recordEvent({ agent, type, msg }) {
57
58
  msg.serialize()
58
- agent.customEventAggregator.add([{ type, timestamp: Date.now() }, msg])
59
+ const llmContext = extractLlmContext(agent)
60
+
61
+ agent.customEventAggregator.add([
62
+ { type, timestamp: Date.now() },
63
+ Object.assign({}, msg, llmContext)
64
+ ])
59
65
  }
60
66
 
61
67
  /**
@@ -7,6 +7,7 @@
7
7
  const {
8
8
  AI: { LANGCHAIN }
9
9
  } = require('../../metrics/names')
10
+ const { extractLlmContext } = require('../../util/llm-utils')
10
11
 
11
12
  const common = module.exports
12
13
 
@@ -49,7 +50,12 @@ common.mergeMetadata = function mergeMetadata(localMeta = {}, paramsMeta = {}) {
49
50
  */
50
51
  common.recordEvent = function recordEvent({ agent, type, msg, pkgVersion }) {
51
52
  agent.metrics.getOrCreateMetric(`${LANGCHAIN.TRACKING_PREFIX}/${pkgVersion}`).incrementCallCount()
52
- agent.customEventAggregator.add([{ type, timestamp: Date.now() }, msg])
53
+ const llmContext = extractLlmContext(agent)
54
+
55
+ agent.customEventAggregator.add([
56
+ { type, timestamp: Date.now() },
57
+ Object.assign({}, msg, llmContext)
58
+ ])
53
59
  }
54
60
 
55
61
  /**
@@ -12,6 +12,7 @@ const {
12
12
  LlmErrorMessage
13
13
  } = require('../../lib/llm-events/openai')
14
14
  const { RecorderSpec } = require('../../lib/shim/specs')
15
+ const { extractLlmContext } = require('../util/llm-utils')
15
16
 
16
17
  const MIN_VERSION = '4.0.0'
17
18
  const MIN_STREAM_VERSION = '4.12.2'
@@ -75,7 +76,12 @@ function decorateSegment({ shim, result, apiKey }) {
75
76
  * @param {object} params.msg LLM event
76
77
  */
77
78
  function recordEvent({ agent, type, msg }) {
78
- agent.customEventAggregator.add([{ type, timestamp: Date.now() }, msg])
79
+ const llmContext = extractLlmContext(agent)
80
+
81
+ agent.customEventAggregator.add([
82
+ { type, timestamp: Date.now() },
83
+ Object.assign({}, msg, llmContext)
84
+ ])
79
85
  }
80
86
 
81
87
  /**
@@ -106,38 +106,45 @@ function isLambdaProxyEvent(event) {
106
106
  return isGatewayV1Event(event) || isGatewayV2Event(event)
107
107
  }
108
108
 
109
- function isGatewayV1Event(event) {
110
- let result = false
111
-
112
- if (event?.version === '1.0') {
113
- result = true
114
- } else if (
115
- typeof event?.path === 'string' &&
116
- (event.headers ?? event.multiValueHeaders) &&
117
- typeof event?.httpMethod === 'string'
118
- // eslint-disable-next-line sonarjs/no-duplicated-branches
119
- ) {
120
- result = true
121
- }
109
+ const v1Keys = [
110
+ 'body',
111
+ 'headers',
112
+ 'httpMethod',
113
+ 'isBase64Encoded',
114
+ 'multiValueHeaders',
115
+ 'multiValueQueryStringParameters',
116
+ 'path',
117
+ 'pathParameters',
118
+ 'queryStringParameters',
119
+ 'requestContext',
120
+ 'resource',
121
+ 'stageVariables',
122
+ 'version'
123
+ ].join(',')
122
124
 
123
- return result
125
+ function isGatewayV1Event(event) {
126
+ const keys = Object.keys(event).sort().join(',')
127
+ return keys === v1Keys && event?.version === '1.0'
124
128
  }
125
129
 
126
- function isGatewayV2Event(event) {
127
- let result = false
128
-
129
- if (event?.version === '2.0') {
130
- result = true
131
- } else if (
132
- typeof event?.requestContext?.http?.path === 'string' &&
133
- Object.prototype.toString.call(event.headers) === '[object Object]' &&
134
- typeof event?.requestContext?.http?.method === 'string'
135
- // eslint-disable-next-line sonarjs/no-duplicated-branches
136
- ) {
137
- result = true
138
- }
130
+ const v2Keys = [
131
+ 'body',
132
+ 'cookies',
133
+ 'headers',
134
+ 'isBase64Encoded',
135
+ 'pathParameters',
136
+ 'queryStringParameters',
137
+ 'rawPath',
138
+ 'rawQueryString',
139
+ 'requestContext',
140
+ 'routeKey',
141
+ 'stageVariables',
142
+ 'version'
143
+ ].join(',')
139
144
 
140
- return result
145
+ function isGatewayV2Event(event) {
146
+ const keys = Object.keys(event).sort().join(',')
147
+ return keys === v2Keys && event?.version === '2.0'
141
148
  }
142
149
 
143
150
  /**
@@ -155,5 +162,7 @@ module.exports = {
155
162
  LambdaProxyWebRequest,
156
163
  LambdaProxyWebResponse,
157
164
  isLambdaProxyEvent,
158
- isValidLambdaProxyResponse
165
+ isValidLambdaProxyResponse,
166
+ isGatewayV1Event,
167
+ isGatewayV2Event
159
168
  }
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Copyright 2020 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ exports = module.exports = { extractLlmContext, extractLlmAttributes }
9
+
10
+ /**
11
+ * Extract LLM attributes from the LLM context
12
+ *
13
+ * @param {Object} context LLM context object
14
+ * @returns {Object} LLM custom attributes
15
+ */
16
+ function extractLlmAttributes(context) {
17
+ return Object.keys(context).reduce((result, key) => {
18
+ if (key.indexOf('llm.') === 0) {
19
+ result[key] = context[key]
20
+ }
21
+ return result
22
+ }, {})
23
+ }
24
+
25
+ /**
26
+ * Extract LLM context from the active transaction
27
+ *
28
+ * @param {Agent} agent NR agent instance
29
+ * @returns {Object} LLM context object
30
+ */
31
+ function extractLlmContext(agent) {
32
+ const context = agent.tracer.getTransaction()?._llmContextManager?.getStore() || {}
33
+ return extractLlmAttributes(context)
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "12.2.0",
3
+ "version": "12.3.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -164,9 +164,9 @@
164
164
  "integration": "npm run prepare-test && npm run sub-install && time c8 -o ./coverage/integration borp --timeout 600000 --reporter ./test/lib/test-reporter.mjs 'test/integration/**/*.tap.js'",
165
165
  "integration:esm": "NODE_OPTIONS='--loader=./esm-loader.mjs' time c8 -o ./coverage/integration-esm borp --reporter ./test/lib/test-reporter.mjs 'test/integration/**/*.tap.mjs'",
166
166
  "prepare-test": "npm run ssl && npm run docker-env",
167
- "lint": "eslint ./*.{js,mjs} lib test bin examples",
168
- "lint:fix": "eslint --fix, ./*.{js,mjs} lib test bin examples",
169
- "public-docs": "jsdoc -c ./jsdoc-conf.jsonc && cp examples/shim/*.png out/",
167
+ "lint": "eslint ./*.{js,mjs} lib test bin",
168
+ "lint:fix": "eslint --fix, ./*.{js,mjs} lib test bin",
169
+ "public-docs": "jsdoc -c ./jsdoc-conf.jsonc",
170
170
  "publish-docs": "./bin/publish-docs.sh",
171
171
  "services": "DOCKER_PLATFORM=linux/$(uname -m) docker compose up -d --wait",
172
172
  "services:stop": "docker compose down",
@@ -221,7 +221,7 @@
221
221
  "@koa/router": "^12.0.1",
222
222
  "@newrelic/eslint-config": "^0.3.0",
223
223
  "@newrelic/newrelic-oss-cli": "^0.1.2",
224
- "@newrelic/test-utilities": "^8.5.0",
224
+ "@newrelic/test-utilities": "^9.1.0",
225
225
  "@octokit/rest": "^18.0.15",
226
226
  "@slack/bolt": "^3.7.0",
227
227
  "@smithy/eventstream-codec": "^2.2.0",