newrelic 13.2.1 → 13.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.
Files changed (56) hide show
  1. package/NEWS.md +44 -10
  2. package/api.js +1 -1
  3. package/lib/instrumentation/@azure/functions.js +8 -46
  4. package/lib/instrumentation/core/http-outbound.js +26 -10
  5. package/lib/instrumentation/core/http.js +20 -118
  6. package/lib/instrumentations.js +0 -14
  7. package/lib/otel/logs/index.js +80 -0
  8. package/lib/otel/metrics/index.js +119 -0
  9. package/lib/otel/setup-signal.js +37 -0
  10. package/lib/otel/setup.js +15 -25
  11. package/lib/otel/{attr-reconciler.js → traces/attr-reconciler.js} +2 -2
  12. package/lib/otel/{exception-mapping.js → traces/exception-mapping.js} +1 -1
  13. package/lib/otel/traces/index.js +40 -0
  14. package/lib/otel/{rules.js → traces/rules.js} +1 -1
  15. package/lib/otel/{segment-synthesis.js → traces/segment-synthesis.js} +5 -4
  16. package/lib/otel/{segments → traces/segments}/consumer.js +4 -4
  17. package/lib/otel/{segments → traces/segments}/database.js +6 -6
  18. package/lib/otel/{segments → traces/segments}/http-external.js +11 -8
  19. package/lib/otel/traces/segments/index.js +22 -0
  20. package/lib/otel/{segments → traces/segments}/internal.js +1 -1
  21. package/lib/otel/{segments → traces/segments}/producer.js +2 -2
  22. package/lib/otel/{segments → traces/segments}/server.js +4 -4
  23. package/lib/otel/{segments → traces/segments}/utils.js +1 -1
  24. package/lib/otel/{span-processor.js → traces/span-processor.js} +13 -19
  25. package/lib/otel/{transformation-rules.json → traces/transformation-rules.json} +3 -2
  26. package/lib/otel/{utils.js → traces/utils.js} +2 -1
  27. package/lib/serverless/aws-lambda.js +7 -68
  28. package/lib/shim/webframework-shim/middleware.js +1 -1
  29. package/lib/shimmer.js +1 -1
  30. package/lib/subscriber-configs.js +2 -1
  31. package/lib/subscribers/base.js +30 -0
  32. package/lib/subscribers/elasticsearch/config.js +1 -0
  33. package/lib/subscribers/mcp-sdk/client-request.js +66 -0
  34. package/lib/subscribers/mcp-sdk/config.js +13 -75
  35. package/lib/subscribers/openai/base.js +21 -0
  36. package/lib/subscribers/openai/chat-responses.js +15 -0
  37. package/lib/subscribers/openai/chat.js +85 -0
  38. package/lib/subscribers/openai/client.js +31 -0
  39. package/lib/subscribers/openai/config.js +85 -0
  40. package/lib/subscribers/openai/embeddings.js +54 -0
  41. package/lib/subscribers/openai/utils.js +349 -0
  42. package/lib/transaction/index.js +157 -56
  43. package/lib/transaction/trace/segment.js +5 -2
  44. package/lib/util/urltils.js +158 -192
  45. package/lib/utilization/common.js +0 -6
  46. package/package.json +1 -1
  47. package/lib/instrumentation/openai.js +0 -482
  48. package/lib/otel/logs/bootstrap-logs.js +0 -84
  49. package/lib/otel/metrics/bootstrap-metrics.js +0 -117
  50. package/lib/otel/segments/index.js +0 -22
  51. package/lib/subscribers/mcp-sdk/client-prompt.js +0 -23
  52. package/lib/subscribers/mcp-sdk/client-resource.js +0 -24
  53. package/lib/subscribers/mcp-sdk/client-tool.js +0 -23
  54. package/lib/subscribers/mcp-sdk/client.js +0 -29
  55. /package/lib/otel/{constants.js → traces/constants.js} +0 -0
  56. /package/lib/otel/{sampler.js → traces/sampler.js} +0 -0
@@ -2,6 +2,7 @@
2
2
  * Copyright 2025 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
+ 'use strict'
5
6
 
6
7
  module.exports = {
7
8
  '@elastic/elasticsearch': [{
@@ -0,0 +1,66 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const Subscriber = require('../base')
9
+ const { MCP } = require('../../metrics/names')
10
+
11
+ class McpClientRequestSubscriber extends Subscriber {
12
+ constructor({ agent, logger }) {
13
+ super({ agent, logger, packageName: '@modelcontextprotocol/sdk', channelName: 'nr_request' })
14
+ this.events = ['asyncEnd']
15
+ }
16
+
17
+ get enabled() {
18
+ return super.enabled && this.config.ai_monitoring.enabled === true
19
+ }
20
+
21
+ /**
22
+ * Determine the MCP function to record based on the incoming request.
23
+ * @param {*} args0 - The arguments passed to the request.
24
+ * @returns {Object} - An object containing the MCP prefix, function name, and primitive name.
25
+ */
26
+ determineFunction(args0) {
27
+ const methodName = args0?.method
28
+ let mcpPrefix
29
+ let functionName
30
+ let primitiveName
31
+
32
+ if (methodName === 'tools/call') {
33
+ mcpPrefix = MCP.TOOL
34
+ functionName = 'callTool'
35
+ primitiveName = args0?.params?.name ?? 'tool'
36
+ } else if (methodName === 'prompts/get') {
37
+ mcpPrefix = MCP.PROMPT
38
+ functionName = 'getPrompt'
39
+ primitiveName = args0?.params?.name ?? 'prompt'
40
+ } else if (methodName === 'resources/read') {
41
+ mcpPrefix = MCP.RESOURCE
42
+ functionName = 'readResource'
43
+ const uri = args0?.params?.uri
44
+ primitiveName = typeof uri === 'string' ? uri.split('://')[0] : 'resource'
45
+ } else {
46
+ this.logger.debug(`@modelcontextprotocol/sdk: Will not create '${methodName}' segment.`)
47
+ }
48
+ return { mcpPrefix, functionName, primitiveName }
49
+ }
50
+
51
+ handler(data, ctx) {
52
+ const { mcpPrefix, functionName, primitiveName } = this.determineFunction(data?.arguments?.[0])
53
+ if (!mcpPrefix || !functionName || !primitiveName) {
54
+ // If it is not a function we care about, don't instrument at all
55
+ return
56
+ }
57
+
58
+ const segmentName = `${mcpPrefix}/${functionName}/${primitiveName}`
59
+ return this.createSegment({
60
+ name: segmentName,
61
+ ctx
62
+ })
63
+ }
64
+ }
65
+
66
+ module.exports = McpClientRequestSubscriber
@@ -3,96 +3,32 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
- const mcpClientToolConfig = {
7
- path: './mcp-sdk/client-tool',
6
+ const mcpClientRequestConfig = {
7
+ path: './mcp-sdk/client-request',
8
8
  instrumentations: [
9
9
  {
10
- channelName: 'nr_callTool',
10
+ channelName: 'nr_request',
11
11
  module: {
12
12
  name: '@modelcontextprotocol/sdk',
13
13
  versionRange: '>=1.13.0',
14
- filePath: 'dist/cjs/client/index.js'
14
+ filePath: 'dist/cjs/shared/protocol.js'
15
15
  },
16
16
  functionQuery: {
17
- className: 'Client',
18
- methodName: 'callTool', // must be methodName, not functionName
17
+ className: 'Protocol',
18
+ methodName: 'request',
19
19
  kind: 'Async'
20
20
  }
21
21
  },
22
22
  {
23
- channelName: 'nr_callTool',
23
+ channelName: 'nr_request',
24
24
  module: {
25
25
  name: '@modelcontextprotocol/sdk',
26
26
  versionRange: '>=1.13.0',
27
- filePath: 'dist/esm/client/index.js'
27
+ filePath: 'dist/esm/shared/protocol.js'
28
28
  },
29
29
  functionQuery: {
30
- className: 'Client',
31
- methodName: 'callTool',
32
- kind: 'Async'
33
- }
34
- },
35
- ]
36
- }
37
-
38
- const mcpClientResourceConfig = {
39
- path: './mcp-sdk/client-resource',
40
- instrumentations: [
41
- {
42
- channelName: 'nr_readResource',
43
- module: {
44
- name: '@modelcontextprotocol/sdk',
45
- versionRange: '>=1.13.0',
46
- filePath: 'dist/cjs/client/index.js'
47
- },
48
- functionQuery: {
49
- className: 'Client',
50
- methodName: 'readResource',
51
- kind: 'Async'
52
- }
53
- },
54
- {
55
- channelName: 'nr_readResource',
56
- module: {
57
- name: '@modelcontextprotocol/sdk',
58
- versionRange: '>=1.13.0',
59
- filePath: 'dist/esm/client/index.js'
60
- },
61
- functionQuery: {
62
- className: 'Client',
63
- methodName: 'readResource',
64
- kind: 'Async'
65
- }
66
- },
67
- ]
68
- }
69
-
70
- const mcpClientPromptConfig = {
71
- path: './mcp-sdk/client-prompt',
72
- instrumentations: [
73
- {
74
- channelName: 'nr_getPrompt',
75
- module: {
76
- name: '@modelcontextprotocol/sdk',
77
- versionRange: '>=1.13.0',
78
- filePath: 'dist/cjs/client/index.js'
79
- },
80
- functionQuery: {
81
- className: 'Client',
82
- methodName: 'getPrompt',
83
- kind: 'Async'
84
- }
85
- },
86
- {
87
- channelName: 'nr_getPrompt',
88
- module: {
89
- name: '@modelcontextprotocol/sdk',
90
- versionRange: '>=1.13.0',
91
- filePath: 'dist/esm/client/index.js'
92
- },
93
- functionQuery: {
94
- className: 'Client',
95
- methodName: 'getPrompt',
30
+ className: 'Protocol',
31
+ methodName: 'request',
96
32
  kind: 'Async'
97
33
  }
98
34
  },
@@ -100,5 +36,7 @@ const mcpClientPromptConfig = {
100
36
  }
101
37
 
102
38
  module.exports = {
103
- '@modelcontextprotocol/sdk': [mcpClientToolConfig, mcpClientResourceConfig, mcpClientPromptConfig],
39
+ '@modelcontextprotocol/sdk': [
40
+ mcpClientRequestConfig
41
+ ]
104
42
  }
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const Subscriber = require('../base')
9
+
10
+ class OpenAISubscriber extends Subscriber {
11
+ constructor({ agent, logger, channelName }) {
12
+ super({ agent, logger, packageName: 'openai', channelName })
13
+ this.events = ['asyncEnd']
14
+ }
15
+
16
+ get enabled() {
17
+ return super.enabled && this.config.ai_monitoring?.enabled
18
+ }
19
+ }
20
+
21
+ module.exports = OpenAISubscriber
@@ -0,0 +1,15 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const OpenAIChatCompletions = require('./chat')
8
+
9
+ class OpenAIResponses extends OpenAIChatCompletions {
10
+ constructor({ agent, logger }) {
11
+ super({ agent, logger, channelName: 'nr_responses' })
12
+ }
13
+ }
14
+
15
+ module.exports = OpenAIResponses
@@ -0,0 +1,85 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ /*
8
+ * Copyright 2025 New Relic Corporation. All rights reserved.
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ */
11
+
12
+ const OpenAISubscriber = require('./base')
13
+ const { AI } = require('../../metrics/names')
14
+ const { OPENAI } = AI
15
+ const {
16
+ addLlmMeta,
17
+ recordChatCompletionMessages,
18
+ instrumentStream,
19
+ } = require('./utils')
20
+ const semver = require('semver')
21
+ const MIN_STREAM_VERSION = '4.12.2'
22
+
23
+ class OpenAIChatCompletions extends OpenAISubscriber {
24
+ constructor({ agent, logger, channelName = 'nr_completionsCreate' }) {
25
+ super({ agent, logger, channelName })
26
+ }
27
+
28
+ handler(data, ctx) {
29
+ const { arguments: args, moduleVersion } = data
30
+ const [request] = args
31
+ if (request.stream && semver.lt(moduleVersion, MIN_STREAM_VERSION)) {
32
+ this.logger.warn(`Instrumenting chat completion streams is only supported with openai version ${MIN_STREAM_VERSION}+.`)
33
+ return ctx
34
+ }
35
+
36
+ const segment = this.agent.tracer.createSegment({
37
+ name: OPENAI.COMPLETION,
38
+ parent: ctx.segment,
39
+ transaction: ctx.transaction
40
+ })
41
+ const newCtx = ctx.enterSegment({ segment })
42
+ return newCtx
43
+ }
44
+
45
+ asyncEnd(data) {
46
+ const ctx = this.agent.tracer.getContext()
47
+ if (!ctx?.segment || !ctx?.transaction) {
48
+ return
49
+ }
50
+ const { result: response, arguments: args, error: err } = data
51
+ const [request] = args
52
+
53
+ if (request.stream) {
54
+ instrumentStream({
55
+ agent: this.agent,
56
+ logger: this.logger,
57
+ request,
58
+ headers: ctx.extras?.headers,
59
+ response,
60
+ segment: ctx.segment,
61
+ transaction: ctx.transaction,
62
+ err
63
+ })
64
+ } else {
65
+ recordChatCompletionMessages({
66
+ agent: this.agent,
67
+ logger: this.logger,
68
+ segment: ctx.segment,
69
+ transaction: ctx.transaction,
70
+ request,
71
+ response,
72
+ headers: ctx.extras?.headers,
73
+ err
74
+ })
75
+ }
76
+
77
+ addLlmMeta({
78
+ agent: this.agent,
79
+ transaction: ctx.transaction,
80
+ version: data.moduleVersion,
81
+ })
82
+ }
83
+ }
84
+
85
+ module.exports = OpenAIChatCompletions
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const OpenAISubscriber = require('./base')
8
+ class OpenAIClientSubscriber extends OpenAISubscriber {
9
+ constructor({ agent, logger }) {
10
+ super({ agent, logger, channelName: 'nr_makeRequest' })
11
+ }
12
+
13
+ handler(data, ctx) {
14
+ const { self } = data
15
+ ctx.extras = { apiKey: self.apiKey }
16
+ return ctx
17
+ }
18
+
19
+ asyncEnd(data) {
20
+ const { result } = data
21
+ const ctx = this.agent.tracer.getContext()
22
+ if (ctx?.segment) {
23
+ const headers = result?.response?.headers
24
+ ? Object.fromEntries(result.response.headers)
25
+ : { ...result?.headers }
26
+ ctx.extras = { headers }
27
+ }
28
+ }
29
+ }
30
+
31
+ module.exports = OpenAIClientSubscriber
@@ -0,0 +1,85 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ module.exports = {
9
+ openai: [
10
+ {
11
+ path: './openai/chat.js',
12
+ instrumentations: [
13
+ {
14
+ channelName: 'nr_completionsCreate',
15
+ module: { name: 'openai', versionRange: '>=4.0.0', filePath: 'resources/chat/completions.js' },
16
+ functionQuery: {
17
+ className: 'Completions',
18
+ methodName: 'create',
19
+ kind: 'Async'
20
+ }
21
+ },
22
+ {
23
+ channelName: 'nr_completionsCreate',
24
+ module: { name: 'openai', versionRange: '>=4.0.0', filePath: 'resources/chat/completions/completions.js' },
25
+ functionQuery: {
26
+ className: 'Completions',
27
+ methodName: 'create',
28
+ kind: 'Async'
29
+ }
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ path: './openai/chat-responses.js',
35
+ instrumentations: [
36
+ {
37
+ channelName: 'nr_responses',
38
+ module: { name: 'openai', versionRange: '>=4.87.0', filePath: 'resources/responses/responses.js' },
39
+ functionQuery: {
40
+ className: 'Responses',
41
+ methodName: 'create',
42
+ kind: 'Async'
43
+ }
44
+ }
45
+ ]
46
+ },
47
+ {
48
+ path: './openai/client.js',
49
+ instrumentations: [
50
+ {
51
+ channelName: 'nr_makeRequest',
52
+ module: { name: 'openai', versionRange: '>=4.0.0', filePath: 'core.js' },
53
+ functionQuery: {
54
+ className: 'APIClient',
55
+ methodName: 'makeRequest',
56
+ kind: 'Async'
57
+ }
58
+ },
59
+ {
60
+ channelName: 'nr_makeRequest',
61
+ module: { name: 'openai', versionRange: '>=4.0.0', filePath: 'client.js' },
62
+ functionQuery: {
63
+ className: 'OpenAI',
64
+ methodName: 'makeRequest',
65
+ kind: 'Async'
66
+ }
67
+ }
68
+ ]
69
+ },
70
+ {
71
+ path: './openai/embeddings.js',
72
+ instrumentations: [
73
+ {
74
+ channelName: 'nr_embeddingsCreate',
75
+ module: { name: 'openai', versionRange: '>=4.0.0', filePath: 'resources/embeddings.js' },
76
+ functionQuery: {
77
+ className: 'Embeddings',
78
+ methodName: 'create',
79
+ kind: 'Async'
80
+ }
81
+ }
82
+ ]
83
+ }
84
+ ]
85
+ }
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const OpenAISubscriber = require('./base')
8
+ const { AI } = require('../../metrics/names')
9
+ const { OPENAI } = AI
10
+ const {
11
+ addLlmMeta,
12
+ recordEmbeddingMessage
13
+ } = require('./utils')
14
+
15
+ class OpenAIEmbeddings extends OpenAISubscriber {
16
+ constructor({ agent, logger }) {
17
+ super({ agent, logger, channelName: 'nr_embeddingsCreate' })
18
+ }
19
+
20
+ handler(data, ctx) {
21
+ const segment = this.agent.tracer.createSegment({
22
+ name: OPENAI.EMBEDDING,
23
+ parent: ctx.segment,
24
+ transaction: ctx.transaction
25
+ })
26
+ const newCtx = ctx.enterSegment({ segment })
27
+ return newCtx
28
+ }
29
+
30
+ asyncEnd(data) {
31
+ const ctx = this.agent.tracer.getContext()
32
+ if (!ctx?.segment || !ctx?.transaction) {
33
+ return
34
+ }
35
+ const { arguments: args, error: err } = data
36
+ const { result: response } = data
37
+ const [request] = args
38
+ const agent = this.agent
39
+ const { segment, transaction } = ctx
40
+ recordEmbeddingMessage({
41
+ agent,
42
+ logger: this.logger,
43
+ segment,
44
+ transaction,
45
+ request,
46
+ response,
47
+ headers: ctx.extras?.headers,
48
+ err
49
+ })
50
+ addLlmMeta({ agent, transaction, version: data.moduleVersion })
51
+ }
52
+ }
53
+
54
+ module.exports = OpenAIEmbeddings