Skip to content

Commit a9eb593

Browse files
authored
chore: Migrated @newrelic/aws-sdk into agent repo (#2161)
2 parents 5ac870e + f7fcf7e commit a9eb593

File tree

101 files changed

+14615
-2728
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+14615
-2728
lines changed

THIRD_PARTY_NOTICES.md

Lines changed: 1225 additions & 397 deletions
Large diffs are not rendered by default.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2020 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
const InstrumentationDescriptor = require('../../instrumentation-descriptor')
8+
9+
const instrumentations = [
10+
{
11+
type: InstrumentationDescriptor.TYPE_CONGLOMERATE,
12+
moduleName: 'aws-sdk',
13+
onRequire: require('./v2/instrumentation')
14+
},
15+
{
16+
type: InstrumentationDescriptor.TYPE_CONGLOMERATE,
17+
moduleName: '@aws-sdk/smithy-client',
18+
onRequire: require('./v3/smithy-client'),
19+
shimName: 'aws-sdk'
20+
},
21+
{
22+
type: InstrumentationDescriptor.TYPE_CONGLOMERATE,
23+
moduleName: '@smithy/smithy-client',
24+
onRequire: require('./v3/smithy-client'),
25+
shimName: 'aws-sdk'
26+
}
27+
]
28+
29+
module.exports = instrumentations
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2020 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
const UNKNOWN = 'Unknown'
8+
const {
9+
params: { DatastoreParameters }
10+
} = require('../../shim/specs')
11+
12+
function grabLastUrlSegment(url = '/') {
13+
// cast URL as string, and an empty
14+
// string for null, undefined, NaN etc.
15+
url = '' + (url || '/')
16+
const lastSlashIndex = url.lastIndexOf('/')
17+
return url.substr(lastSlashIndex + 1)
18+
}
19+
20+
/**
21+
* Retrieves the db segment params from endpoint and command parameters
22+
*
23+
* @param {Object} endpoint instance of ddb endpoint
24+
* @param {Object} params parameters passed to a ddb command
25+
* @returns {Object}
26+
*/
27+
function setDynamoParameters(endpoint, params) {
28+
return new DatastoreParameters({
29+
host: endpoint && (endpoint.host || endpoint.hostname),
30+
port_path_or_id: (endpoint && endpoint.port) || 443,
31+
collection: (params && params.TableName) || UNKNOWN
32+
})
33+
}
34+
35+
module.exports = {
36+
grabLastUrlSegment,
37+
setDynamoParameters
38+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2020 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
8+
const UNKNOWN = 'Unknown'
9+
const symbols = require('../../../symbols')
10+
const InstrumentationDescriptor = require('../../../instrumentation-descriptor')
11+
12+
function validate(shim, AWS) {
13+
if (!shim.isFunction(AWS.NodeHttpClient)) {
14+
shim.logger.debug('Could not find NodeHttpClient, not instrumenting.')
15+
return false
16+
}
17+
if (!shim.isFunction(AWS.Service) || !shim.isFunction(AWS.Service.prototype.makeRequest)) {
18+
shim.logger.debug('Could not find AWS.Service#makeRequest, not instrumenting.')
19+
return false
20+
}
21+
return true
22+
}
23+
24+
function instrument(shim, AWS) {
25+
shim.wrap(AWS.NodeHttpClient.prototype, 'handleRequest', wrapHandleRequest)
26+
shim.wrapReturn(AWS.Service.prototype, 'makeRequest', wrapMakeRequest)
27+
}
28+
29+
function wrapHandleRequest(shim, handleRequest) {
30+
return function wrappedHandleRequest(httpRequest) {
31+
if (httpRequest) {
32+
if (!httpRequest.headers) {
33+
httpRequest.headers = Object.create(null)
34+
}
35+
httpRequest.headers[symbols.disableDT] = true
36+
} else {
37+
shim.logger.debug('Unknown arguments to AWS.NodeHttpClient#handleRequest!')
38+
}
39+
40+
return handleRequest.apply(this, arguments)
41+
}
42+
}
43+
44+
function wrapMakeRequest(shim, fn, name, request) {
45+
if (!request) {
46+
shim.logger.trace('No request object returned from Service#makeRequest')
47+
return
48+
}
49+
50+
const service = getServiceName(this)
51+
const region = this?.config?.region
52+
request.on('complete', function onAwsRequestComplete() {
53+
const httpRequest = request.httpRequest && request.httpRequest.stream
54+
const segment = shim.getSegment(httpRequest)
55+
if (!httpRequest || !segment) {
56+
shim.logger.trace('No segment found for request, not extracting information.')
57+
return
58+
}
59+
60+
const requestRegion = request?.httpRequest?.region
61+
const requestId = request?.response?.requestId
62+
63+
segment.addAttribute('aws.operation', request.operation || UNKNOWN)
64+
segment.addAttribute('aws.requestId', requestId || UNKNOWN)
65+
segment.addAttribute('aws.service', service || UNKNOWN)
66+
segment.addAttribute('aws.region', requestRegion || region || UNKNOWN)
67+
})
68+
69+
shim.wrap(request, 'promise', function wrapPromiseFunc(shim, original) {
70+
const activeSegment = shim.getActiveSegment()
71+
72+
return function wrappedPromiseFunc() {
73+
if (!activeSegment) {
74+
return original.apply(this, arguments)
75+
}
76+
77+
const promise = shim.applySegment(original, activeSegment, false, this, arguments)
78+
79+
return shim.bindPromise(promise, activeSegment)
80+
}
81+
})
82+
}
83+
84+
function getServiceName(service) {
85+
if (service.api && (service.api.abbreviation || service.api.serviceId)) {
86+
return service.api.abbreviation || service.api.serviceId
87+
}
88+
89+
// In theory, getting the `constructor.prototype` should be redundant with
90+
// checking `service`. However, the aws-sdk dynamically generates classes and
91+
// doing this deep check was the recommended method by the maintainers.
92+
const constructor = service.constructor
93+
const api = constructor && constructor.prototype && constructor.prototype.api
94+
if (api) {
95+
return api.abbreviation || api.serviceId
96+
}
97+
return null
98+
}
99+
100+
module.exports = {
101+
name: 'core',
102+
type: InstrumentationDescriptor.TYPE_GENERIC,
103+
validate,
104+
instrument
105+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2020 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
const InstrumentationDescriptor = require('../../../instrumentation-descriptor')
8+
9+
const DDB_OPERATIONS = [
10+
'putItem',
11+
'getItem',
12+
'updateItem',
13+
'deleteItem',
14+
'createTable',
15+
'deleteTable',
16+
'query',
17+
'scan'
18+
]
19+
20+
const DOC_CLIENT_OPERATIONS = [
21+
'get',
22+
'put',
23+
'update',
24+
'delete',
25+
'batchGet',
26+
'batchWrite',
27+
'transactGet',
28+
'transactWrite',
29+
'query',
30+
'scan'
31+
]
32+
const {
33+
OperationSpec,
34+
params: { DatastoreParameters }
35+
} = require('../../../shim/specs')
36+
37+
const { setDynamoParameters } = require('../util')
38+
39+
function instrument(shim, AWS) {
40+
shim.setDatastore(shim.DYNAMODB)
41+
42+
// DynamoDB's service API methods are dynamically generated
43+
// in the constructor so we have to wrap the return.
44+
shim.wrapReturn(AWS, 'DynamoDB', function wrapDynamo(shim, fn, name, ddb) {
45+
shim.recordOperation(
46+
ddb,
47+
DDB_OPERATIONS,
48+
function wrapMethod(shim, original, operationName, args) {
49+
const params = args[0]
50+
51+
return new OperationSpec({
52+
name: operationName,
53+
parameters: setDynamoParameters(this.endpoint, params),
54+
callback: shim.LAST,
55+
opaque: true
56+
})
57+
}
58+
)
59+
})
60+
61+
// DocumentClient's API is predefined so we can instrument the prototype.
62+
// DocumentClient does defer to DynamoDB but it also does enough individual
63+
// steps for the request we want to hide that instrumenting specifically and
64+
// setting to opaque is currently required.
65+
const docClientProto = AWS.DynamoDB.DocumentClient.prototype
66+
shim.recordOperation(
67+
docClientProto,
68+
DOC_CLIENT_OPERATIONS,
69+
function wrapOperation(shim, original, operationName, args) {
70+
const params = args[0]
71+
const dynamoOperation = this.serviceClientOperationsMap[operationName]
72+
73+
// DocumentClient can be defined with a different service such as AmazonDaxClient.
74+
// In these cases, an endpoint property may not exist. In the DAX case,
75+
// the eventual cached endpoint to be hit is not known at this point.
76+
const endpoint = this.service && this.service.endpoint
77+
78+
return new OperationSpec({
79+
name: dynamoOperation,
80+
parameters: new DatastoreParameters({
81+
host: endpoint?.host,
82+
port_path_or_id: endpoint?.port,
83+
collection: params?.TableName || 'Unknown'
84+
}),
85+
callback: shim.LAST,
86+
opaque: true
87+
})
88+
}
89+
)
90+
}
91+
92+
module.exports = {
93+
name: 'dynamodb',
94+
type: InstrumentationDescriptor.TYPE_DATASTORE,
95+
instrument,
96+
validate: (shim, AWS) => {
97+
if (!shim.isFunction(AWS.DynamoDB)) {
98+
shim.logger.debug('Could not find DynamoDB, not instrumenting.')
99+
return false
100+
}
101+
return true
102+
}
103+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2020 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
8+
/**
9+
* Series of tests to determine if the library
10+
* has the features needed to provide instrumentation
11+
* @param AWS
12+
*/
13+
const instrumentationSupported = function instrumentationSupported(AWS) {
14+
// instrumentation requires the serviceClientOperationsMap property
15+
/* eslint-disable-next-line */
16+
if (
17+
!AWS ||
18+
!AWS.DynamoDB ||
19+
!AWS.DynamoDB.DocumentClient ||
20+
!AWS.DynamoDB.DocumentClient.prototype ||
21+
!AWS.DynamoDB.DocumentClient.prototype.serviceClientOperationsMap
22+
) {
23+
return false
24+
}
25+
26+
return true
27+
}
28+
29+
module.exports = {
30+
instrumentationSupported
31+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2020 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
8+
const INSTRUMENTATIONS = [
9+
require('./core'),
10+
require('./dynamodb'),
11+
require('./sqs'),
12+
require('./sns')
13+
]
14+
15+
const helper = require('./instrumentation-helper')
16+
17+
module.exports = function initialize(shim, AWS) {
18+
if (!helper.instrumentationSupported(AWS)) {
19+
return false
20+
}
21+
// Validate every instrumentation before attempting to run any of them.
22+
for (const instrumentation of INSTRUMENTATIONS) {
23+
if (!instrumentation.validate(shim, AWS)) {
24+
return false
25+
}
26+
}
27+
28+
for (const instrumentation of INSTRUMENTATIONS) {
29+
const subshim = shim.makeSpecializedShim(instrumentation.type, instrumentation.name)
30+
instrumentation.instrument(subshim, AWS)
31+
}
32+
33+
return true
34+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2020 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
const { MessageSpec } = require('../../../shim/specs')
8+
const InstrumentationDescriptor = require('../../../instrumentation-descriptor')
9+
10+
module.exports = {
11+
name: 'sns',
12+
type: InstrumentationDescriptor.TYPE_MESSAGE,
13+
validate: (shim, AWS) => {
14+
if (!shim.isFunction(AWS.SNS)) {
15+
shim.logger.debug('Could not find SNS, not instrumenting.')
16+
return false
17+
}
18+
return true
19+
},
20+
instrument
21+
}
22+
23+
function instrument(shim, AWS) {
24+
shim.setLibrary(shim.SNS)
25+
26+
shim.wrapReturn(AWS, 'SNS', function wrapSns(shim, original, name, sns) {
27+
shim.recordProduce(sns, 'publish', wrapPublish)
28+
})
29+
}
30+
31+
function wrapPublish(shim, original, name, args) {
32+
return new MessageSpec({
33+
callback: shim.LAST,
34+
destinationName: getDestinationName(args[0]),
35+
destinationType: shim.TOPIC,
36+
opaque: true
37+
})
38+
}
39+
40+
function getDestinationName({ TopicArn, TargetArn }) {
41+
return TopicArn || TargetArn || 'PhoneNumber' // We don't want the value of PhoneNumber
42+
}

0 commit comments

Comments
 (0)