Skip to content

Commit

Permalink
feat: implment awk-sdk v2 dynamodb instrumentation (elastic#2128)
Browse files Browse the repository at this point in the history
* feat: Implements dynamodb instrumentation for aws sdk v2

Implements elastic#1953
  • Loading branch information
astorm authored and dgieselaar committed Sep 10, 2021
1 parent f86fdb2 commit 739d761
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 5 deletions.
6 changes: 4 additions & 2 deletions .tav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,10 @@ aws-sdk:
# is no need to test *all* those releases. Instead we statically list every
# N=5 releases to test.
#
# Maintenance note: This should be updated periodically.
versions: '2.858.0 || 2.863.0 || 2.868.0 || 2.873.0 || 2.878.0 || 2.883.0 || 2.888.0 || 2.893.0 || 2.898.0 || 2.903.0 || 2.908.0 || 2.913.0 || 2.918.0 || >2.918 <3'
# Maintenance note: This should be updated periodically, keeping 2.858
# as the earliest version but updating the others.
versions: '2.858.0 || 2.881.0 || 2.886.0 || 2.891.0 || 2.896.0 || 2.901.0 || 2.906.0 || 2.911.0 || 2.916.0 || 2.921.0 || 2.926.0 || 2.931.0 || 2.936.0 || >2.936 <3'
commands:
- node test/instrumentation/modules/aws-sdk/s3.test.js
- node test/instrumentation/modules/aws-sdk/sqs.js
- node test/instrumentation/modules/aws-sdk/dynamodb.js
3 changes: 3 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ Notes:
* Add <<disable-send, `disableSend`>> configuration option. This supports some
use cases using the APM agent **without** an APM server. ({issues}2101[#2101])
* Add instrumentation of all DynamoDB methods when using the
https://www.npmjs.com/package/aws-sdk[JavaScript AWS SDK v2] (`aws-sdk`).
[float]
===== Bug fixes
Expand Down
2 changes: 1 addition & 1 deletion docs/supported-technologies.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The Node.js agent will automatically instrument the following modules to give yo
[options="header"]
|=======================================================================
|Module |Version |Note
|https://www.npmjs.com/package/aws-sdk[aws-sdk] |>1 <3 |Will instrument SQS send/receive/delete messages, all S3 methods
|https://www.npmjs.com/package/aws-sdk[aws-sdk] |>1 <3 |Will instrument SQS send/receive/delete messages, all S3 methods, and all DynamoDB methods
|https://www.npmjs.com/package/cassandra-driver[cassandra-driver] |>=3.0.0 |Will instrument all queries
|https://www.npmjs.com/package/elasticsearch[elasticsearch] |>=8.0.0 |Will instrument all queries
|https://www.npmjs.com/package/@elastic/elasticsearch[@elastic/elasticsearch] |>=7.0.0 <8.0.0 |Will instrument all queries
Expand Down
4 changes: 3 additions & 1 deletion lib/instrumentation/modules/aws-sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ const semver = require('semver')
const shimmer = require('../shimmer')
const { instrumentationS3 } = require('./aws-sdk/s3')
const { instrumentationSqs } = require('./aws-sdk/sqs')
const { instrumentationDynamoDb } = require('./aws-sdk/dynamodb.js')

const instrumentorFromSvcId = {
s3: instrumentationS3,
sqs: instrumentationSqs
sqs: instrumentationSqs,
dynamodb: instrumentationDynamoDb
}

// Called in place of AWS.Request.send and AWS.Request.promise
Expand Down
125 changes: 125 additions & 0 deletions lib/instrumentation/modules/aws-sdk/dynamodb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
'use strict'
const constants = require('../../../constants')
const TYPE = 'db'
const SUBTYPE = 'dynamodb'
const ACTION = 'query'

function getRegionFromRequest (request) {
return request && request.service &&
request.service.config && request.service.config.region
}

function getPortFromRequest (request) {
return request && request.service &&
request.service.endpoint && request.service.endpoint.port
}

function getMethodFromRequest (request) {
const method = request && request.operation
if (method) {
return method[0].toUpperCase() + method.slice(1)
}
}

function getStatementFromRequest (request) {
const method = getMethodFromRequest(request)
if (method === 'Query' && request && request.params && request.params.KeyConditionExpression) {
return request.params.KeyConditionExpression
}
return undefined
}

function getAddressFromRequest (request) {
return request && request.service && request.service.endpoint &&
request.service.endpoint.hostname
}

function getTableFromRequest (request) {
const table = request && request.params && request.params.TableName
if (!table) {
return ''
}
return ` ${table}`
}

// Creates the span name from request information
function getSpanNameFromRequest (request) {
const method = getMethodFromRequest(request)
const table = getTableFromRequest(request)
const name = `DynamoDB ${method}${table}`
return name
}

function shouldIgnoreRequest (request, agent) {
return false
}

// Main entrypoint for SQS instrumentation
//
// Must call (or one of its function calls must call) the
// `orig` function/method
function instrumentationDynamoDb (orig, origArguments, request, AWS, agent, { version, enabled }) {
if (shouldIgnoreRequest(request, agent)) {
return orig.apply(request, origArguments)
}

const type = TYPE
const subtype = SUBTYPE
const action = ACTION

const name = getSpanNameFromRequest(request)
const span = agent.startSpan(name, type, subtype, action)
if (!span) {
return orig.apply(request, origArguments)
}

span.setDbContext({
instance: getRegionFromRequest(request),
statement: getStatementFromRequest(request),
type: SUBTYPE
})
span.setDestinationContext({
address: getAddressFromRequest(request),
port: getPortFromRequest(request),
service: {
name: SUBTYPE,
type: 'db',
resource: SUBTYPE
},
cloud: {
region: getRegionFromRequest(request)
}
})

request.on('complete', function (response) {
if (response && response.error) {
const errOpts = {
skipOutcome: true
}
agent.captureError(response.error, errOpts)
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE)
}

// Workaround a bug in the agent's handling of `span.sync`.
//
// The bug: Currently this span.sync is not set `false` because there is
// an HTTP span created (for this S3 request) in the same async op. That
// HTTP span becomes the "active span" for this async op, and *it* gets
// marked as sync=false in `before()` in async-hooks.js.
span.sync = false
span.end()
})

return orig.apply(request, origArguments)
}

module.exports = {
instrumentationDynamoDb,

// exported for testing
getRegionFromRequest,
getPortFromRequest,
getStatementFromRequest,
getAddressFromRequest,
getMethodFromRequest
}
Loading

0 comments on commit 739d761

Please sign in to comment.