newrelic 12.2.0 → 12.3.1

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,53 @@
1
+ ### v12.3.1 (2024-09-04)
2
+
3
+ #### Bug fixes
4
+
5
+ * Fixed detection of REST API type payloads in AWS Lambda ([#2543](https://github.com/newrelic/node-newrelic/pull/2543)) ([adfeebc](https://github.com/newrelic/node-newrelic/commit/adfeebc043161e0e0c35de2cf93989dbde9cb8fa))
6
+
7
+ #### Documentation
8
+
9
+ * Cleaned up formatting of api.js to properly inject example snippets when rendering on API docs site ([#2524](https://github.com/newrelic/node-newrelic/pull/2524)) ([4b34f3d](https://github.com/newrelic/node-newrelic/commit/4b34f3dbab45a55ec447b6e21b69c7621b41e539))
10
+ * Updated compatibility report ([#2523](https://github.com/newrelic/node-newrelic/pull/2523)) ([29784ea](https://github.com/newrelic/node-newrelic/commit/29784ea766b2a9388c050f271ab7190895bc22ed))
11
+ * Updated Next.js Otel cloud provider FAQ ([#2537](https://github.com/newrelic/node-newrelic/pull/2537)) ([6553807](https://github.com/newrelic/node-newrelic/commit/655380760a89193c5b6cd47d3955d1244cd79e7b))
12
+
13
+ #### Tests
14
+
15
+ * Converted db unit tests to node:test ([#2514](https://github.com/newrelic/node-newrelic/pull/2514)) ([bea4548](https://github.com/newrelic/node-newrelic/commit/bea45481a8a04099096929b36532203fbb8b6921))
16
+ * Converted grpc, lib, and utilization tests to `node:test` ([#2532](https://github.com/newrelic/node-newrelic/pull/2532)) ([c207e1e](https://github.com/newrelic/node-newrelic/commit/c207e1e3de75a9c3a2c4a05fa1bc318d3e455ef9))
17
+ * Replaced distributed tracing tests with `node:test` ([#2527](https://github.com/newrelic/node-newrelic/pull/2527)) ([8184c56](https://github.com/newrelic/node-newrelic/commit/8184c5676155b9028c84adc0da3902803ee9d107))
18
+ * Added a match function for tests ([#2541](https://github.com/newrelic/node-newrelic/pull/2541)) ([51e7f34](https://github.com/newrelic/node-newrelic/commit/51e7f34e733202a9c2c024d9d9a7f3c207dfc4b0))
19
+ * Converted `config` to `node:test` ([#2517](https://github.com/newrelic/node-newrelic/pull/2517)) ([1534a73](https://github.com/newrelic/node-newrelic/commit/1534a734995b6800c4cab3b6712f1b6b1329ed5e))
20
+
21
+
22
+ ### v12.3.0 (2024-08-27)
23
+
24
+ #### Features
25
+
26
+ * 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))
27
+ * The context will be used to assign custom attributes to every LLM event produced within the function
28
+
29
+ #### Bug fixes
30
+
31
+ * Improved AWS Lambda event detection ([#2498](https://github.com/newrelic/node-newrelic/pull/2498)) ([5e8b260](https://github.com/newrelic/node-newrelic/commit/5e8b2608d9914e2a4282f7c9c42ff17dfa9f793e))
32
+
33
+ #### Documentation
34
+
35
+ * Updated compatibility report ([#2493](https://github.com/newrelic/node-newrelic/pull/2493)) ([0448927](https://github.com/newrelic/node-newrelic/commit/0448927a49254b5b3c7ed9ff072cec24449fc558))
36
+
37
+ #### Miscellaneous chores
38
+ * Fixed linting scripts ([#2497](https://github.com/newrelic/node-newrelic/pull/2497)) ([c395779](https://github.com/newrelic/node-newrelic/commit/c395779f499cca0ec7f915342c23b2d2381b0163))
39
+ * Removed examples/shim ([#2484](https://github.com/newrelic/node-newrelic/pull/2484)) ([40d1f5c](https://github.com/newrelic/node-newrelic/commit/40d1f5ccc50d49805fc68946806fc9f74179673b))
40
+ * 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))
41
+
42
+ #### Tests
43
+
44
+ * 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))
45
+ * 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))
46
+
47
+ #### Continuous integration
48
+
49
+ * 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))
50
+
1
51
  ### v12.2.0 (2024-08-19)
2
52
 
3
53
  #### 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
  *
@@ -303,8 +304,7 @@ API.prototype.addCustomAttribute = function addCustomAttribute(key, value) {
303
304
  * See documentation for newrelic.addCustomAttribute for more information on
304
305
  * setting custom attributes.
305
306
  *
306
- * An example of setting a custom attribute object:
307
- *
307
+ * @example
308
308
  * newrelic.addCustomAttributes({test: 'value', test2: 'value2'});
309
309
  *
310
310
  * @param {object} [atts] Attribute object
@@ -331,7 +331,7 @@ API.prototype.addCustomAttributes = function addCustomAttributes(atts) {
331
331
  *
332
332
  * See documentation for newrelic.addCustomSpanAttribute for more information.
333
333
  *
334
- * An example of setting a custom span attribute:
334
+ * @example
335
335
  *
336
336
  * newrelic.addCustomSpanAttribute({test: 'value', test2: 'value2'})
337
337
  *
@@ -400,21 +400,20 @@ API.prototype.addCustomSpanAttribute = function addCustomSpanAttribute(key, valu
400
400
  * `Error` or one of its subtypes, but the API will handle strings and objects
401
401
  * that have an attached `.message` or `.stack` property.
402
402
  *
403
- * An example of using this function is
404
- *
405
- * try {
406
- * performSomeTask();
407
- * } catch (err) {
408
- * newrelic.noticeError(
409
- * err,
410
- * {extraInformation: "error already handled in the application"},
411
- * true
412
- * );
413
- * }
414
- *
415
403
  * NOTE: Errors that are recorded using this method do _not_ obey the
416
404
  * `ignore_status_codes` configuration.
417
405
  *
406
+ * @example
407
+ * try {
408
+ * performSomeTask();
409
+ * } catch (err) {
410
+ * newrelic.noticeError(
411
+ * err,
412
+ * {extraInformation: "error already handled in the application"},
413
+ * true
414
+ * );
415
+ * }
416
+ *
418
417
  * @param {Error} error
419
418
  * The error to be traced.
420
419
  * @param {object} [customAttributes]
@@ -473,13 +472,12 @@ API.prototype.noticeError = function noticeError(error, customAttributes, expect
473
472
  * If application log forwarding is disabled in the agent
474
473
  * configuration, this function does nothing.
475
474
  *
476
- * An example of using this function is
477
- *
478
- * newrelic.recordLogEvent({
479
- * message: 'cannot find file',
480
- * level: 'ERROR',
481
- * error: new SystemError('missing.txt')
482
- * })
475
+ * @example
476
+ * newrelic.recordLogEvent({
477
+ * message: 'cannot find file',
478
+ * level: 'ERROR',
479
+ * error: new SystemError('missing.txt')
480
+ * })
483
481
  *
484
482
  * @param {object} logEvent The log event object to send. Any
485
483
  * attributes besides `message`, `level`, `timestamp`, and `error` are
@@ -553,20 +551,21 @@ API.prototype.recordLogEvent = function recordLogEvent(logEvent = {}) {
553
551
  * GUIDs, or timestamps), the rule will generate too many metrics and
554
552
  * potentially get your application blocked by New Relic.
555
553
  *
556
- * An example of a good rule with replacements:
557
- *
558
- * newrelic.addNamingRule('^/storefront/(v[1-5])/(item|category|tag)',
559
- * 'CommerceAPI/$1/$2')
560
554
  *
561
- * An example of a bad rule with replacements:
555
+ * @example
556
+ * // An example of a good rule with replacements:
557
+ * newrelic.addNamingRule('^/storefront/(v[1-5])/(item|category|tag)',
558
+ * 'CommerceAPI/$1/$2')
562
559
  *
563
- * newrelic.addNamingRule('^/item/([0-9a-f]+)', 'Item/$1')
560
+ * @example
561
+ * // An example of a bad rule with replacements:
562
+ * newrelic.addNamingRule('^/item/([0-9a-f]+)', 'Item/$1')
564
563
  *
565
- * Keep in mind that the original URL and any query parameters will be sent
566
- * along with the request, so slow transactions will still be identifiable.
564
+ * // Keep in mind that the original URL and any query parameters will be sent
565
+ * // along with the request, so slow transactions will still be identifiable.
567
566
  *
568
- * Naming rules can not be removed once added. They can also be added via the
569
- * agent's configuration. See configuration documentation for details.
567
+ * // Naming rules can not be removed once added. They can also be added via the
568
+ * // agent's configuration. See configuration documentation for details.
570
569
  *
571
570
  * @param {RegExp} pattern The pattern to rename (with capture groups).
572
571
  * @param {string} name The name to use for the transaction.
@@ -590,8 +589,7 @@ API.prototype.addNamingRule = function addNamingRule(pattern, name) {
590
589
  * distorting an app's apdex or mean response time. Pattern may be a (standard
591
590
  * JavaScript) RegExp or a string.
592
591
  *
593
- * Example:
594
- *
592
+ * @example
595
593
  * newrelic.addIgnoringRule('^/socket\\.io/')
596
594
  *
597
595
  * @param {RegExp} pattern The pattern to ignore.
@@ -928,9 +926,9 @@ API.prototype.startSegment = function startSegment(name, record, handler, callba
928
926
  * will be ended when {@link TransactionHandle#end} is called in the user's code.
929
927
  *
930
928
  * @example
931
- * var newrelic = require('newrelic')
929
+ * const newrelic = require('newrelic')
932
930
  * newrelic.startWebTransaction('/some/url/path', function() {
933
- * var transaction = newrelic.getTransaction()
931
+ * const transaction = newrelic.getTransaction()
934
932
  * setTimeout(function() {
935
933
  * // do some work
936
934
  * transaction.end()
@@ -1016,9 +1014,9 @@ API.prototype.startBackgroundTransaction = startBackgroundTransaction
1016
1014
  * will be ended when {@link TransactionHandle#end} is called in the user's code.
1017
1015
  *
1018
1016
  * @example
1019
- * var newrelic = require('newrelic')
1017
+ * const newrelic = require('newrelic')
1020
1018
  * newrelic.startBackgroundTransaction('Red October', 'Subs', function() {
1021
- * var transaction = newrelic.getTransaction()
1019
+ * const transaction = newrelic.getTransaction()
1022
1020
  * setTimeout(function() {
1023
1021
  * // do some work
1024
1022
  * transaction.end()
@@ -1477,14 +1475,15 @@ API.prototype.instrumentMessages = function instrumentMessages(moduleName, onReq
1477
1475
  * Applies an instrumentation to an already loaded CommonJs module.
1478
1476
  *
1479
1477
  * Note: This function will not work for ESM packages.
1478
+ * @example
1480
1479
  *
1481
- * // oh no, express was loaded before newrelic
1482
- * const express = require('express')
1483
- * const newrelic = require('newrelic')
1480
+ * // oh no, express was loaded before newrelic
1481
+ * const express = require('express')
1482
+ * const newrelic = require('newrelic')
1484
1483
  *
1485
- * // phew, we can use instrumentLoadedModule to make
1486
- * // sure express is still instrumented
1487
- * newrelic.instrumentLoadedModule('express', express)
1484
+ * // phew, we can use instrumentLoadedModule to make
1485
+ * // sure express is still instrumented
1486
+ * newrelic.instrumentLoadedModule('express', express)
1488
1487
  *
1489
1488
  * @param {string} moduleName
1490
1489
  * The module's name/identifier. Will be normalized
@@ -1902,4 +1901,55 @@ API.prototype.ignoreApdex = function ignoreApdex() {
1902
1901
  transaction.ignoreApdex = true
1903
1902
  }
1904
1903
 
1904
+ /**
1905
+ * Run a function with the passed in LLM context as the active context and return its return value.
1906
+ *
1907
+ * @example
1908
+ * const OpenAI = require('openai')
1909
+ * const client = new OpenAI()
1910
+ * newrelic.withLlmCustomAttributes({'llm.someAttribute': 'someValue'}, async () => {
1911
+ * const response = await client.chat.completions.create({ messages: [
1912
+ * { role: 'user', content: 'Tell me about Node.js.'}
1913
+ * ]})
1914
+ * })
1915
+ * @param {Object} context LLM custom attributes context
1916
+ * @param {Function} callback The function to execute in context.
1917
+ */
1918
+ API.prototype.withLlmCustomAttributes = function withLlmCustomAttributes(context, callback) {
1919
+ context = context || {}
1920
+ const metric = this.agent.metrics.getOrCreateMetric(
1921
+ NAMES.SUPPORTABILITY.API + '/withLlmCustomAttributes'
1922
+ )
1923
+ metric.incrementCallCount()
1924
+
1925
+ const transaction = this.agent.tracer.getTransaction()
1926
+
1927
+ if (!callback || typeof callback !== 'function') {
1928
+ logger.warn('withLlmCustomAttributes must be used with a valid callback')
1929
+ return
1930
+ }
1931
+
1932
+ if (!transaction) {
1933
+ logger.warn('withLlmCustomAttributes must be called within the scope of a transaction.')
1934
+ return callback()
1935
+ }
1936
+
1937
+ for (const [key, value] of Object.entries(context)) {
1938
+ if (typeof value === 'object' || typeof value === 'function') {
1939
+ logger.warn(`Invalid attribute type for ${key}. Skipped.`)
1940
+ delete context[key]
1941
+ } else if (key.indexOf('llm.') !== 0) {
1942
+ logger.warn(`Invalid attribute name ${key}. Renamed to "llm.${key}".`)
1943
+ delete context[key]
1944
+ context[`llm.${key}`] = value
1945
+ }
1946
+ }
1947
+
1948
+ transaction._llmContextManager = transaction._llmContextManager || new AsyncLocalStorage()
1949
+ const parentContext = transaction._llmContextManager.getStore() || {}
1950
+
1951
+ const fullContext = Object.assign({}, parentContext, context)
1952
+ return transaction._llmContextManager.run(fullContext, callback)
1953
+ }
1954
+
1905
1955
  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
  /**
@@ -97,6 +97,10 @@ function normalizeHeaders(event, lowerCaseKey = false) {
97
97
 
98
98
  /**
99
99
  * Determines if Lambda event appears to be a valid Lambda Proxy event.
100
+ * There are multiple types of events possible. They are described at
101
+ * https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html#services-apigateway-apitypes.
102
+ * Each type of event has its own event payload structure; some types have
103
+ * multiple versions of the payload structure.
100
104
  *
101
105
  * @param {object} event The event to inspect.
102
106
  * @returns {boolean} Whether the given object contains fields necessary
@@ -106,38 +110,53 @@ function isLambdaProxyEvent(event) {
106
110
  return isGatewayV1Event(event) || isGatewayV2Event(event)
107
111
  }
108
112
 
113
+ // See https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
114
+ const restApiV1Keys = [
115
+ 'body',
116
+ 'headers',
117
+ 'httpMethod',
118
+ 'isBase64Encoded',
119
+ 'multiValueHeaders',
120
+ 'multiValueQueryStringParameters',
121
+ 'path',
122
+ 'pathParameters',
123
+ 'queryStringParameters',
124
+ 'requestContext',
125
+ 'resource',
126
+ 'stageVariables'
127
+ ].join(',')
128
+
129
+ // See https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
130
+ const httpApiV1Keys = [...restApiV1Keys.split(','), 'version'].join(',')
131
+
109
132
  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
133
+ const keys = Object.keys(event).sort().join(',')
134
+ if (keys === httpApiV1Keys && event?.version === '1.0') {
135
+ return true
121
136
  }
122
137
 
123
- return result
138
+ return keys === restApiV1Keys
124
139
  }
125
140
 
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
- }
141
+ // See https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
142
+ const httpApiV2Keys = [
143
+ 'body',
144
+ 'cookies',
145
+ 'headers',
146
+ 'isBase64Encoded',
147
+ 'pathParameters',
148
+ 'queryStringParameters',
149
+ 'rawPath',
150
+ 'rawQueryString',
151
+ 'requestContext',
152
+ 'routeKey',
153
+ 'stageVariables',
154
+ 'version'
155
+ ].join(',')
139
156
 
140
- return result
157
+ function isGatewayV2Event(event) {
158
+ const keys = Object.keys(event).sort().join(',')
159
+ return keys === httpApiV2Keys && event?.version === '2.0'
141
160
  }
142
161
 
143
162
  /**
@@ -155,5 +174,7 @@ module.exports = {
155
174
  LambdaProxyWebRequest,
156
175
  LambdaProxyWebResponse,
157
176
  isLambdaProxyEvent,
158
- isValidLambdaProxyResponse
177
+ isValidLambdaProxyResponse,
178
+ isGatewayV1Event,
179
+ isGatewayV2Event
159
180
  }
@@ -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.1",
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",