Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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