newrelic 13.13.0 → 13.15.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.
Files changed (60) hide show
  1. package/NEWS.md +66 -0
  2. package/THIRD_PARTY_NOTICES.md +11 -11
  3. package/lib/aggregators/base-aggregator.js +2 -0
  4. package/lib/aggregators/profiling-aggregator.js +6 -1
  5. package/lib/errors/index.js +81 -21
  6. package/lib/harvester.js +18 -3
  7. package/lib/instrumentation/@azure/functions.js +45 -1
  8. package/lib/instrumentation/aws-sdk/v3/bedrock.js +15 -12
  9. package/lib/{llm-events/aws-bedrock → instrumentation/aws-sdk/v3}/converse-stream-handler.js +12 -0
  10. package/lib/{llm-events/aws-bedrock → instrumentation/aws-sdk/v3}/stream-handler.js +13 -0
  11. package/lib/instrumentation/redis.js +1 -0
  12. package/lib/instrumentations.js +0 -5
  13. package/lib/llm-events/aws-bedrock/bedrock-command.js +1 -1
  14. package/lib/llm-events/aws-bedrock/bedrock-response.js +1 -1
  15. package/lib/llm-events/aws-bedrock/chat-completion-message.js +40 -64
  16. package/lib/llm-events/aws-bedrock/chat-completion-summary.js +37 -41
  17. package/lib/llm-events/aws-bedrock/embedding.js +31 -29
  18. package/lib/llm-events/aws-bedrock/index.js +4 -9
  19. package/lib/llm-events/chat-completion-summary.js +6 -1
  20. package/lib/llm-events/error-message.js +2 -2
  21. package/lib/llm-events/google-genai/chat-completion-message.js +24 -30
  22. package/lib/llm-events/google-genai/chat-completion-summary.js +33 -11
  23. package/lib/llm-events/google-genai/embedding.js +25 -12
  24. package/lib/llm-events/google-genai/get-usage-tokens.js +26 -0
  25. package/lib/llm-events/google-genai/index.js +4 -12
  26. package/lib/llm-events/langgraph/agent.js +13 -29
  27. package/lib/llm-events/openai/chat-completion-summary.js +4 -2
  28. package/lib/profiling/index.js +2 -1
  29. package/lib/spans/span-event.js +8 -8
  30. package/lib/subscriber-configs.js +3 -0
  31. package/lib/subscribers/ai-monitoring/chat.js +9 -7
  32. package/lib/subscribers/ai-monitoring/embedding.js +1 -1
  33. package/lib/subscribers/google-genai/config.js +20 -2
  34. package/lib/subscribers/google-genai/embed-content.js +11 -1
  35. package/lib/subscribers/google-genai/generate-content-stream.js +3 -0
  36. package/lib/subscribers/google-genai/generate-content.js +40 -10
  37. package/lib/subscribers/langgraph/graph-stream.js +3 -3
  38. package/lib/subscribers/nestjs/config.js +27 -0
  39. package/lib/subscribers/nestjs/instrumentation.js +39 -0
  40. package/lib/subscribers/node-redis-client/add-command.js +13 -0
  41. package/lib/subscribers/node-redis-client/config.js +63 -0
  42. package/lib/subscribers/node-redis-client/multi.js +13 -0
  43. package/lib/subscribers/node-redis-client/send-command.js +12 -0
  44. package/lib/subscribers/openai/chat.js +11 -1
  45. package/lib/subscribers/redis-client/add-command.js +46 -0
  46. package/lib/subscribers/redis-client/client-propagation.js +108 -0
  47. package/lib/subscribers/redis-client/config.js +83 -0
  48. package/lib/subscribers/redis-client/multi.js +17 -0
  49. package/lib/subscribers/redis-client/send-command.js +17 -0
  50. package/lib/transaction/trace/partial-trace.js +9 -6
  51. package/package.json +2 -2
  52. package/lib/instrumentation/@nestjs/core.js +0 -35
  53. package/lib/instrumentation/@node-redis/client.js +0 -114
  54. package/lib/instrumentation/@redis/client.js +0 -7
  55. package/lib/instrumentation/generic-pool.js +0 -33
  56. package/lib/instrumentation/superagent.js +0 -68
  57. package/lib/llm-events/aws-bedrock/event.js +0 -79
  58. package/lib/llm-events/event.js +0 -130
  59. package/lib/llm-events/google-genai/event.js +0 -36
  60. package/lib/llm-events/aws-bedrock/{utils.js → stringify-message.js} +5 -5
package/NEWS.md CHANGED
@@ -1,3 +1,67 @@
1
+ ### v13.15.0 (2026-03-09)
2
+
3
+ #### Features
4
+
5
+ * Added `time_to_first_token` for `LlmChatCompletionSummary` ([#3781](https://github.com/newrelic/node-newrelic/pull/3781)) ([737f374](https://github.com/newrelic/node-newrelic/commit/737f3740d43908be348a7795dd2c85e7a4e88cb8))
6
+ * Added `time_to_first_token` for AWS Bedrock ([#3804](https://github.com/newrelic/node-newrelic/pull/3804)) ([a993b00](https://github.com/newrelic/node-newrelic/commit/a993b00b4fa55fb2e7ba0b7748c4c21d8660b911))
7
+ * Added `time_to_first_token` for Google Gen AI ([#3785](https://github.com/newrelic/node-newrelic/pull/3785)) ([d90ee70](https://github.com/newrelic/node-newrelic/commit/d90ee709538e430395c0fb5863c344fe37009ce5))
8
+ * Added `time_to_first_token` for OpenAI ([#3782](https://github.com/newrelic/node-newrelic/pull/3782)) ([07cfd57](https://github.com/newrelic/node-newrelic/commit/07cfd570d5cc3dd77a5ae23e88587d47af3d195a))
9
+
10
+ #### Code refactoring
11
+
12
+ * Migrated `redis` 4+ instrumentation to subscribe to events emitted ([#3773](https://github.com/newrelic/node-newrelic/pull/3773)) ([add6827](https://github.com/newrelic/node-newrelic/commit/add68278d29f5f415134426d33986a59c089d71f))
13
+
14
+ #### Documentation
15
+
16
+ * Updated compatibility report ([#3800](https://github.com/newrelic/node-newrelic/pull/3800)) ([5228cdc](https://github.com/newrelic/node-newrelic/commit/5228cdc0508217fa139d8b4f8232391fd063a5fa)) ([#3795](https://github.com/newrelic/node-newrelic/pull/3795)) ([1d6fe52](https://github.com/newrelic/node-newrelic/commit/1d6fe522f0e707729a4a01f5c5e5599670136917)) ([#3791](https://github.com/newrelic/node-newrelic/pull/3791)) ([e79db5b](https://github.com/newrelic/node-newrelic/commit/e79db5bbac1500bfa49f8ac3e9dc64c409396d79)) ([#3777](https://github.com/newrelic/node-newrelic/pull/3777)) ([12fe4c6](https://github.com/newrelic/node-newrelic/commit/12fe4c6f2a7a3f08efd874ba949150c7805a8888))
17
+
18
+ #### Miscellaneous chores
19
+
20
+ * Added version comment for agent-metadata.yml ([#3779](https://github.com/newrelic/node-newrelic/pull/3779)) ([1267293](https://github.com/newrelic/node-newrelic/commit/1267293cc36c79106b7cdd84dedf2c18b8a010d9))
21
+ * Refactored nestjs instrumentation to subscriber type ([#3792](https://github.com/newrelic/node-newrelic/pull/3792)) ([f7feb25](https://github.com/newrelic/node-newrelic/commit/f7feb25a5c7349433e7de26a0f6aebb820c034f4))
22
+ * Removed `@langchain/core` peer dep from langchain openai and aws tests ([#3788](https://github.com/newrelic/node-newrelic/pull/3788)) ([e670bb1](https://github.com/newrelic/node-newrelic/commit/e670bb1f47720c61509157cf45cd200234505733))
23
+ * Removed unneccessary `generic-pool` context propagation ([#3786](https://github.com/newrelic/node-newrelic/pull/3786)) ([bdd35fb](https://github.com/newrelic/node-newrelic/commit/bdd35fbf6b57d374de84f716488498a40ece41fd))
24
+ * Removed unnecessary `superagent` context propagation ([#3798](https://github.com/newrelic/node-newrelic/pull/3798)) ([81650e1](https://github.com/newrelic/node-newrelic/commit/81650e18d7da3897f9c82098def2f34c05c78d9e))
25
+ * Updated `@apm-js-collab/tracing-hooks` to `0.4.0` ([#3787](https://github.com/newrelic/node-newrelic/pull/3787)) ([d85fb2b](https://github.com/newrelic/node-newrelic/commit/d85fb2b832bc7202e5acafa19ff59094c6e386ee))
26
+ * Updated version format in agent-metadata.yml ([#3778](https://github.com/newrelic/node-newrelic/pull/3778)) ([61759e9](https://github.com/newrelic/node-newrelic/commit/61759e9c4e9ad2796a6462b967e30bad35b3fbf7))
27
+
28
+ #### Tests
29
+
30
+ * Updated langchain vectorstore tests to use a custom vectorstore to simplify the testing process ([#3793](https://github.com/newrelic/node-newrelic/pull/3793)) ([0fd9b54](https://github.com/newrelic/node-newrelic/commit/0fd9b54c0b4782162dae769453169ad4b05d7918))
31
+ * Updated version range for langgraph tests ([#3794](https://github.com/newrelic/node-newrelic/pull/3794)) ([e02ce99](https://github.com/newrelic/node-newrelic/commit/e02ce99083d584d8b61a223c67c4509f3fa9f4ce))
32
+
33
+ ### v13.14.0 (2026-02-24)
34
+
35
+ #### Features
36
+
37
+ * Added ability to delay the start of the Profiler and run for n milliseconds before shutting down. ([#3758](https://github.com/newrelic/node-newrelic/pull/3758)) ([8503d25](https://github.com/newrelic/node-newrelic/commit/8503d252dce31fa5ba0cbb4f146511155de48f88))
38
+ * Added functionality to ingest logs from Azure Functions App automatically ([#3772](https://github.com/newrelic/node-newrelic/pull/3772)) ([0c70ba2](https://github.com/newrelic/node-newrelic/commit/0c70ba20d2c03af03605ebc12baadbfd00c8733d))
39
+ * Added support for error causes ([#3769](https://github.com/newrelic/node-newrelic/pull/3769)) ([7c5aae3](https://github.com/newrelic/node-newrelic/commit/7c5aae3ac8b14e781a0740ffc57c2b2defc4300b))
40
+
41
+ #### Bug fixes
42
+
43
+ * Updated `@google/genai` subscriber to handle the new `embedContentInternal` method in 1.42.0 ([#3771](https://github.com/newrelic/node-newrelic/pull/3771)) ([e96c974](https://github.com/newrelic/node-newrelic/commit/e96c974a1aa8c15caebd66004c939c43f61cc0e2))
44
+
45
+ #### Code refactoring
46
+
47
+ * AWS Bedrock llm events refactor ([#3759](https://github.com/newrelic/node-newrelic/pull/3759)) ([7ff953d](https://github.com/newrelic/node-newrelic/commit/7ff953dbda4529591438811b8b58cfada3767f80))
48
+ * Google Gen AI LLM event refactor ([#3748](https://github.com/newrelic/node-newrelic/pull/3748)) ([f1e3a0e](https://github.com/newrelic/node-newrelic/commit/f1e3a0e57ea26370ce5021010ab6fe793dd5c594))
49
+ * LangGraph LlmAgent event, remove old LlmEvent base ([#3761](https://github.com/newrelic/node-newrelic/pull/3761)) ([39d1d9a](https://github.com/newrelic/node-newrelic/commit/39d1d9a59ca467d71d4a7db4cf437d743a24939b))
50
+ * Updated the reparenting logic for compact mode of partial traces to assign parent id to entry span ([#3768](https://github.com/newrelic/node-newrelic/pull/3768)) ([5196318](https://github.com/newrelic/node-newrelic/commit/5196318713cbeba113936a902ff4f092c33109cb))
51
+
52
+ #### Documentation
53
+
54
+ * Updated compatibility report ([#3774](https://github.com/newrelic/node-newrelic/pull/3774)) ([5d04bba](https://github.com/newrelic/node-newrelic/commit/5d04bba57ddb23b2aa1861cf46e393adae2c43ca))
55
+
56
+ #### Miscellaneous chores
57
+
58
+ * Added API key to enable support for GH action ([#3760](https://github.com/newrelic/node-newrelic/pull/3760)) ([a521e80](https://github.com/newrelic/node-newrelic/commit/a521e8065c525312d1170bfa725610d708b011e3))
59
+ * Added call to agent metadata workflow - step 1 ([#3750](https://github.com/newrelic/node-newrelic/pull/3750)) ([90eeaec](https://github.com/newrelic/node-newrelic/commit/90eeaec0e4de28c779ccab7a8f4b03448e8448bd))
60
+ * Added span id to all debug/trace level logs in `span.applyPartialTraceRules` ([#3766](https://github.com/newrelic/node-newrelic/pull/3766)) ([8c57230](https://github.com/newrelic/node-newrelic/commit/8c57230f3d580ace1cb5eb1611dc78f85bbe36f1))
61
+ * Renamed file and fix cache output ([#3757](https://github.com/newrelic/node-newrelic/pull/3757)) ([1ac00ea](https://github.com/newrelic/node-newrelic/commit/1ac00ea4718fb6e95502961d777e11c8ae71dde9))
62
+ * Set up metadata action to run with releases ([#3762](https://github.com/newrelic/node-newrelic/pull/3762)) ([66efb13](https://github.com/newrelic/node-newrelic/commit/66efb13b9bde33a28fb4256cb70bf313e10774bc))
63
+ * Updated `@langchain/core` version for langchain-openai tests ([#3763](https://github.com/newrelic/node-newrelic/pull/3763)) ([c27213e](https://github.com/newrelic/node-newrelic/commit/c27213e6ca3048463158fcfecb7e1cfcf87d4922))
64
+
1
65
  ### v13.13.0 (2026-02-17)
2
66
 
3
67
  ## Feature Release
@@ -8366,5 +8430,7 @@ Special thanks to Ryan Copley (@RyanCopley) for the contribution.
8366
8430
 
8367
8431
 
8368
8432
 
8433
+
8434
+
8369
8435
 
8370
8436
 
@@ -94,7 +94,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
94
94
 
95
95
  ### @apm-js-collab/tracing-hooks
96
96
 
97
- This product includes source derived from [@apm-js-collab/tracing-hooks](https://github.com/apm-js-collab/tracing-hooks) ([v0.3.1](https://github.com/apm-js-collab/tracing-hooks/tree/v0.3.1)), distributed under the [Apache-2.0 License](https://github.com/apm-js-collab/tracing-hooks/blob/v0.3.1/LICENSE):
97
+ This product includes source derived from [@apm-js-collab/tracing-hooks](https://github.com/apm-js-collab/tracing-hooks) ([v0.4.0](https://github.com/apm-js-collab/tracing-hooks/tree/v0.4.0)), distributed under the [Apache-2.0 License](https://github.com/apm-js-collab/tracing-hooks/blob/v0.4.0/LICENSE):
98
98
 
99
99
  ```
100
100
 
@@ -722,7 +722,7 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
722
722
 
723
723
  ### @newrelic/security-agent
724
724
 
725
- This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v3.0.1](https://github.com/newrelic/csec-node-agent/tree/v3.0.1)), distributed under the [UNKNOWN License](https://github.com/newrelic/csec-node-agent/blob/v3.0.1/LICENSE):
725
+ This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v3.0.2](https://github.com/newrelic/csec-node-agent/tree/v3.0.2)), distributed under the [UNKNOWN License](https://github.com/newrelic/csec-node-agent/blob/v3.0.2/LICENSE):
726
726
 
727
727
  ```
728
728
  ## New Relic Software License v1.0
@@ -1186,7 +1186,7 @@ This product includes source derived from [@opentelemetry/api](https://github.co
1186
1186
 
1187
1187
  ### @opentelemetry/core
1188
1188
 
1189
- This product includes source derived from [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.0](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.0)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.0/LICENSE):
1189
+ This product includes source derived from [@opentelemetry/core](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
1190
1190
 
1191
1191
  ```
1192
1192
  Apache License
@@ -1604,7 +1604,7 @@ This product includes source derived from [@opentelemetry/exporter-metrics-otlp-
1604
1604
 
1605
1605
  ### @opentelemetry/resources
1606
1606
 
1607
- This product includes source derived from [@opentelemetry/resources](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.0](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.0)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.0/LICENSE):
1607
+ This product includes source derived from [@opentelemetry/resources](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
1608
1608
 
1609
1609
  ```
1610
1610
  Apache License
@@ -2022,7 +2022,7 @@ This product includes source derived from [@opentelemetry/sdk-logs](https://gith
2022
2022
 
2023
2023
  ### @opentelemetry/sdk-metrics
2024
2024
 
2025
- This product includes source derived from [@opentelemetry/sdk-metrics](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.0](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.0)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.0/LICENSE):
2025
+ This product includes source derived from [@opentelemetry/sdk-metrics](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
2026
2026
 
2027
2027
  ```
2028
2028
  Apache License
@@ -2231,7 +2231,7 @@ This product includes source derived from [@opentelemetry/sdk-metrics](https://g
2231
2231
 
2232
2232
  ### @opentelemetry/sdk-trace-base
2233
2233
 
2234
- This product includes source derived from [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.0](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.0)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.0/LICENSE):
2234
+ This product includes source derived from [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) ([v2.5.1](https://github.com/open-telemetry/opentelemetry-js/tree/v2.5.1)), distributed under the [Apache-2.0 License](https://github.com/open-telemetry/opentelemetry-js/blob/v2.5.1/LICENSE):
2235
2235
 
2236
2236
  ```
2237
2237
  Apache License
@@ -2904,7 +2904,7 @@ SOFTWARE.
2904
2904
 
2905
2905
  ### semver
2906
2906
 
2907
- This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.7.3](https://github.com/npm/node-semver/tree/v7.7.3)), distributed under the [ISC License](https://github.com/npm/node-semver/blob/v7.7.3/LICENSE):
2907
+ This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.7.4](https://github.com/npm/node-semver/tree/v7.7.4)), distributed under the [ISC License](https://github.com/npm/node-semver/blob/v7.7.4/LICENSE):
2908
2908
 
2909
2909
  ```
2910
2910
  The ISC License
@@ -2960,7 +2960,7 @@ SOFTWARE.
2960
2960
 
2961
2961
  ### @aws-sdk/client-s3
2962
2962
 
2963
- This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.982.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.982.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.982.0/LICENSE):
2963
+ This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.996.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.996.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.996.0/LICENSE):
2964
2964
 
2965
2965
  ```
2966
2966
  Apache License
@@ -3169,7 +3169,7 @@ This product includes source derived from [@aws-sdk/client-s3](https://github.co
3169
3169
 
3170
3170
  ### @aws-sdk/s3-request-presigner
3171
3171
 
3172
- This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.982.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.982.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.982.0/LICENSE):
3172
+ This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.996.0](https://github.com/aws/aws-sdk-js-v3/tree/v3.996.0)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.996.0/LICENSE):
3173
3173
 
3174
3174
  ```
3175
3175
  Apache License
@@ -4510,7 +4510,7 @@ Apache License
4510
4510
 
4511
4511
  ### ajv
4512
4512
 
4513
- This product includes source derived from [ajv](https://github.com/ajv-validator/ajv) ([v6.12.6](https://github.com/ajv-validator/ajv/tree/v6.12.6)), distributed under the [MIT License](https://github.com/ajv-validator/ajv/blob/v6.12.6/LICENSE):
4513
+ This product includes source derived from [ajv](https://github.com/ajv-validator/ajv) ([v6.14.0](https://github.com/ajv-validator/ajv/tree/v6.14.0)), distributed under the [MIT License](https://github.com/ajv-validator/ajv/blob/v6.14.0/LICENSE):
4514
4514
 
4515
4515
  ```
4516
4516
  The MIT License (MIT)
@@ -5000,7 +5000,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
5000
5000
 
5001
5001
  ### eslint
5002
5002
 
5003
- This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v9.39.2](https://github.com/eslint/eslint/tree/v9.39.2)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v9.39.2/LICENSE):
5003
+ This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v9.39.3](https://github.com/eslint/eslint/tree/v9.39.3)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v9.39.3/LICENSE):
5004
5004
 
5005
5005
  ```
5006
5006
  Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
@@ -160,6 +160,8 @@ class Aggregator extends EventEmitter {
160
160
 
161
161
  this.defaultPeriod = this.periodMs = opts.periodMs
162
162
  this.defaultLimit = this.limit = opts.limit
163
+ this.delay = opts.delay ?? 0
164
+ this.duration = opts.duration ?? 0
163
165
  this.runId = opts.runId
164
166
  this.isAsync = opts.isAsync || false
165
167
  // function to pass in to determine if we can start a given aggregator
@@ -43,8 +43,13 @@ class ProfilingAggregator extends BaseAggregator {
43
43
  * and send the gzipped binary encoded data for each profiler
44
44
  */
45
45
  start() {
46
- logger.trace(`${this.method} aggregator started.`)
47
46
  this.profilingManager.register()
47
+ const started = this.profilingManager.start()
48
+ if (!started) {
49
+ return
50
+ }
51
+
52
+ logger.trace(`${this.method} aggregator started.`)
48
53
 
49
54
  if (!this.sendTimer) {
50
55
  this.sendTimer = setInterval(this.collectData.bind(this), this.periodMs)
@@ -18,6 +18,19 @@ const {
18
18
  const synthetics = require('../synthetics')
19
19
  const ERROR_EXPECTED_PATH = 'error.expected'
20
20
 
21
+ /**
22
+ * Wraps `Error` instances for delivery to the back end. The wrapper includes
23
+ * the original error along with metadata, such as custom attributes, to be
24
+ * delivered.
25
+ *
26
+ * @class
27
+ * @param {object} options Configuration options for the exception
28
+ * @param {Error} options.error The error object to be wrapped
29
+ * @param {number} [options.timestamp=0] Unix timestamp (in milliseconds) when the error occurred
30
+ * @param {object} [options.customAttributes={}] Custom user-defined attributes associated with the error
31
+ * @param {object} [options.agentAttributes={}] Agent-generated attributes associated with the error
32
+ * @param {boolean} [options.expected] Whether this error is expected (not a true failure)
33
+ */
21
34
  class Exception {
22
35
  constructor({ error, timestamp, customAttributes, agentAttributes, expected }) {
23
36
  this.error = error
@@ -45,6 +58,36 @@ class Exception {
45
58
  }
46
59
  }
47
60
 
61
+ /**
62
+ * Given the `.cause` value from an `Error` instance, serialize the chain
63
+ * into an object than can be attached to the agent attributes object.
64
+ *
65
+ * @param {Error|string} input The error cause to serialize.
66
+ * @returns {object[]} Serialized causes object array.
67
+ */
68
+ function serializeCauses(input) {
69
+ if (!input) {
70
+ return []
71
+ }
72
+
73
+ if (typeof input === 'string') {
74
+ return [{ message: input }]
75
+ }
76
+
77
+ if (
78
+ Object.hasOwn(input, 'message') === false ||
79
+ Object.hasOwn(input, 'stack') === false
80
+ ) {
81
+ return [{ message: input.message ?? 'cause does not look like an Error instance' }]
82
+ }
83
+
84
+ return [{
85
+ message: input.message,
86
+ stack: input.stack,
87
+ cause: input.cause ? serializeCauses(input.cause) : []
88
+ }]
89
+ }
90
+
48
91
  /**
49
92
  * Given either or both of a transaction and an exception, generate an error
50
93
  * trace in the JSON format expected by the collector. Since this will be
@@ -110,6 +153,21 @@ function createError(transaction, exception, config) {
110
153
 
111
154
  maybeAddAgentAttributes(params, exception)
112
155
 
156
+ // We need to detect error causes and do something with them. We don't have
157
+ // a spec, or ui expectation, for them. So we are opting to embed them as
158
+ // agent attributes. This does not account for `AggregateError` instances.
159
+ if (Object.hasOwn(error ?? {}, 'cause') === true) {
160
+ // We have to stringify the data in order for it to show up correctly
161
+ // in the errors inbox's list of attributes. If we rely on our aggregator
162
+ // to stringify it, then the backend will decode it to an array of objects
163
+ // and the attribute will not be presented in the UI. By doubly encoding
164
+ // it, the back end will decode it to a JSON _string_ and show the
165
+ // attribute in the UI as desired.
166
+ params.agentAttributes['error.cause'] = JSON.stringify(
167
+ serializeCauses(error.cause)
168
+ )
169
+ }
170
+
113
171
  return [0, name, message, type, params, transaction?.id]
114
172
  }
115
173
 
@@ -118,31 +176,33 @@ function isValidErrorGroupOutput(output) {
118
176
  }
119
177
 
120
178
  function maybeAddAgentAttributes(attributes, exception) {
121
- if (exception.errorGroupCallback) {
122
- const callbackInput = {
123
- error: exception.error,
124
- customAttributes: Object.assign({}, attributes.userAttributes),
125
- 'request.uri': attributes.agentAttributes['request.uri'],
126
- 'http.statusCode': attributes.agentAttributes['http.statusCode'],
127
- 'http.method': attributes.agentAttributes['request.method'],
128
- 'error.expected': attributes.intrinsics[ERROR_EXPECTED_PATH]
129
- }
179
+ if (typeof exception.errorGroupCallback !== 'function') {
180
+ return
181
+ }
130
182
 
131
- try {
132
- const callbackOutput = exception.errorGroupCallback(callbackInput)
183
+ const callbackInput = {
184
+ error: exception.error,
185
+ customAttributes: Object.assign({}, attributes.userAttributes),
186
+ 'request.uri': attributes.agentAttributes['request.uri'],
187
+ 'http.statusCode': attributes.agentAttributes['http.statusCode'],
188
+ 'http.method': attributes.agentAttributes['request.method'],
189
+ 'error.expected': attributes.intrinsics[ERROR_EXPECTED_PATH]
190
+ }
133
191
 
134
- if (!isValidErrorGroupOutput(callbackOutput)) {
135
- logger.warn('Function provided via setErrorGroupCallback return value malformed')
136
- return
137
- }
192
+ try {
193
+ const callbackOutput = exception.errorGroupCallback(callbackInput)
138
194
 
139
- attributes.agentAttributes['error.group.name'] = callbackOutput
140
- } catch (err) {
141
- logger.warn(
142
- err,
143
- 'Function provided via setErrorGroupCallback failed, not generating `error.group.name`'
144
- )
195
+ if (!isValidErrorGroupOutput(callbackOutput)) {
196
+ logger.warn('Function provided via setErrorGroupCallback return value malformed')
197
+ return
145
198
  }
199
+
200
+ attributes.agentAttributes['error.group.name'] = callbackOutput
201
+ } catch (err) {
202
+ logger.warn(
203
+ err,
204
+ 'Function provided via setErrorGroupCallback failed, not generating `error.group.name`'
205
+ )
146
206
  }
147
207
  }
148
208
 
package/lib/harvester.js CHANGED
@@ -2,16 +2,17 @@
2
2
  * Copyright 2024 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
-
6
5
  'use strict'
6
+ const defaultLogger = require('./logger').child({ component: 'harvester' })
7
7
 
8
8
  /**
9
9
  * @class
10
10
  * @classdesc Used to keep track of all registered aggregators.
11
11
  */
12
12
  module.exports = class Harvester {
13
- constructor() {
13
+ constructor({ logger = defaultLogger } = {}) {
14
14
  this.aggregators = []
15
+ this.logger = logger
15
16
  }
16
17
 
17
18
  /**
@@ -19,9 +20,23 @@ module.exports = class Harvester {
19
20
  */
20
21
  start() {
21
22
  for (const aggregator of this.aggregators) {
22
- if (aggregator.enabled) {
23
+ if (aggregator.enabled && aggregator.delay > 0) {
24
+ this.logger.debug(`Delay start of ${aggregator.method} by ${aggregator.delay} milliseconds`)
25
+ const timeout = setTimeout(() => {
26
+ aggregator.start()
27
+ }, aggregator.delay)
28
+ timeout.unref()
29
+ } else if (aggregator.enabled) {
23
30
  aggregator.start()
24
31
  }
32
+
33
+ if (aggregator.enabled && aggregator.duration > 0) {
34
+ this.logger.debug(`Running ${aggregator.method} for ${aggregator.duration} milliseconds`)
35
+ const durationTimeout = setTimeout(() => {
36
+ aggregator.stop()
37
+ }, aggregator.delay + aggregator.duration)
38
+ durationTimeout.unref()
39
+ }
25
40
  }
26
41
  }
27
42
 
@@ -10,6 +10,12 @@ const { Transform } = require('node:stream')
10
10
 
11
11
  const backgroundRecorder = require('../../metrics/recorders/other.js')
12
12
  const recordWeb = require('../../metrics/recorders/http')
13
+ const {
14
+ isApplicationLoggingEnabled,
15
+ isLogForwardingEnabled,
16
+ isMetricsEnabled,
17
+ incrementLoggingLinesMetrics
18
+ } = require('../../util/application-logging.js')
13
19
 
14
20
  const {
15
21
  DESTINATIONS: DESTS,
@@ -25,9 +31,16 @@ const SUBSCRIPTION_ID = WEBSITE_OWNER_NAME?.split('+').shift()
25
31
  const RESOURCE_GROUP_NAME = WEBSITE_RESOURCE_GROUP ?? WEBSITE_OWNER_NAME?.split('+').pop().split('-Linux').shift()
26
32
  const AZURE_FUNCTION_APP_NAME = WEBSITE_SITE_NAME
27
33
 
34
+ // Azure Functions uses 'information' as the context level for context.log and context.info and uses 'warning' as the context level for context.warn.
35
+ // These map to the NR standard names that LOGGING.LEVELS recognizes ('info', 'warn').
36
+ const AZURE_TO_NR_LEVEL = {
37
+ information: 'info',
38
+ warning: 'warn'
39
+ }
40
+
28
41
  let coldStart = true
29
42
 
30
- module.exports = function initialize(_agent, azureFunctions, _moduleName, shim, { logger = defaultLogger } = {}) {
43
+ module.exports = function initialize(agent, azureFunctions, _moduleName, shim, { logger = defaultLogger } = {}) {
31
44
  if (!SUBSCRIPTION_ID || !RESOURCE_GROUP_NAME || !AZURE_FUNCTION_APP_NAME) {
32
45
  logger.warn(
33
46
  {
@@ -59,6 +72,8 @@ module.exports = function initialize(_agent, azureFunctions, _moduleName, shim,
59
72
  'webPubSub'
60
73
  ]
61
74
  shim.wrap(azureFunctions.app, backgroundMethods, wrapAzureBackgroundMethods)
75
+
76
+ registerAzureLogs(agent, azureFunctions, logger)
62
77
  }
63
78
 
64
79
  function wrapAzureHttpMethods(shim, appMethod) {
@@ -174,6 +189,35 @@ function wrapAzureBackgroundMethods(shim, appMethod) {
174
189
  }
175
190
  }
176
191
 
192
+ function registerAzureLogs(agent, azureFunctions, logger) {
193
+ if (!isApplicationLoggingEnabled(agent.config)) {
194
+ logger.debug('Application logging not enabled. Not auto capturing logs from Azure Functions.')
195
+ return
196
+ }
197
+
198
+ azureFunctions.app.hook?.log((context) => {
199
+ const logLevel = AZURE_TO_NR_LEVEL[context.level] ?? context.level
200
+
201
+ if (isLogForwardingEnabled(agent.config, agent)) {
202
+ const meta = agent.getLinkingMetadata(true)
203
+
204
+ const logData = {
205
+ message: context.message,
206
+ level: logLevel,
207
+ timestamp: Date.now(),
208
+ category: context.category,
209
+ ...meta
210
+ }
211
+
212
+ agent.logs.add(logData)
213
+ }
214
+
215
+ if (isMetricsEnabled(agent.config)) {
216
+ incrementLoggingLinesMetrics(logLevel, agent.metrics)
217
+ }
218
+ })
219
+ }
220
+
177
221
  /**
178
222
  * Add required New Relic attributes to the transaction.
179
223
  *
@@ -4,22 +4,22 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
+ const StreamHandler = require('./stream-handler')
8
+ const ConverseStreamHandler = require('./converse-stream-handler')
7
9
  const {
8
10
  LlmChatCompletionMessage,
9
11
  LlmChatCompletionSummary,
10
12
  LlmEmbedding,
11
- LlmErrorMessage,
12
13
  BedrockCommand,
13
- BedrockResponse,
14
- StreamHandler
15
- } = require('../../../llm-events/aws-bedrock')
14
+ BedrockResponse
15
+ } = require('#agentlib/llm-events/aws-bedrock/index.js')
16
+ const LlmErrorMessage = require('#agentlib/llm-events/error-message.js')
16
17
 
17
18
  const { DESTINATIONS } = require('../../../config/attribute-filter')
18
19
  const { AI } = require('../../../metrics/names')
19
20
  const { RecorderSpec } = require('../../../shim/specs')
20
21
  const InstrumentationDescriptor = require('../../../instrumentation-descriptor')
21
22
  const { extractLlmContext } = require('../../../util/llm-utils')
22
- const ConverseStreamHandler = require('../../../llm-events/aws-bedrock/converse-stream-handler')
23
23
 
24
24
  let TRACKING_METRIC
25
25
 
@@ -73,7 +73,6 @@ function isStreamingEnabled({ commandName, config }) {
73
73
  * @param {object} params.msg LLM event
74
74
  */
75
75
  function recordEvent({ agent, type, msg }) {
76
- msg.serialize()
77
76
  const llmContext = extractLlmContext(agent)
78
77
  const timestamp = msg?.timestamp ?? Date.now()
79
78
 
@@ -113,6 +112,7 @@ function addLlmMeta({ agent, segment, transaction }) {
113
112
  * @param {BedrockResponse} params.bedrockResponse parsed response
114
113
  * @param {Shim} params.shim shim instance
115
114
  * @param {Transaction} params.transaction active transaction
115
+ * @param {number} [params.timeOfFirstToken] For streaming requests only, timestamp of when first token was sent.
116
116
  */
117
117
  function recordChatCompletionMessages({
118
118
  agent,
@@ -121,7 +121,8 @@ function recordChatCompletionMessages({
121
121
  transaction,
122
122
  bedrockCommand,
123
123
  bedrockResponse,
124
- err
124
+ err,
125
+ timeOfFirstToken
125
126
  }) {
126
127
  if (shouldSkipInstrumentation(agent.config) === true) {
127
128
  shim.logger.debug('skipping sending of ai data')
@@ -134,7 +135,8 @@ function recordChatCompletionMessages({
134
135
  bedrockCommand,
135
136
  transaction,
136
137
  segment,
137
- isError: err !== null
138
+ timeOfFirstToken,
139
+ error: err !== null
138
140
  })
139
141
 
140
142
  // Record context message(s)
@@ -149,7 +151,7 @@ function recordChatCompletionMessages({
149
151
  content: contextMessage.content,
150
152
  role: contextMessage.role,
151
153
  bedrockResponse,
152
- index: i,
154
+ sequence: i,
153
155
  completionId: summary.id
154
156
  })
155
157
  recordEvent({ agent, type: 'LlmChatCompletionMessage', msg })
@@ -164,7 +166,7 @@ function recordChatCompletionMessages({
164
166
  bedrockCommand,
165
167
  bedrockResponse,
166
168
  isResponse: true,
167
- index: promptContextMessages.length + i,
169
+ sequence: promptContextMessages.length + i,
168
170
  content,
169
171
  role: 'assistant',
170
172
  completionId: summary.id
@@ -217,9 +219,9 @@ function recordEmbeddingMessage({
217
219
  segment,
218
220
  transaction,
219
221
  bedrockCommand,
220
- input: prompt.content,
222
+ requestInput: prompt.content,
221
223
  bedrockResponse,
222
- isError: err !== null
224
+ error: err !== null
223
225
  }))
224
226
 
225
227
  for (const embedding of embeddings) {
@@ -342,6 +344,7 @@ function handleResponse({ shim, err, response, segment, transaction, bedrockComm
342
344
  transaction,
343
345
  bedrockCommand,
344
346
  bedrockResponse,
347
+ timeOfFirstToken: this?.timeOfFirstToken,
345
348
  err
346
349
  })
347
350
  } else if (modelType === 'embedding') {
@@ -80,12 +80,19 @@ class ConverseStreamHandler {
80
80
  */
81
81
  onComplete
82
82
 
83
+ /**
84
+ * Timestamp of when first token was sent; needed for construction of the
85
+ * `LlmChatCompletionSummary` event.
86
+ */
87
+ timeOfFirstToken
88
+
83
89
  constructor({ stream, passThroughParams, onComplete }) {
84
90
  this.passThroughParams = passThroughParams
85
91
  this.stream = stream
86
92
  this.onComplete = onComplete
87
93
  this.segment = passThroughParams.segment
88
94
  this.generator = handleConverse
95
+ this.timeOfFirstToken = null
89
96
  }
90
97
 
91
98
  /**
@@ -97,6 +104,7 @@ class ConverseStreamHandler {
97
104
  */
98
105
  finish() {
99
106
  this.passThroughParams.response = this.response
107
+ this.passThroughParams.timeOfFirstToken = this.timeOfFirstToken
100
108
  this.onComplete(this.passThroughParams)
101
109
 
102
110
  this.segment.touch()
@@ -136,6 +144,10 @@ async function * handleConverse() {
136
144
  activeChunk = null
137
145
  }
138
146
  } else if (event.contentBlockDelta?.delta) {
147
+ // Delta represents a letter/word/token
148
+ if (!this.timeOfFirstToken) {
149
+ this.timeOfFirstToken = Date.now()
150
+ }
139
151
  // There are also deltas for tool use (stringified inputs) but we don't currently record them so we just ignore for now
140
152
  if (event.contentBlockDelta.delta.text) {
141
153
  // It seems like the first streamed chunk does not always start with a contentBlockStart message
@@ -83,11 +83,18 @@ class StreamHandler {
83
83
  */
84
84
  onComplete
85
85
 
86
+ /**
87
+ * Timestamp of when first token was sent; needed for construction of the
88
+ * `LlmChatCompletionSummary` event.
89
+ */
90
+ timeOfFirstToken
91
+
86
92
  constructor({ stream, passThroughParams, onComplete }) {
87
93
  this.passThroughParams = passThroughParams
88
94
  this.stream = stream
89
95
  this.onComplete = onComplete
90
96
  this.segment = passThroughParams.segment
97
+ this.timeOfFirstToken = null
91
98
 
92
99
  const { bedrockCommand } = passThroughParams
93
100
 
@@ -204,6 +211,7 @@ async function * handleClaude() {
204
211
 
205
212
  try {
206
213
  for await (const event of this.stream) {
214
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
207
215
  yield event
208
216
  const parsed = this.parseEvent(event)
209
217
  this.updateHeaders(parsed)
@@ -224,6 +232,7 @@ async function * handleClaude3() {
224
232
 
225
233
  try {
226
234
  for await (const event of this.stream) {
235
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
227
236
  yield event
228
237
  const parsed = this.parseEvent(event)
229
238
  this.updateHeaders(parsed)
@@ -247,6 +256,7 @@ async function * handleCohere() {
247
256
  const generations = []
248
257
  try {
249
258
  for await (const event of this.stream) {
259
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
250
260
  yield event
251
261
  const parsed = this.parseEvent(event)
252
262
  this.updateHeaders(parsed)
@@ -265,6 +275,7 @@ async function * handleCohereEmbed() {
265
275
  const embeddings = []
266
276
  try {
267
277
  for await (const event of this.stream) {
278
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
268
279
  yield event
269
280
  const parsed = this.parseEvent(event)
270
281
  this.updateHeaders(parsed)
@@ -284,6 +295,7 @@ async function * handleLlama() {
284
295
 
285
296
  try {
286
297
  for await (const event of this.stream) {
298
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
287
299
  yield event
288
300
  const parsed = this.parseEvent(event)
289
301
  this.updateHeaders(parsed)
@@ -308,6 +320,7 @@ async function * handleTitan() {
308
320
 
309
321
  try {
310
322
  for await (const event of this.stream) {
323
+ if (!this.timeOfFirstToken) this.timeOfFirstToken = Date.now()
311
324
  yield event // Pass it up to the real consumer of the stream.
312
325
  const parsed = this.parseEvent(event)
313
326
  this.updateHeaders(parsed)