newrelic 13.1.0 → 13.2.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 +46 -0
- package/README.md +2 -4
- package/THIRD_PARTY_NOTICES.md +3 -3
- package/esm-loader.mjs +9 -1
- package/index.js +2 -2
- package/lib/agent.js +1 -2
- package/lib/config/build-instrumentation-config.js +3 -0
- package/lib/config/default.js +1397 -1395
- package/lib/config/index.js +15 -19
- package/lib/context-manager/context.js +13 -4
- package/lib/harvester.js +11 -13
- package/lib/instrumentation/@google/genai.js +8 -3
- package/lib/instrumentation/@hapi/hapi.js +4 -6
- package/lib/instrumentation/@nestjs/core.js +12 -14
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +2 -2
- package/lib/instrumentation/kafkajs/producer.js +13 -19
- package/lib/instrumentation/openai.js +2 -2
- package/lib/instrumentation/undici.js +48 -40
- package/lib/instrumentations.js +14 -5
- package/lib/llm-events/aws-bedrock/utils.js +1 -1
- package/lib/metrics/names.js +8 -0
- package/lib/metrics/normalizer.js +1 -3
- package/lib/otel/context.js +18 -14
- package/lib/otel/metrics/bootstrap-metrics.js +1 -1
- package/lib/patch-module.js +70 -0
- package/lib/serverless/aws-lambda.js +1 -3
- package/lib/shim/webframework-shim/middleware-mounter.js +1 -3
- package/lib/shimmer.js +56 -8
- package/lib/subscriber-configs.js +17 -0
- package/lib/subscribers/application-logs.js +55 -0
- package/lib/subscribers/base.js +177 -0
- package/lib/subscribers/create-config.js +27 -0
- package/lib/subscribers/db-operation.js +21 -0
- package/lib/subscribers/db-query.js +54 -0
- package/lib/subscribers/db.js +57 -0
- package/lib/subscribers/elasticsearch/config.js +49 -0
- package/lib/subscribers/elasticsearch/elasticsearch.js +42 -0
- package/lib/subscribers/elasticsearch/opensearch.js +15 -0
- package/lib/subscribers/elasticsearch/transport.js +14 -0
- package/lib/subscribers/ioredis/config.js +37 -0
- package/lib/subscribers/ioredis/index.js +48 -0
- package/lib/subscribers/mcp-sdk/client-prompt.js +23 -0
- package/lib/subscribers/mcp-sdk/client-resource.js +24 -0
- package/lib/subscribers/mcp-sdk/client-tool.js +23 -0
- package/lib/subscribers/mcp-sdk/client.js +29 -0
- package/lib/subscribers/mcp-sdk/config.js +104 -0
- package/lib/subscribers/pino/config.js +20 -0
- package/lib/subscribers/pino/index.js +102 -0
- package/lib/transaction/trace/aggregator.js +7 -9
- package/lib/util/camel-case.js +1 -3
- package/lib/util/get-package-version.js +34 -0
- package/lib/w3c/tracestate.js +3 -3
- package/package.json +7 -5
- package/lib/instrumentation/@elastic/elasticsearch.js +0 -62
- package/lib/instrumentation/@opensearch-project/opensearch.js +0 -66
- package/lib/instrumentation/ioredis.js +0 -50
- package/lib/instrumentation/pino/nr-hooks.js +0 -19
- package/lib/instrumentation/pino/pino.js +0 -168
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const DbQuerySubscriber = require('../db-query')
|
|
8
|
+
const stringify = require('json-stringify-safe')
|
|
9
|
+
const { queryParser } = require('../../db/query-parsers/elasticsearch')
|
|
10
|
+
|
|
11
|
+
class ElasticSearchSubscriber extends DbQuerySubscriber {
|
|
12
|
+
constructor({ agent, logger, packageName = '@elastic/elasticsearch', channelName = 'nr_request', system = 'ElasticSearch' } = {}) {
|
|
13
|
+
super({ agent, logger, packageName, channelName, system })
|
|
14
|
+
this.events = ['asyncEnd']
|
|
15
|
+
this.opaque = true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
handler(data, ctx) {
|
|
19
|
+
const { self, arguments: args } = data
|
|
20
|
+
this.queryString = stringify(args?.[0])
|
|
21
|
+
this.setParameters(self)
|
|
22
|
+
return super.handler(data, ctx)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setParameters(self) {
|
|
26
|
+
this.parameters = {}
|
|
27
|
+
this.parameters.product = this.system
|
|
28
|
+
const connectionPool = self?.connectionPool?.connections?.[0]
|
|
29
|
+
if (connectionPool) {
|
|
30
|
+
const host = connectionPool?.url?.host?.split(':')
|
|
31
|
+
const port = connectionPool?.url?.port || host?.[1]
|
|
32
|
+
this.parameters.host = host?.[0]
|
|
33
|
+
this.parameters.port_path_or_id = port
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
parseQuery(queryString) {
|
|
38
|
+
return queryParser(queryString)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = ElasticSearchSubscriber
|
|
@@ -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
|
+
|
|
8
|
+
const ElasticSearchSubscriber = require('./elasticsearch.js')
|
|
9
|
+
class OpenSearchSubscriber extends ElasticSearchSubscriber {
|
|
10
|
+
constructor({ agent, logger }) {
|
|
11
|
+
super({ agent, logger, packageName: '@opensearch-project/opensearch', channelName: 'nr_request', system: 'OpenSearch' })
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = OpenSearchSubscriber
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ElasticSearchSubscriber = require('./elasticsearch')
|
|
7
|
+
|
|
8
|
+
class ElasticSearchTransportSubscriber extends ElasticSearchSubscriber {
|
|
9
|
+
constructor({ agent, logger }) {
|
|
10
|
+
super({ agent, logger, packageName: '@elastic/transport' })
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = ElasticSearchTransportSubscriber
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
ioredis: [{
|
|
8
|
+
path: './ioredis',
|
|
9
|
+
instrumentations: [
|
|
10
|
+
{
|
|
11
|
+
channelName: 'nr_sendCommand',
|
|
12
|
+
module: { name: 'ioredis', versionRange: '>=4', filePath: 'built/Redis.js' },
|
|
13
|
+
functionQuery: {
|
|
14
|
+
className: 'Redis',
|
|
15
|
+
methodName: 'sendCommand',
|
|
16
|
+
kind: 'Async'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
channelName: 'nr_sendCommand',
|
|
21
|
+
module: { name: 'ioredis', versionRange: '>=4', filePath: 'built/redis.js' },
|
|
22
|
+
functionQuery: {
|
|
23
|
+
expressionName: 'sendCommand',
|
|
24
|
+
kind: 'Async'
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
channelName: 'nr_sendCommand',
|
|
29
|
+
module: { name: 'ioredis', versionRange: '>=4', filePath: 'built/redis/index.js' },
|
|
30
|
+
functionQuery: {
|
|
31
|
+
expressionName: 'sendCommand',
|
|
32
|
+
kind: 'Async'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}]
|
|
37
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const DbOperationSubscriber = require('../db-operation')
|
|
8
|
+
const stringify = require('json-stringify-safe')
|
|
9
|
+
|
|
10
|
+
class IoRedisSubscriber extends DbOperationSubscriber {
|
|
11
|
+
constructor({ agent, logger }) {
|
|
12
|
+
super({ agent, logger, packageName: 'ioredis', channelName: 'nr_sendCommand', system: 'Redis' })
|
|
13
|
+
this.events = ['asyncEnd']
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
const { self, arguments: args } = data
|
|
18
|
+
const [command] = args
|
|
19
|
+
this.operation = command.name
|
|
20
|
+
this.setParameters(self, command)
|
|
21
|
+
return super.handler(data, ctx)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setParameters(self, command) {
|
|
25
|
+
this.parameters = {}
|
|
26
|
+
this.parameters.product = this.system
|
|
27
|
+
this.parameters.host = self?.connector?.options?.host
|
|
28
|
+
this.parameters.port_path_or_id = self?.connector?.options?.port
|
|
29
|
+
this.parameters.key = this.parseKey(command.args)
|
|
30
|
+
this.parameters.database_name = self?.condition?.select
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
parseKey(keys) {
|
|
34
|
+
let key
|
|
35
|
+
if (keys && typeof keys !== 'function') {
|
|
36
|
+
try {
|
|
37
|
+
key = stringify(keys[0])
|
|
38
|
+
} catch (err) {
|
|
39
|
+
this.logger.debug(err, 'Failed to stringify ioredis key')
|
|
40
|
+
key = '<unknown>'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return key
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = IoRedisSubscriber
|
|
@@ -0,0 +1,23 @@
|
|
|
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 McpClientSubscriber = require('./client')
|
|
9
|
+
const { MCP } = require('../../metrics/names')
|
|
10
|
+
|
|
11
|
+
class McpClientPromptSubscriber extends McpClientSubscriber {
|
|
12
|
+
constructor({ agent, logger }) {
|
|
13
|
+
super({ agent, logger, channelName: 'nr_getPrompt' })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
const promptName = data?.arguments?.[0]?.name
|
|
18
|
+
this.segmentName = `${MCP.PROMPT}/getPrompt/${promptName}`
|
|
19
|
+
return super.handler(ctx)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = McpClientPromptSubscriber
|
|
@@ -0,0 +1,24 @@
|
|
|
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 McpClientSubscriber = require('./client')
|
|
9
|
+
const { MCP } = require('../../metrics/names')
|
|
10
|
+
|
|
11
|
+
class McpClientResourceSubscriber extends McpClientSubscriber {
|
|
12
|
+
constructor({ agent, logger }) {
|
|
13
|
+
super({ agent, logger, channelName: 'nr_readResource' })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
const uri = data?.arguments?.[0]?.uri
|
|
18
|
+
const scheme = typeof uri === 'string' ? uri.split('://')[0] : undefined
|
|
19
|
+
this.segmentName = `${MCP.RESOURCE}/readResource/${scheme}`
|
|
20
|
+
return super.handler(ctx)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = McpClientResourceSubscriber
|
|
@@ -0,0 +1,23 @@
|
|
|
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 McpClientSubscriber = require('./client')
|
|
9
|
+
const { MCP } = require('../../metrics/names')
|
|
10
|
+
|
|
11
|
+
class McpClientToolSubscriber extends McpClientSubscriber {
|
|
12
|
+
constructor({ agent, logger }) {
|
|
13
|
+
super({ agent, logger, channelName: 'nr_callTool' })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
const toolName = data?.arguments?.[0]?.name
|
|
18
|
+
this.segmentName = `${MCP.TOOL}/callTool/${toolName}`
|
|
19
|
+
return super.handler(ctx)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = McpClientToolSubscriber
|
|
@@ -0,0 +1,29 @@
|
|
|
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 McpClientSubscriber extends Subscriber {
|
|
11
|
+
constructor({ agent, logger, channelName }) {
|
|
12
|
+
super({ agent, logger, packageName: '@modelcontextprotocol/sdk', channelName })
|
|
13
|
+
this.events = ['asyncEnd']
|
|
14
|
+
this.segmentName = 'Unknown'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get enabled() {
|
|
18
|
+
return super.enabled && this.config.ai_monitoring.enabled === true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
handler(ctx) {
|
|
22
|
+
return this.createSegment({
|
|
23
|
+
name: this.segmentName,
|
|
24
|
+
ctx
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = McpClientSubscriber
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const mcpClientToolConfig = {
|
|
7
|
+
path: './mcp-sdk/client-tool',
|
|
8
|
+
instrumentations: [
|
|
9
|
+
{
|
|
10
|
+
channelName: 'nr_callTool',
|
|
11
|
+
module: {
|
|
12
|
+
name: '@modelcontextprotocol/sdk',
|
|
13
|
+
versionRange: '>=1.13.0',
|
|
14
|
+
filePath: 'dist/cjs/client/index.js'
|
|
15
|
+
},
|
|
16
|
+
functionQuery: {
|
|
17
|
+
className: 'Client',
|
|
18
|
+
methodName: 'callTool', // must be methodName, not functionName
|
|
19
|
+
kind: 'Async'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
channelName: 'nr_callTool',
|
|
24
|
+
module: {
|
|
25
|
+
name: '@modelcontextprotocol/sdk',
|
|
26
|
+
versionRange: '>=1.13.0',
|
|
27
|
+
filePath: 'dist/esm/client/index.js'
|
|
28
|
+
},
|
|
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',
|
|
96
|
+
kind: 'Async'
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
'@modelcontextprotocol/sdk': [mcpClientToolConfig, mcpClientResourceConfig, mcpClientPromptConfig],
|
|
104
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
pino: [{
|
|
8
|
+
path: './pino',
|
|
9
|
+
instrumentations: [
|
|
10
|
+
{
|
|
11
|
+
channelName: 'nr_asJson',
|
|
12
|
+
module: { name: 'pino', versionRange: '>=8.0.0', filePath: 'lib/tools.js' },
|
|
13
|
+
functionQuery: {
|
|
14
|
+
functionName: 'asJson',
|
|
15
|
+
kind: 'Sync'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}]
|
|
20
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const ApplicationLogsSubscriber = require('../application-logs')
|
|
8
|
+
const { truncate } = require('../../util/application-logging')
|
|
9
|
+
|
|
10
|
+
class PinoSubscriber extends ApplicationLogsSubscriber {
|
|
11
|
+
constructor({ agent, logger }) {
|
|
12
|
+
super({ agent, logger, packageName: 'pino', channelName: 'nr_asJson' })
|
|
13
|
+
this.events = ['end']
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handler(data, ctx) {
|
|
17
|
+
this.createModuleUsageMetric('pino')
|
|
18
|
+
const { self, arguments: args } = data
|
|
19
|
+
const level = self?.levels?.labels?.[args[2]]
|
|
20
|
+
this.incrementLinesMetric(level)
|
|
21
|
+
const useMergeObj = this.decorateLogLine(data)
|
|
22
|
+
ctx.extras = { useMergeObj }
|
|
23
|
+
return ctx
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
decorateLogLine(data) {
|
|
27
|
+
// Pino log methods accept a singular object (a merging object) that can
|
|
28
|
+
// have a `msg` property for the log message. In such cases, we need to
|
|
29
|
+
// update that log property instead of the second parameter.
|
|
30
|
+
const useMergeObj = data.arguments[1] === undefined && Object.hasOwn(data.arguments[0], 'msg')
|
|
31
|
+
const meta = super.decorateLogLine()
|
|
32
|
+
if (meta) {
|
|
33
|
+
if (useMergeObj === true) {
|
|
34
|
+
data.arguments[0].msg += this.agent.getNRLinkingMetadata()
|
|
35
|
+
} else {
|
|
36
|
+
data.arguments[1] += this.agent.getNRLinkingMetadata()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return useMergeObj
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
end(data) {
|
|
43
|
+
this.forwardLogLine(data)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* reformats error and assigns NR context data
|
|
48
|
+
* to log line
|
|
49
|
+
*
|
|
50
|
+
* @param {object} data event passed to the end handler
|
|
51
|
+
* @param {object} ctx agent context object
|
|
52
|
+
* @returns {function} wrapped log formatter function
|
|
53
|
+
*/
|
|
54
|
+
reformatLogLine(data, ctx) {
|
|
55
|
+
const subscriber = this
|
|
56
|
+
const { self, result: logLine, arguments: args } = data
|
|
57
|
+
const msg = ctx?.extras?.useMergeObj === true ? args[0].msg : args[1]
|
|
58
|
+
const level = self?.levels?.labels?.[args[2]]
|
|
59
|
+
const metadata = this.agent.getLinkingMetadata(true)
|
|
60
|
+
|
|
61
|
+
const agentMeta = Object.assign({}, { timestamp: Date.now() }, metadata)
|
|
62
|
+
// eslint-disable-next-line eqeqeq
|
|
63
|
+
if (msg != undefined) {
|
|
64
|
+
// The spec lists `message` as "MUST" under the required column, but then
|
|
65
|
+
// details that it "MUST be omitted" if the value is "empty". Additionally,
|
|
66
|
+
// if someone has logged only a merging object, and that object contains a
|
|
67
|
+
// message key, we do not want to overwrite their value. See issue 2595.
|
|
68
|
+
agentMeta.message = msg
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A function that gets executed in `_toPayloadSync` of log aggregator.
|
|
73
|
+
* This will parse the serialized log line and then add the relevant NR
|
|
74
|
+
* context metadata and rename the time/msg keys to timestamp/message
|
|
75
|
+
* @returns {object} formatted log line
|
|
76
|
+
*/
|
|
77
|
+
return function formatLogLine() {
|
|
78
|
+
let formattedLog
|
|
79
|
+
try {
|
|
80
|
+
formattedLog = JSON.parse(logLine)
|
|
81
|
+
} catch (err) {
|
|
82
|
+
subscriber.logger.error('Failed to parse log line as json: %s', err.message)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (formattedLog.err) {
|
|
87
|
+
formattedLog['error.message'] = truncate(formattedLog.err.message)
|
|
88
|
+
formattedLog['error.stack'] = truncate(formattedLog.err.stack)
|
|
89
|
+
formattedLog['error.class'] = formattedLog.err.type
|
|
90
|
+
delete formattedLog.err
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Object.assign(formattedLog, agentMeta)
|
|
94
|
+
formattedLog.level = level
|
|
95
|
+
delete formattedLog.time
|
|
96
|
+
delete formattedLog.msg
|
|
97
|
+
return formattedLog
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = PinoSubscriber
|
|
@@ -190,16 +190,14 @@ class TransactionTraceAggregator extends TraceAggregator {
|
|
|
190
190
|
return callback(null, traces)
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
const tracePromises = traces.map((trace) => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
resolve(data)
|
|
200
|
-
})
|
|
193
|
+
const tracePromises = traces.map((trace) => new Promise((resolve, reject) => {
|
|
194
|
+
trace.generateJSON((err, data) => {
|
|
195
|
+
if (err) {
|
|
196
|
+
reject(err)
|
|
197
|
+
}
|
|
198
|
+
resolve(data)
|
|
201
199
|
})
|
|
202
|
-
})
|
|
200
|
+
}))
|
|
203
201
|
|
|
204
202
|
try {
|
|
205
203
|
const encodedTraces = await Promise.all(tracePromises)
|
package/lib/util/camel-case.js
CHANGED
|
@@ -19,8 +19,6 @@ const words = (string) => string.match(wordPattern) || []
|
|
|
19
19
|
*/
|
|
20
20
|
module.exports = function toCamelCase(string) {
|
|
21
21
|
return words(string)
|
|
22
|
-
.map((word, index) =>
|
|
23
|
-
index === 0 ? word.toLowerCase() : word.slice(0, 1).toUpperCase() + word.slice(1)
|
|
24
|
-
)
|
|
22
|
+
.map((word, index) => (index === 0 ? word.toLowerCase() : word.slice(0, 1).toUpperCase() + word.slice(1)))
|
|
25
23
|
.join('')
|
|
26
24
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const { readFileSync } = require('node:fs')
|
|
8
|
+
const { join } = require('node:path')
|
|
9
|
+
|
|
10
|
+
const packageVersions = new Map()
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Retrieves the version of a package from its package.json file.
|
|
14
|
+
* If the package.json file cannot be read, it defaults to the Node.js version.
|
|
15
|
+
* @param {string} baseDir - The base directory where the package.json file is located.
|
|
16
|
+
* @returns {string} The version of the package or the Node.js version if the package.json cannot be read.
|
|
17
|
+
*/
|
|
18
|
+
function getPackageVersion(baseDir) {
|
|
19
|
+
if (packageVersions.has(baseDir)) {
|
|
20
|
+
return packageVersions.get(baseDir)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const packageJsonPath = join(baseDir, 'package.json')
|
|
25
|
+
const jsonFile = readFileSync(packageJsonPath)
|
|
26
|
+
const { version } = JSON.parse(jsonFile)
|
|
27
|
+
packageVersions.set(baseDir, version)
|
|
28
|
+
return version
|
|
29
|
+
} catch {
|
|
30
|
+
return process.version.slice(1)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = getPackageVersion
|
package/lib/w3c/tracestate.js
CHANGED
|
@@ -69,7 +69,7 @@ class Tracestate {
|
|
|
69
69
|
const vendors = new Set()
|
|
70
70
|
const listMembers = new Map()
|
|
71
71
|
for (const listMember of kvPairs) {
|
|
72
|
-
const parts = listMember.split('=', 2).filter(v => Boolean(v))
|
|
72
|
+
const parts = listMember.split('=', 2).filter((v) => Boolean(v))
|
|
73
73
|
if (parts.length !== 2) {
|
|
74
74
|
this.#logger.debug('Unable to parse tracestate list members.')
|
|
75
75
|
this.#agent.recordSupportability('TraceContext/TraceState/Parse/Exception/ListMember')
|
|
@@ -94,7 +94,7 @@ class Tracestate {
|
|
|
94
94
|
let intrinsics
|
|
95
95
|
if (nrTraceStateValue) {
|
|
96
96
|
intrinsics = new TracestateIntrinsics()
|
|
97
|
-
const values = nrTraceStateValue.split('-').map(v => v === '' ? null : v)
|
|
97
|
+
const values = nrTraceStateValue.split('-').map((v) => (v === '' ? null : v))
|
|
98
98
|
intrinsics.version = values[0]
|
|
99
99
|
intrinsics.parentType = values[1]
|
|
100
100
|
intrinsics.accountId = values[2]
|
|
@@ -150,7 +150,7 @@ class Tracestate {
|
|
|
150
150
|
if (typeof header !== 'string') {
|
|
151
151
|
throw Error('header value must be a string')
|
|
152
152
|
}
|
|
153
|
-
const kvPairs = header.split(',').map(item => item.trim()).filter(Boolean)
|
|
153
|
+
const kvPairs = header.split(',').map((item) => item.trim()).filter(Boolean)
|
|
154
154
|
return new Tracestate({ agent, logger, kvPairs })
|
|
155
155
|
}
|
|
156
156
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newrelic",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.2.0",
|
|
4
4
|
"author": "New Relic Node.js agent team <nodejs@newrelic.com>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"contributors": [
|
|
@@ -172,11 +172,11 @@
|
|
|
172
172
|
"services": "DOCKER_PLATFORM=linux/$(uname -m) docker compose up -d --wait",
|
|
173
173
|
"services:start": "npm run services",
|
|
174
174
|
"services:stop": "docker compose down",
|
|
175
|
-
"smoke": "time borp --timeout 180000 --reporter ./test/lib/test-reporter.mjs 'test/smoke/**/*.
|
|
175
|
+
"smoke": "time borp --timeout 180000 --reporter ./test/lib/test-reporter.mjs 'test/smoke/**/*.test.js'",
|
|
176
176
|
"sub-install": "node test/bin/install_sub_deps",
|
|
177
177
|
"test": "npm run integration && npm run unit",
|
|
178
178
|
"third-party-updates": "oss third-party manifest --includeOptDeps && oss third-party notices --includeOptDeps && git add THIRD_PARTY_NOTICES.md third_party_manifest.json",
|
|
179
|
-
"unit": "rm -f newrelic_agent.log && time c8 -o ./coverage/unit borp --timeout 180000 --reporter ./test/lib/test-reporter.mjs 'test/unit/**/*.test.js'",
|
|
179
|
+
"unit": "rm -f newrelic_agent.log && time c8 -o ./coverage/unit borp --timeout 180000 --reporter ./test/lib/test-reporter.mjs 'test/unit/**/*.test.{js,mjs}'",
|
|
180
180
|
"unit:scripts": "time c8 -o ./coverage/scripts-unit borp --reporter ./test/lib/test-reporter.mjs 'bin/test/*.test.js'",
|
|
181
181
|
"update-cross-agent-tests": "./bin/update-cats.sh",
|
|
182
182
|
"versioned-tests": "./bin/run-versioned-tests.sh",
|
|
@@ -201,6 +201,7 @@
|
|
|
201
201
|
"#test/assert": "./test/lib/custom-assertions/index.js"
|
|
202
202
|
},
|
|
203
203
|
"dependencies": {
|
|
204
|
+
"@apm-js-collab/code-transformer": "^0.6.0",
|
|
204
205
|
"@grpc/grpc-js": "^1.13.2",
|
|
205
206
|
"@grpc/proto-loader": "^0.7.5",
|
|
206
207
|
"@newrelic/security-agent": "^2.4.2",
|
|
@@ -234,7 +235,7 @@
|
|
|
234
235
|
"@aws-sdk/s3-request-presigner": "^3.556.0",
|
|
235
236
|
"@koa/router": "^12.0.1",
|
|
236
237
|
"@matteo.collina/tspl": "^0.1.1",
|
|
237
|
-
"@newrelic/eslint-config": "^0.
|
|
238
|
+
"@newrelic/eslint-config": "^0.6.0",
|
|
238
239
|
"@newrelic/newrelic-oss-cli": "^0.1.2",
|
|
239
240
|
"@newrelic/test-utilities": "^10.0.0",
|
|
240
241
|
"@octokit/rest": "^18.0.15",
|
|
@@ -271,7 +272,8 @@
|
|
|
271
272
|
"self-cert": "^2.0.0",
|
|
272
273
|
"should": "*",
|
|
273
274
|
"sinon": "^5.1.1",
|
|
274
|
-
"superagent": "^9.0.1"
|
|
275
|
+
"superagent": "^9.0.1",
|
|
276
|
+
"testdouble": "^3.20.2"
|
|
275
277
|
},
|
|
276
278
|
"repository": {
|
|
277
279
|
"type": "git",
|