Skip to content

Commit

Permalink
chore: Migrated @newrelic/aws-sdk into agent repo (#2161)
Browse files Browse the repository at this point in the history
  • Loading branch information
bizob2828 authored Apr 24, 2024
2 parents 5ac870e + f7fcf7e commit a9eb593
Show file tree
Hide file tree
Showing 101 changed files with 14,615 additions and 2,728 deletions.
1,622 changes: 1,225 additions & 397 deletions THIRD_PARTY_NOTICES.md

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions lib/instrumentation/aws-sdk/nr-hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const InstrumentationDescriptor = require('../../instrumentation-descriptor')

const instrumentations = [
{
type: InstrumentationDescriptor.TYPE_CONGLOMERATE,
moduleName: 'aws-sdk',
onRequire: require('./v2/instrumentation')
},
{
type: InstrumentationDescriptor.TYPE_CONGLOMERATE,
moduleName: '@aws-sdk/smithy-client',
onRequire: require('./v3/smithy-client'),
shimName: 'aws-sdk'
},
{
type: InstrumentationDescriptor.TYPE_CONGLOMERATE,
moduleName: '@smithy/smithy-client',
onRequire: require('./v3/smithy-client'),
shimName: 'aws-sdk'
}
]

module.exports = instrumentations
38 changes: 38 additions & 0 deletions lib/instrumentation/aws-sdk/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const UNKNOWN = 'Unknown'
const {
params: { DatastoreParameters }
} = require('../../shim/specs')

function grabLastUrlSegment(url = '/') {
// cast URL as string, and an empty
// string for null, undefined, NaN etc.
url = '' + (url || '/')
const lastSlashIndex = url.lastIndexOf('/')
return url.substr(lastSlashIndex + 1)
}

/**
* Retrieves the db segment params from endpoint and command parameters
*
* @param {Object} endpoint instance of ddb endpoint
* @param {Object} params parameters passed to a ddb command
* @returns {Object}
*/
function setDynamoParameters(endpoint, params) {
return new DatastoreParameters({
host: endpoint && (endpoint.host || endpoint.hostname),
port_path_or_id: (endpoint && endpoint.port) || 443,
collection: (params && params.TableName) || UNKNOWN
})
}

module.exports = {
grabLastUrlSegment,
setDynamoParameters
}
105 changes: 105 additions & 0 deletions lib/instrumentation/aws-sdk/v2/core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const UNKNOWN = 'Unknown'
const symbols = require('../../../symbols')
const InstrumentationDescriptor = require('../../../instrumentation-descriptor')

function validate(shim, AWS) {
if (!shim.isFunction(AWS.NodeHttpClient)) {
shim.logger.debug('Could not find NodeHttpClient, not instrumenting.')
return false
}
if (!shim.isFunction(AWS.Service) || !shim.isFunction(AWS.Service.prototype.makeRequest)) {
shim.logger.debug('Could not find AWS.Service#makeRequest, not instrumenting.')
return false
}
return true
}

function instrument(shim, AWS) {
shim.wrap(AWS.NodeHttpClient.prototype, 'handleRequest', wrapHandleRequest)
shim.wrapReturn(AWS.Service.prototype, 'makeRequest', wrapMakeRequest)
}

function wrapHandleRequest(shim, handleRequest) {
return function wrappedHandleRequest(httpRequest) {
if (httpRequest) {
if (!httpRequest.headers) {
httpRequest.headers = Object.create(null)
}
httpRequest.headers[symbols.disableDT] = true
} else {
shim.logger.debug('Unknown arguments to AWS.NodeHttpClient#handleRequest!')
}

return handleRequest.apply(this, arguments)
}
}

function wrapMakeRequest(shim, fn, name, request) {
if (!request) {
shim.logger.trace('No request object returned from Service#makeRequest')
return
}

const service = getServiceName(this)
const region = this?.config?.region
request.on('complete', function onAwsRequestComplete() {
const httpRequest = request.httpRequest && request.httpRequest.stream
const segment = shim.getSegment(httpRequest)
if (!httpRequest || !segment) {
shim.logger.trace('No segment found for request, not extracting information.')
return
}

const requestRegion = request?.httpRequest?.region
const requestId = request?.response?.requestId

segment.addAttribute('aws.operation', request.operation || UNKNOWN)
segment.addAttribute('aws.requestId', requestId || UNKNOWN)
segment.addAttribute('aws.service', service || UNKNOWN)
segment.addAttribute('aws.region', requestRegion || region || UNKNOWN)
})

shim.wrap(request, 'promise', function wrapPromiseFunc(shim, original) {
const activeSegment = shim.getActiveSegment()

return function wrappedPromiseFunc() {
if (!activeSegment) {
return original.apply(this, arguments)
}

const promise = shim.applySegment(original, activeSegment, false, this, arguments)

return shim.bindPromise(promise, activeSegment)
}
})
}

function getServiceName(service) {
if (service.api && (service.api.abbreviation || service.api.serviceId)) {
return service.api.abbreviation || service.api.serviceId
}

// In theory, getting the `constructor.prototype` should be redundant with
// checking `service`. However, the aws-sdk dynamically generates classes and
// doing this deep check was the recommended method by the maintainers.
const constructor = service.constructor
const api = constructor && constructor.prototype && constructor.prototype.api
if (api) {
return api.abbreviation || api.serviceId
}
return null
}

module.exports = {
name: 'core',
type: InstrumentationDescriptor.TYPE_GENERIC,
validate,
instrument
}
103 changes: 103 additions & 0 deletions lib/instrumentation/aws-sdk/v2/dynamodb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const InstrumentationDescriptor = require('../../../instrumentation-descriptor')

const DDB_OPERATIONS = [
'putItem',
'getItem',
'updateItem',
'deleteItem',
'createTable',
'deleteTable',
'query',
'scan'
]

const DOC_CLIENT_OPERATIONS = [
'get',
'put',
'update',
'delete',
'batchGet',
'batchWrite',
'transactGet',
'transactWrite',
'query',
'scan'
]
const {
OperationSpec,
params: { DatastoreParameters }
} = require('../../../shim/specs')

const { setDynamoParameters } = require('../util')

function instrument(shim, AWS) {
shim.setDatastore(shim.DYNAMODB)

// DynamoDB's service API methods are dynamically generated
// in the constructor so we have to wrap the return.
shim.wrapReturn(AWS, 'DynamoDB', function wrapDynamo(shim, fn, name, ddb) {
shim.recordOperation(
ddb,
DDB_OPERATIONS,
function wrapMethod(shim, original, operationName, args) {
const params = args[0]

return new OperationSpec({
name: operationName,
parameters: setDynamoParameters(this.endpoint, params),
callback: shim.LAST,
opaque: true
})
}
)
})

// DocumentClient's API is predefined so we can instrument the prototype.
// DocumentClient does defer to DynamoDB but it also does enough individual
// steps for the request we want to hide that instrumenting specifically and
// setting to opaque is currently required.
const docClientProto = AWS.DynamoDB.DocumentClient.prototype
shim.recordOperation(
docClientProto,
DOC_CLIENT_OPERATIONS,
function wrapOperation(shim, original, operationName, args) {
const params = args[0]
const dynamoOperation = this.serviceClientOperationsMap[operationName]

// DocumentClient can be defined with a different service such as AmazonDaxClient.
// In these cases, an endpoint property may not exist. In the DAX case,
// the eventual cached endpoint to be hit is not known at this point.
const endpoint = this.service && this.service.endpoint

return new OperationSpec({
name: dynamoOperation,
parameters: new DatastoreParameters({
host: endpoint?.host,
port_path_or_id: endpoint?.port,
collection: params?.TableName || 'Unknown'
}),
callback: shim.LAST,
opaque: true
})
}
)
}

module.exports = {
name: 'dynamodb',
type: InstrumentationDescriptor.TYPE_DATASTORE,
instrument,
validate: (shim, AWS) => {
if (!shim.isFunction(AWS.DynamoDB)) {
shim.logger.debug('Could not find DynamoDB, not instrumenting.')
return false
}
return true
}
}
31 changes: 31 additions & 0 deletions lib/instrumentation/aws-sdk/v2/instrumentation-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

/**
* Series of tests to determine if the library
* has the features needed to provide instrumentation
* @param AWS
*/
const instrumentationSupported = function instrumentationSupported(AWS) {
// instrumentation requires the serviceClientOperationsMap property
/* eslint-disable-next-line */
if (
!AWS ||
!AWS.DynamoDB ||
!AWS.DynamoDB.DocumentClient ||
!AWS.DynamoDB.DocumentClient.prototype ||
!AWS.DynamoDB.DocumentClient.prototype.serviceClientOperationsMap
) {
return false
}

return true
}

module.exports = {
instrumentationSupported
}
34 changes: 34 additions & 0 deletions lib/instrumentation/aws-sdk/v2/instrumentation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const INSTRUMENTATIONS = [
require('./core'),
require('./dynamodb'),
require('./sqs'),
require('./sns')
]

const helper = require('./instrumentation-helper')

module.exports = function initialize(shim, AWS) {
if (!helper.instrumentationSupported(AWS)) {
return false
}
// Validate every instrumentation before attempting to run any of them.
for (const instrumentation of INSTRUMENTATIONS) {
if (!instrumentation.validate(shim, AWS)) {
return false
}
}

for (const instrumentation of INSTRUMENTATIONS) {
const subshim = shim.makeSpecializedShim(instrumentation.type, instrumentation.name)
instrumentation.instrument(subshim, AWS)
}

return true
}
42 changes: 42 additions & 0 deletions lib/instrumentation/aws-sdk/v2/sns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const { MessageSpec } = require('../../../shim/specs')
const InstrumentationDescriptor = require('../../../instrumentation-descriptor')

module.exports = {
name: 'sns',
type: InstrumentationDescriptor.TYPE_MESSAGE,
validate: (shim, AWS) => {
if (!shim.isFunction(AWS.SNS)) {
shim.logger.debug('Could not find SNS, not instrumenting.')
return false
}
return true
},
instrument
}

function instrument(shim, AWS) {
shim.setLibrary(shim.SNS)

shim.wrapReturn(AWS, 'SNS', function wrapSns(shim, original, name, sns) {
shim.recordProduce(sns, 'publish', wrapPublish)
})
}

function wrapPublish(shim, original, name, args) {
return new MessageSpec({
callback: shim.LAST,
destinationName: getDestinationName(args[0]),
destinationType: shim.TOPIC,
opaque: true
})
}

function getDestinationName({ TopicArn, TargetArn }) {
return TopicArn || TargetArn || 'PhoneNumber' // We don't want the value of PhoneNumber
}
Loading

0 comments on commit a9eb593

Please sign in to comment.