Skip to content

Commit

Permalink
feat: add @aws-sdk/client-dynamodb instrumentation (#3530)
Browse files Browse the repository at this point in the history
  • Loading branch information
david-luna authored Sep 18, 2023
1 parent 24d2d5f commit 3adbadf
Show file tree
Hide file tree
Showing 18 changed files with 1,048 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .ci/scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ elif [[ -n "${TAV_MODULE}" ]]; then
memcached)
DOCKER_COMPOSE_FILE=docker-compose-memcached.yml
;;
aws-sdk|@aws-sdk/client-s3)
aws-sdk|@aws-sdk/client-s3|@aws-sdk/client-dynamodb)
DOCKER_COMPOSE_FILE=docker-compose-localstack.yml
;;
*)
Expand Down
1 change: 1 addition & 0 deletions .ci/tav.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
],
"modules": [
{ "name": "@apollo/server", "minVersion": 14 },
{ "name": "@aws-sdk/client-dynamodb", "minVersion": 14 },
{ "name": "@aws-sdk/client-s3", "minVersion": 14 },
{ "name": "@elastic/elasticsearch", "minVersion": 10 },
{ "name": "@hapi/hapi", "minVersion": 8 },
Expand Down
23 changes: 16 additions & 7 deletions .tav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -517,18 +517,27 @@ aws-sdk:
- node test/instrumentation/modules/aws-sdk/sqs.test.js
- node test/instrumentation/modules/aws-sdk/dynamodb.test.js

# For all AWS-SDK clients want this version range:
# versions: '>=3.15.0 <4'
# However, @awk-sdk/client-* releases *very* frequently (almost every day) and there
# is no need to test *all* those releases. Instead we statically list a subset
# of versions to test.
'@aws-sdk/client-s3':
# We want this version range:
# versions: '>=3.15.0 <4'
# However, @awk-sdk/client-s3 releases *very* frequently (almost every day) and there
# is no need to test *all* those releases. Instead we statically list a subset
# of versions to test.
#
# Maintenance note: This should be updated periodically using:
# node ./dev-utils/tav-versions.js @aws-sdk/client-s3 ">=3.15.0 <4"
#
# Test v3.15.0, every N=47 of 241 releases, and current latest.
versions: '3.15.0 || 3.56.0 || 3.159.0 || 3.236.0 || 3.319.0 || 3.400.0 || 3.412.0 || >3.412.0 <4'
commands:
- node test/instrumentation/modules/@aws-sdk/client-s3.test.js
node: '>=14'

'@aws-sdk/client-dynamodb':
# Maintenance note: This should be updated periodically using:
# node ./dev-utils/tav-versions.js @aws-sdk/client-dynamodb ">=3.15.0 <4"
#
# Test v3.15.0, every N=44 of 225 releases, and current latest.
versions: '3.15.0 || 3.54.0 || 3.141.0 || 3.218.0 || 3.300.0 || 3.373.0 || 3.379.1 || >3.379.1 <4'
versions: '3.15.0 || 3.55.0 || 3.165.0 || 3.234.0 || 3.312.0 || 3.398.0 || 3.410.0 || >3.410.0 <4'
commands:
- node test/instrumentation/modules/@aws-sdk/client-s3.test.js
node: '>=14'
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ See the <<upgrade-to-v4>> guide.
* Update <<opentelemetry-bridge>> support to `@opentelemetry/api` version 1.6.0.
{pull}3622[#3622]
* Add support for `@aws-sdk/client-dynamodb`, one of the AWS SDK v3 clients.
({issues}2958[#2958])
[float]
===== Bug fixes
Expand Down
23 changes: 0 additions & 23 deletions dev-utils/aws-sdk-s3-client-tav-versions.sh

This file was deleted.

1 change: 1 addition & 0 deletions docs/supported-technologies.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ The Node.js agent will automatically instrument the following modules to give yo
|Module |Version |Note
|https://www.npmjs.com/package/aws-sdk[aws-sdk] |>=2.858.0 <3 |Will instrument SQS send/receive/delete messages, all S3 methods, all DynamoDB methods, and the SNS publish method
|https://www.npmjs.com/package/@aws-sdk/client-s3[@aws-sdk/client-s3] |>=3.15.0 <4 |Will instrument all S3 methods
|https://www.npmjs.com/package/@aws-sdk/client-dynamodb[@aws-sdk/client-dynamodb] |>=3.15.0 <4 |Will instrument all DynamoDB methods
|https://www.npmjs.com/package/cassandra-driver[cassandra-driver] |>=3.0.0 <5 |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 <9.0.0 |Will instrument all queries
Expand Down
132 changes: 132 additions & 0 deletions lib/instrumentation/modules/@aws-sdk/client-dynamodb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright Elasticsearch B.V. and other contributors where applicable.
* Licensed under the BSD 2-Clause License; you may not use this file except in
* compliance with the BSD 2-Clause License.
*/

'use strict';

const constants = require('../../../constants');
const NAME = 'DynamoDB';
const TYPE = 'db';
const SUBTYPE = 'dynamodb';
const elasticAPMStash = Symbol('elasticAPMStash');

/**
* Returns middlewares to instrument an S3Client instance
*
* @param {import('@aws-sdk/client-dynamodb').DynamoDBClient} client
* @param {any} agent
* @returns {import('../@smithy/smithy-client').AWSMiddlewareEntry[]}
*/
function dynamoDBMiddlewareFactory(client, agent) {
return [
{
middleware: (next, context) => async (args) => {
// Ensure there is a span from the wrapped `client.send()`.
const span = agent._instrumentation.currSpan();
if (!span || !(span.type === TYPE && span.subtype === SUBTYPE)) {
return await next(args);
}

const input = args.input;
const table = input && input.TableName;
// The given span comes with the operation name and we need to
// add the table if applies
if (table) {
span.name += ' ' + table;
}

let err;
let result;
let response;
let statusCode;
try {
result = await next(args);
response = result && result.response;
statusCode = response && response.statusCode;
} catch (ex) {
// Save the error for use in `finally` below, but re-throw it to
// not impact code flow.
err = ex;

// This code path happens with a GetObject conditional request
// that returns a 304 Not Modified.
statusCode = err && err.$metadata && err.$metadata.httpStatusCode;
throw ex;
} finally {
if (statusCode) {
span._setOutcomeFromHttpStatusCode(statusCode);
} else {
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE);
}
if (err && (!statusCode || statusCode >= 400)) {
agent.captureError(err, { skipOutcome: true });
}

const config = client.config;
const region = await config.region();

// Set the db context
const dbContext = { type: SUBTYPE }; // dynamodb
if (region) {
dbContext.instance = region;
}
if (input && input.KeyConditionExpression) {
dbContext.statement = input.KeyConditionExpression;
}
span.setDbContext(dbContext);

// Set destination context
const destContext = {};
if (context[elasticAPMStash]) {
destContext.address = context[elasticAPMStash].hostname;
destContext.port = context[elasticAPMStash].port;
}
if (region) {
destContext.service = { resource: `dynamodb/${region}` };
destContext.cloud = { region };
}
span._setDestinationContext(destContext);

// TODO: add OTel attributes when spec is merged
// https://github.com/elastic/apm/pull/817

span.end();
}

return result;
},
options: { step: 'initialize', priority: 'high', name: 'elasticAPMSpan' },
},
{
middleware: (next, context) => async (args) => {
const req = args.request;
let port = req.port;

// Resolve port for HTTP(S) protocols
if (port === undefined) {
if (req.protocol === 'https:') {
port = 443;
} else if (req.protocol === 'http:') {
port = 80;
}
}

context[elasticAPMStash] = {
hostname: req.hostname,
port,
};
return next(args);
},
options: { step: 'finalizeRequest', name: 'elasticAPMHTTPInfo' },
},
];
}

module.exports = {
DYNAMODB_NAME: NAME,
DYNAMODB_TYPE: TYPE,
DYNAMODB_SUBTYPE: SUBTYPE,
dynamoDBMiddlewareFactory,
};
12 changes: 12 additions & 0 deletions lib/instrumentation/modules/@smithy/smithy-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const {
S3_SUBTYPE,
s3MiddlewareFactory,
} = require('../@aws-sdk/client-s3');
const {
DYNAMODB_NAME,
DYNAMODB_TYPE,
DYNAMODB_SUBTYPE,
dynamoDBMiddlewareFactory,
} = require('../@aws-sdk/client-dynamodb');

/**
* We do alias them to a local type
Expand Down Expand Up @@ -56,6 +62,12 @@ const clientsConfig = {
SUBTYPE: S3_SUBTYPE,
factory: s3MiddlewareFactory,
},
DynamoDBClient: {
NAME: DYNAMODB_NAME,
TYPE: DYNAMODB_TYPE,
SUBTYPE: DYNAMODB_SUBTYPE,
factory: dynamoDBMiddlewareFactory,
},
};

module.exports = function (mod, agent, { name, version, enabled }) {
Expand Down
Loading

0 comments on commit 3adbadf

Please sign in to comment.