newrelic 13.2.0 → 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.
- package/NEWS.md +44 -0
- package/THIRD_PARTY_NOTICES.md +219 -8
- package/api.js +1 -1
- package/esm-loader.mjs +6 -1
- package/lib/instrumentation/@azure/functions.js +8 -46
- package/lib/instrumentation/core/http-outbound.js +26 -10
- package/lib/instrumentation/core/http.js +20 -118
- package/lib/instrumentations.js +0 -14
- package/lib/otel/logs/index.js +80 -0
- package/lib/otel/metrics/index.js +119 -0
- package/lib/otel/setup-signal.js +37 -0
- package/lib/otel/setup.js +15 -25
- package/lib/otel/{attr-reconciler.js → traces/attr-reconciler.js} +2 -2
- package/lib/otel/{exception-mapping.js → traces/exception-mapping.js} +1 -1
- package/lib/otel/traces/index.js +40 -0
- package/lib/otel/{rules.js → traces/rules.js} +1 -1
- package/lib/otel/{segment-synthesis.js → traces/segment-synthesis.js} +5 -4
- package/lib/otel/{segments → traces/segments}/consumer.js +4 -4
- package/lib/otel/{segments → traces/segments}/database.js +6 -6
- package/lib/otel/{segments → traces/segments}/http-external.js +11 -8
- package/lib/otel/traces/segments/index.js +22 -0
- package/lib/otel/{segments → traces/segments}/internal.js +1 -1
- package/lib/otel/{segments → traces/segments}/producer.js +2 -2
- package/lib/otel/{segments → traces/segments}/server.js +4 -4
- package/lib/otel/{segments → traces/segments}/utils.js +1 -1
- package/lib/otel/{span-processor.js → traces/span-processor.js} +13 -19
- package/lib/otel/{transformation-rules.json → traces/transformation-rules.json} +3 -2
- package/lib/otel/{utils.js → traces/utils.js} +2 -1
- package/lib/serverless/aws-lambda.js +7 -68
- package/lib/shim/webframework-shim/middleware.js +1 -1
- package/lib/shimmer.js +2 -2
- package/lib/subscriber-configs.js +2 -1
- package/lib/subscribers/base.js +30 -0
- package/lib/subscribers/elasticsearch/config.js +1 -0
- package/lib/subscribers/mcp-sdk/client-request.js +66 -0
- package/lib/subscribers/mcp-sdk/config.js +13 -75
- package/lib/subscribers/openai/base.js +21 -0
- package/lib/subscribers/openai/chat-responses.js +15 -0
- package/lib/subscribers/openai/chat.js +85 -0
- package/lib/subscribers/openai/client.js +31 -0
- package/lib/subscribers/openai/config.js +85 -0
- package/lib/subscribers/openai/embeddings.js +54 -0
- package/lib/subscribers/openai/utils.js +349 -0
- package/lib/transaction/index.js +157 -56
- package/lib/transaction/trace/segment.js +5 -2
- package/lib/util/urltils.js +158 -192
- package/lib/utilization/common.js +0 -6
- package/package.json +3 -4
- package/lib/instrumentation/openai.js +0 -482
- package/lib/otel/logs/bootstrap-logs.js +0 -84
- package/lib/otel/metrics/bootstrap-metrics.js +0 -117
- package/lib/otel/segments/index.js +0 -22
- package/lib/patch-module.js +0 -70
- package/lib/subscribers/mcp-sdk/client-prompt.js +0 -23
- package/lib/subscribers/mcp-sdk/client-resource.js +0 -24
- package/lib/subscribers/mcp-sdk/client-tool.js +0 -23
- package/lib/subscribers/mcp-sdk/client.js +0 -29
- /package/lib/otel/{constants.js → traces/constants.js} +0 -0
- /package/lib/otel/{sampler.js → traces/sampler.js} +0 -0
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const parseSql = require('
|
|
9
|
-
const recordQueryMetrics = require('
|
|
10
|
-
const recordOperationMetrics = require('
|
|
11
|
-
const ParsedStatement = require('
|
|
12
|
-
const metrics = require('
|
|
13
|
-
const { transformTemplate } = require('../utils')
|
|
8
|
+
const parseSql = require('#agentlib/db/query-parsers/sql.js')
|
|
9
|
+
const recordQueryMetrics = require('#agentlib/metrics/recorders/database.js')
|
|
10
|
+
const recordOperationMetrics = require('#agentlib/metrics/recorders/database-operation.js')
|
|
11
|
+
const ParsedStatement = require('#agentlib/db/parsed-statement.js')
|
|
12
|
+
const metrics = require('#agentlib/metrics/names.js')
|
|
13
|
+
const { transformTemplate } = require('../utils.js')
|
|
14
14
|
|
|
15
15
|
module.exports = function createDbSegment(agent, otelSpan, rule) {
|
|
16
16
|
const context = agent.tracer.getContext()
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const recordExternal = require('
|
|
9
|
-
const urltils = require('
|
|
10
|
-
const { transformTemplate } = require('../utils')
|
|
11
|
-
const { UNKNOWN } = require('../constants')
|
|
8
|
+
const recordExternal = require('#agentlib/metrics/recorders/http_external.js')
|
|
9
|
+
const urltils = require('#agentlib/util/urltils.js')
|
|
10
|
+
const { transformTemplate } = require('../utils.js')
|
|
11
|
+
const { UNKNOWN } = require('../constants.js')
|
|
12
12
|
|
|
13
13
|
function assignHost(otelSpan, segmentTransformation) {
|
|
14
14
|
let host = UNKNOWN
|
|
@@ -23,18 +23,21 @@ function assignHost(otelSpan, segmentTransformation) {
|
|
|
23
23
|
return host
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
module.exports = function createHttpExternalSegment(agent, otelSpan, rule) {
|
|
26
|
+
module.exports = function createHttpExternalSegment(agent, otelSpan, rule, logger) {
|
|
27
27
|
const context = agent.tracer.getContext()
|
|
28
28
|
const segmentTransformation = rule.segmentTransformation
|
|
29
29
|
|
|
30
30
|
const host = assignHost(otelSpan, segmentTransformation)
|
|
31
31
|
const url = otelSpan?.attributes[segmentTransformation?.url]
|
|
32
32
|
const system = otelSpan?.attributes[segmentTransformation?.system] ?? 'http'
|
|
33
|
-
let parsedUrl
|
|
34
33
|
let obfuscatedPath = `/${UNKNOWN}`
|
|
35
34
|
if (url) {
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
try {
|
|
36
|
+
const parsedUrl = new URL(url)
|
|
37
|
+
obfuscatedPath = urltils.obfuscatePath(agent.config, parsedUrl.pathname)
|
|
38
|
+
} catch (err) {
|
|
39
|
+
logger.debug('Could not parse URL %s: %s', url, err.message)
|
|
40
|
+
}
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
const name = transformTemplate(segmentTransformation?.name?.template, { host, path: obfuscatedPath, ...otelSpan?.attributes })
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const createConsumerSegment = require('./consumer.js')
|
|
9
|
+
const createDbSegment = require('./database.js')
|
|
10
|
+
const createHttpExternalSegment = require('./http-external.js')
|
|
11
|
+
const createProducerSegment = require('./producer.js')
|
|
12
|
+
const createServerSegment = require('./server.js')
|
|
13
|
+
const createInternalSegment = require('./internal.js')
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
createConsumerSegment,
|
|
17
|
+
createDbSegment,
|
|
18
|
+
createHttpExternalSegment,
|
|
19
|
+
createInternalSegment,
|
|
20
|
+
createProducerSegment,
|
|
21
|
+
createServerSegment
|
|
22
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
|
-
const genericRecorder = require('
|
|
7
|
+
const genericRecorder = require('#agentlib/metrics/recorders/generic.js')
|
|
8
8
|
|
|
9
9
|
module.exports = function createInternalSegment(agent, otelSpan, rule) {
|
|
10
10
|
const context = agent.tracer.getContext()
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const genericRecorder = require('
|
|
9
|
-
const { transformTemplate } = require('../utils')
|
|
8
|
+
const genericRecorder = require('#agentlib/metrics/recorders/generic.js')
|
|
9
|
+
const { transformTemplate } = require('../utils.js')
|
|
10
10
|
|
|
11
11
|
module.exports = function createProducerSegment(agent, otelSpan, rule) {
|
|
12
12
|
const context = agent.tracer.getContext()
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const Transaction = require('
|
|
9
|
-
const httpRecorder = require('
|
|
10
|
-
const { propagateTraceContext } = require('./utils')
|
|
11
|
-
const createInternalSegment = require('./internal')
|
|
8
|
+
const Transaction = require('#agentlib/transaction/index.js')
|
|
9
|
+
const httpRecorder = require('#agentlib/metrics/recorders/http.js')
|
|
10
|
+
const { propagateTraceContext } = require('./utils.js')
|
|
11
|
+
const createInternalSegment = require('./internal.js')
|
|
12
12
|
|
|
13
13
|
module.exports = function createServerSegment(agent, otelSpan, rule) {
|
|
14
14
|
const activeTx = agent.tracer.getTransaction()
|
|
@@ -7,19 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
const { hrTimeToMilliseconds } = require('@opentelemetry/core')
|
|
9
9
|
const { SpanKind } = require('@opentelemetry/api')
|
|
10
|
-
const urltils = require('
|
|
10
|
+
const urltils = require('#agentlib/util/urltils.js')
|
|
11
11
|
|
|
12
|
-
const AttributeReconciler = require('./attr-reconciler')
|
|
13
|
-
const SegmentSynthesizer = require('./segment-synthesis')
|
|
14
|
-
const { otelSynthesis } = require('
|
|
15
|
-
const {
|
|
16
|
-
const
|
|
17
|
-
const defaultLogger = require('../logger').child({ component: 'span-processor' })
|
|
12
|
+
const AttributeReconciler = require('./attr-reconciler.js')
|
|
13
|
+
const SegmentSynthesizer = require('./segment-synthesis.js')
|
|
14
|
+
const { otelSynthesis } = require('#agentlib/symbols.js')
|
|
15
|
+
const { assignToTarget, buildRuleMappings, extractAttributeValue, processRegex, transformTemplate } = require('./utils.js')
|
|
16
|
+
const defaultLogger = require('#agentlib/logger.js').child({ component: 'span-processor' })
|
|
18
17
|
|
|
19
18
|
const {
|
|
20
19
|
SPAN_STATUS_CODE
|
|
21
20
|
} = require('./constants')
|
|
22
|
-
const exceptionAttr = require('./exception-mapping')
|
|
21
|
+
const exceptionAttr = require('./exception-mapping.js')
|
|
23
22
|
|
|
24
23
|
module.exports = class NrSpanProcessor {
|
|
25
24
|
#reconciler
|
|
@@ -215,24 +214,19 @@ module.exports = class NrSpanProcessor {
|
|
|
215
214
|
|
|
216
215
|
try {
|
|
217
216
|
const requestUrl = new URL(httpUrl)
|
|
218
|
-
transaction.parsedUrl = requestUrl
|
|
219
217
|
transaction.url = urltils.obfuscatePath(this.agent.config, requestUrl.pathname)
|
|
220
218
|
transaction.applyUserNamingRules(requestUrl.pathname)
|
|
221
|
-
|
|
219
|
+
// If `http.route` was not emitted on server span, name the transaction from the path
|
|
220
|
+
// to avoid transactions being named `*`
|
|
221
|
+
transaction.nameState.appendPathIfEmpty(requestUrl.pathname)
|
|
222
|
+
} catch (err) {
|
|
223
|
+
this.logger.debug('Could not parse URL from span for transaction URL: %s, err: %s', httpUrl, err.message)
|
|
222
224
|
transaction.url = httpUrl
|
|
223
225
|
}
|
|
224
226
|
|
|
225
|
-
transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_COMMON, 'request.uri', transaction.url)
|
|
226
|
-
|
|
227
|
-
// If `http.route` was not emitted on server span, name the transaction from the path
|
|
228
|
-
// to avoid transactions being named `*`
|
|
229
|
-
if (transaction.parsedUrl) {
|
|
230
|
-
transaction.nameState.appendPathIfEmpty(transaction.parsedUrl?.path)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
227
|
// Add the status code to transaction name
|
|
234
228
|
if (transaction.statusCode) {
|
|
235
|
-
transaction.
|
|
229
|
+
transaction.finalizeNameFromWeb(transaction.statusCode)
|
|
236
230
|
}
|
|
237
231
|
}
|
|
238
232
|
}
|
|
@@ -731,8 +731,9 @@
|
|
|
731
731
|
],
|
|
732
732
|
"segment": {
|
|
733
733
|
"host": "server.address",
|
|
734
|
+
"url": "url.full",
|
|
734
735
|
"name": {
|
|
735
|
-
"template": "External/${
|
|
736
|
+
"template": "External/${host}${path}"
|
|
736
737
|
}
|
|
737
738
|
}
|
|
738
739
|
},
|
|
@@ -798,7 +799,7 @@
|
|
|
798
799
|
"host": "net.peer.name",
|
|
799
800
|
"url": "http.url",
|
|
800
801
|
"name": {
|
|
801
|
-
"template": "External/${
|
|
802
|
+
"template": "External/${host}${path}"
|
|
802
803
|
}
|
|
803
804
|
}
|
|
804
805
|
},
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
const { DESTINATIONS } = require('#agentlib/config/attribute-filter.js')
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Transforms a template string by replacing placeholders with values from the data object.
|
|
@@ -6,13 +6,11 @@
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
8
|
const apiGateway = require('./api-gateway')
|
|
9
|
-
const headerAttributes = require('../header-attributes')
|
|
10
9
|
const get = require('../util/get')
|
|
11
10
|
const logger = require('../logger').child({ component: 'aws-lambda' })
|
|
12
11
|
const recordBackground = require('../metrics/recorders/other')
|
|
13
12
|
const recordWeb = require('../metrics/recorders/http')
|
|
14
13
|
const TransactionShim = require('../shim/transaction-shim')
|
|
15
|
-
const urltils = require('../util/urltils')
|
|
16
14
|
const specs = require('../shim/specs')
|
|
17
15
|
|
|
18
16
|
// CONSTANTS
|
|
@@ -255,7 +253,7 @@ class AwsLambda {
|
|
|
255
253
|
|
|
256
254
|
if (isApiGatewayLambdaProxy) {
|
|
257
255
|
const webRequest = new apiGateway.LambdaProxyWebRequest(event)
|
|
258
|
-
setWebRequest(
|
|
256
|
+
setWebRequest(transaction, webRequest)
|
|
259
257
|
resultProcessor = getApiGatewayLambdaProxyResultProcessor(transaction)
|
|
260
258
|
}
|
|
261
259
|
const cbIndex = args.length - 1
|
|
@@ -411,57 +409,21 @@ function getApiGatewayLambdaProxyResultProcessor(transaction) {
|
|
|
411
409
|
return function processApiGatewayLambdaProxyResponse(response) {
|
|
412
410
|
if (apiGateway.isValidLambdaProxyResponse(response)) {
|
|
413
411
|
const webResponse = new apiGateway.LambdaProxyWebResponse(response)
|
|
414
|
-
|
|
412
|
+
transaction.finalizeWeb({ end: false, statusCode: webResponse.statusCode, headers: webResponse.headers })
|
|
415
413
|
} else {
|
|
416
414
|
logger.debug('Did not contain a valid API Gateway Lambda Proxy response.')
|
|
417
415
|
}
|
|
418
416
|
}
|
|
419
417
|
}
|
|
420
418
|
|
|
421
|
-
function setWebRequest(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
transaction.url = urltils.scrub(request.url.path)
|
|
427
|
-
transaction.verb = request.method
|
|
428
|
-
transaction.trace.attributes.addAttribute(
|
|
429
|
-
ATTR_DEST.TRANS_COMMON,
|
|
430
|
-
'request.method',
|
|
431
|
-
request.method
|
|
432
|
-
)
|
|
433
|
-
|
|
434
|
-
segment.addSpanAttribute('request.method', request.method)
|
|
435
|
-
|
|
436
|
-
transaction.port = request.url.port
|
|
419
|
+
function setWebRequest(transaction, request) {
|
|
420
|
+
// make a fake URL to scrub the path from a URL object
|
|
421
|
+
const absoluteUrl = `http://localhost${request.url.path}`
|
|
422
|
+
transaction.initializeWeb({ absoluteUrl, method: request.method, port: request.url.port, transport: request?.transportType?.toUpperCase(), headers: request.headers })
|
|
437
423
|
|
|
438
424
|
// These are only query parameters, from lib/serverless/api-gateway.js
|
|
425
|
+
// Not handled in `transaction.initializeWeb` because the query params are not on the absoluteUrl
|
|
439
426
|
transaction.addRequestParameters(request.url.requestParameters)
|
|
440
|
-
|
|
441
|
-
// URL is sent as an agent attribute with transaction events
|
|
442
|
-
transaction.trace.attributes.addAttribute(
|
|
443
|
-
ATTR_DEST.TRANS_EVENT | ATTR_DEST.ERROR_EVENT,
|
|
444
|
-
'request.uri',
|
|
445
|
-
request.url.path
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
segment.addSpanAttribute('request.uri', request.url.path)
|
|
449
|
-
|
|
450
|
-
headerAttributes.collectRequestHeaders(request.headers, transaction)
|
|
451
|
-
|
|
452
|
-
if (shim.agent.config.distributed_tracing.enabled) {
|
|
453
|
-
const lowercaseHeaders = lowercaseObjectKeys(request.headers)
|
|
454
|
-
|
|
455
|
-
const transportType = request.transportType && request.transportType.toUpperCase()
|
|
456
|
-
transaction.acceptDistributedTraceHeaders(transportType, lowercaseHeaders)
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function lowercaseObjectKeys(original) {
|
|
461
|
-
return Object.keys(original).reduce((destination, key) => {
|
|
462
|
-
destination[key.toLowerCase()] = original[key]
|
|
463
|
-
return destination
|
|
464
|
-
}, {})
|
|
465
427
|
}
|
|
466
428
|
|
|
467
429
|
function endTransaction(transaction, enderIndex) {
|
|
@@ -490,27 +452,4 @@ function endTransaction(transaction, enderIndex) {
|
|
|
490
452
|
}
|
|
491
453
|
}
|
|
492
454
|
|
|
493
|
-
function setWebResponse(transaction, response) {
|
|
494
|
-
transaction.statusCode = response.statusCode
|
|
495
|
-
|
|
496
|
-
const responseCode = String(response.statusCode)
|
|
497
|
-
|
|
498
|
-
if (/^\d+$/.test(responseCode)) {
|
|
499
|
-
transaction.trace.attributes.addAttribute(
|
|
500
|
-
ATTR_DEST.TRANS_COMMON,
|
|
501
|
-
'http.statusCode',
|
|
502
|
-
responseCode
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
// We are adding http.statusCode to base segment as
|
|
506
|
-
// we found in testing async invoked lambdas, the
|
|
507
|
-
// active segment is not available at this point.
|
|
508
|
-
const segment = transaction.baseSegment
|
|
509
|
-
|
|
510
|
-
segment.addSpanAttribute('http.statusCode', responseCode)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
headerAttributes.collectResponseHeaders(response.headers, transaction)
|
|
514
|
-
}
|
|
515
|
-
|
|
516
455
|
module.exports = AwsLambda
|
|
@@ -70,7 +70,7 @@ function copyParams({ spec, shim, fn, fnName, args, req }) {
|
|
|
70
70
|
? null
|
|
71
71
|
: spec.params.call(this, shim, fn, fnName, args, req)
|
|
72
72
|
|
|
73
|
-
// Route parameters are handled here, query parameters are handled in lib/transaction/index.js#
|
|
73
|
+
// Route parameters are handled here, query parameters are handled in lib/transaction/index.js#finalizeNameFromWeb as part of finalization
|
|
74
74
|
return shim.prefixRouteParameters(params)
|
|
75
75
|
}
|
|
76
76
|
|
package/lib/shimmer.js
CHANGED
|
@@ -25,7 +25,7 @@ const symbols = require('./symbols')
|
|
|
25
25
|
const { unsubscribe } = require('./instrumentation/undici')
|
|
26
26
|
const subscriptions = require('./subscriber-configs')
|
|
27
27
|
const createSubscriberConfigs = require('./subscribers/create-config')
|
|
28
|
-
const ModulePatch = require('
|
|
28
|
+
const ModulePatch = require('@apm-js-collab/tracing-hooks')
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Unwrapping is only likely to be used by test code, and is a fairly drastic
|
|
@@ -400,7 +400,7 @@ const shimmer = (module.exports = {
|
|
|
400
400
|
if (subscriber.enabled === false) {
|
|
401
401
|
logger.debug(
|
|
402
402
|
'Skipping subscriber %s because it is disabled in the config',
|
|
403
|
-
subscriber.
|
|
403
|
+
subscriber.packageName
|
|
404
404
|
)
|
|
405
405
|
continue
|
|
406
406
|
}
|
|
@@ -11,7 +11,8 @@ const subscribers = {
|
|
|
11
11
|
...require('./subscribers/elasticsearch/config'),
|
|
12
12
|
...require('./subscribers/ioredis/config'),
|
|
13
13
|
...require('./subscribers/mcp-sdk/config'),
|
|
14
|
-
...require('./subscribers/
|
|
14
|
+
...require('./subscribers/openai/config'),
|
|
15
|
+
...require('./subscribers/pino/config')
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
module.exports = subscribers
|
package/lib/subscribers/base.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
'use strict'
|
|
7
7
|
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
8
8
|
const { tracingChannel } = require('node:diagnostics_channel')
|
|
9
|
+
const semver = require('semver')
|
|
10
|
+
const NAMES = require('../metrics/names')
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* The baseline parameters available to all subscribers.
|
|
@@ -66,6 +68,33 @@ class Subscriber {
|
|
|
66
68
|
)
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Creates the `Supportability/Features/Instrumentation/OnRequire/<packageName>`
|
|
73
|
+
* and `Supportability/Features/Instrumentation/OnRequire/<packageName>/Version/<majorVersion>`
|
|
74
|
+
* metrics to track the usage of an instrumented package.
|
|
75
|
+
* We only want to increment this once per package, so we check the call count
|
|
76
|
+
*
|
|
77
|
+
* @param {string} [version] - The version of the package being instrumented.
|
|
78
|
+
* @returns {void}
|
|
79
|
+
*/
|
|
80
|
+
trackInstrumentationUsage(version) {
|
|
81
|
+
const prefix = NAMES.FEATURES.INSTRUMENTATION.ON_REQUIRE
|
|
82
|
+
const instrumentationMetric = `${prefix}/${this.packageName}`
|
|
83
|
+
const metric = this.agent.metrics.getOrCreateMetric(instrumentationMetric)
|
|
84
|
+
if (metric.callCount === 0) {
|
|
85
|
+
metric.incrementCallCount()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (version) {
|
|
89
|
+
const majorVersion = semver.major(version)
|
|
90
|
+
const versionMetricName = `${instrumentationMetric}/Version/${majorVersion}`
|
|
91
|
+
const versionMetric = this.agent.metrics.getOrCreateMetric(versionMetricName)
|
|
92
|
+
if (versionMetric.callCount === 0) {
|
|
93
|
+
versionMetric.incrementCallCount()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
69
98
|
/**
|
|
70
99
|
* Creates a segment with a name, parent, transaction and optional recorder.
|
|
71
100
|
* If the segment is successfully created, it will be started and added to the context.
|
|
@@ -127,6 +156,7 @@ class Subscriber {
|
|
|
127
156
|
*/
|
|
128
157
|
enable() {
|
|
129
158
|
this.channel.start.bindStore(this.store, (data) => {
|
|
159
|
+
this.trackInstrumentationUsage(data?.moduleVersion)
|
|
130
160
|
const ctx = this.agent.tracer.getContext()
|
|
131
161
|
if (this.requireActiveTx && !ctx?.transaction?.isActive()) {
|
|
132
162
|
this.logger.debug('Not recording event for %s, transaction is not active', this.package)
|
|
@@ -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
|
|
7
|
-
path: './mcp-sdk/client-
|
|
6
|
+
const mcpClientRequestConfig = {
|
|
7
|
+
path: './mcp-sdk/client-request',
|
|
8
8
|
instrumentations: [
|
|
9
9
|
{
|
|
10
|
-
channelName: '
|
|
10
|
+
channelName: 'nr_request',
|
|
11
11
|
module: {
|
|
12
12
|
name: '@modelcontextprotocol/sdk',
|
|
13
13
|
versionRange: '>=1.13.0',
|
|
14
|
-
filePath: 'dist/cjs/
|
|
14
|
+
filePath: 'dist/cjs/shared/protocol.js'
|
|
15
15
|
},
|
|
16
16
|
functionQuery: {
|
|
17
|
-
className: '
|
|
18
|
-
methodName: '
|
|
17
|
+
className: 'Protocol',
|
|
18
|
+
methodName: 'request',
|
|
19
19
|
kind: 'Async'
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
channelName: '
|
|
23
|
+
channelName: 'nr_request',
|
|
24
24
|
module: {
|
|
25
25
|
name: '@modelcontextprotocol/sdk',
|
|
26
26
|
versionRange: '>=1.13.0',
|
|
27
|
-
filePath: 'dist/esm/
|
|
27
|
+
filePath: 'dist/esm/shared/protocol.js'
|
|
28
28
|
},
|
|
29
29
|
functionQuery: {
|
|
30
|
-
className: '
|
|
31
|
-
methodName: '
|
|
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': [
|
|
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
|