Repository: serverless/dashboard (language: python)

Issue:
SDK Schema: Release v0.7.0
_Depends on #198 to be merged first_


Discussion / hints:

Fix patch:
diff --git a/node/package.json b/node/package.json
index 9006911f9..7649aa1b4 100644
--- a/node/package.json
+++ b/node/package.json
@@ -18,6 +18,7 @@
     "@aws-sdk/client-api-gateway": "^3.171.0",
     "@aws-sdk/client-apigatewayv2": "^3.171.0",
     "@aws-sdk/client-cloudwatch-logs": "^3.171.0",
+    "@aws-sdk/client-dynamodb": "^3.171.0",
     "@aws-sdk/client-iam": "^3.171.0",
     "@aws-sdk/client-lambda": "^3.171.0",
     "@aws-sdk/client-s3": "^3.171.0",
diff --git a/node/packages/aws-lambda-sdk/.ts-types/index.d.ts b/node/packages/aws-lambda-sdk/.ts-types/index.d.ts
index 1007c4941..896314a80 100644
--- a/node/packages/aws-lambda-sdk/.ts-types/index.d.ts
+++ b/node/packages/aws-lambda-sdk/.ts-types/index.d.ts
@@ -1,21 +1,21 @@
 import TraceSpan from './lib/trace-span';
-
-export interface SdkOptions {
-  orgId?: string;
-  disableHttpMonitoring?: boolean;
-  disableRequestMonitoring?: boolean;
-  disableResponseMonitoring?: boolean;
-}
+import AwsSdkV2Instrument from './instrument/aws-sdk-v2';
+import AwsSdkV3ClientInstrument from './instrument/aws-sdk-v3-client';
 
 interface traceSpans {
   awsLambda: TraceSpan;
   awsLambdaInitialization: TraceSpan;
 }
 
+interface instrument {
+  awsSdkV2: AwsSdkV2Instrument;
+  awsSdkV3Client: AwsSdkV3ClientInstrument;
+}
+
 interface Sdk {
   orgId: string;
   traceSpans: traceSpans;
-  initialize(options?: SdkOptions): Sdk;
+  instrument: instrument;
 }
 
 export default Sdk;
diff --git a/node/packages/aws-lambda-sdk/.ts-types/instrument.d.ts b/node/packages/aws-lambda-sdk/.ts-types/instrument.d.ts
index fab409049..d8ea31a08 100644
--- a/node/packages/aws-lambda-sdk/.ts-types/instrument.d.ts
+++ b/node/packages/aws-lambda-sdk/.ts-types/instrument.d.ts
@@ -1,4 +1,10 @@
-import { SdkOptions } from './';
+interface SdkOptions {
+  orgId?: string;
+  disableHttpMonitoring?: boolean;
+  disableRequestMonitoring?: boolean;
+  disableResponseMonitoring?: boolean;
+  disableAwsSdkMonitoring?: boolean;
+}
 
 declare function instrument(handler: Function, options?: SdkOptions): Function;
 export default instrument;
diff --git a/node/packages/aws-lambda-sdk/.ts-types/instrument/aws-sdk-v2.d.ts b/node/packages/aws-lambda-sdk/.ts-types/instrument/aws-sdk-v2.d.ts
new file mode 100644
index 000000000..53eb93c4b
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/.ts-types/instrument/aws-sdk-v2.d.ts
@@ -0,0 +1,6 @@
+interface awsSdkV2Instrument {
+  install(AWS: Object): Function;
+  uninstall(AWS: Object): undefined;
+}
+
+export default awsSdkV2Instrument;
diff --git a/node/packages/aws-lambda-sdk/.ts-types/instrument/aws-sdk-v3-client.d.ts b/node/packages/aws-lambda-sdk/.ts-types/instrument/aws-sdk-v3-client.d.ts
new file mode 100644
index 000000000..897582af6
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/.ts-types/instrument/aws-sdk-v3-client.d.ts
@@ -0,0 +1,6 @@
+interface awsSdkV3ClientInstrument {
+  install(Client: Object): Function;
+  uninstall(Client: Object): undefined;
+}
+
+export default awsSdkV3ClientInstrument;
diff --git a/node/packages/aws-lambda-sdk/README.md b/node/packages/aws-lambda-sdk/README.md
index eeffc5cb4..b856d87b3 100644
--- a/node/packages/aws-lambda-sdk/README.md
+++ b/node/packages/aws-lambda-sdk/README.md
@@ -21,6 +21,7 @@ Resolve Layer ARN with following steps
 2. Configure following environment variables for the function environment:
    - `SLS_ORG_ID`: _(id of your organization in Serverless Console)_
    - `AWS_LAMBDA_EXEC_WRAPPER`: `/opt/sls-sdk-node/exec-wrapper.sh`
+3. If needed Serverless SDK can be accessed at `serverlessSdk` global variable
 
 ##### (B) Instrument function manually
 
@@ -54,6 +55,8 @@ export const handler = instrument(
 );
 ```
 
+3. If needed Serveless SDK can be loaded by requiring (or importing) `@serverless/aws-lambda-sdk`
+
 #### Configuration options.
 
 Extension can be configured either via environment variables, or in case of manual instrumentation by passing the options object to `instrument` function;
@@ -76,8 +79,16 @@ Disable lambda requests (events) monitoring
 
 Disable lambda responses monitoring
 
+##### `SLS_DISABLE_AWS_SDK_MONITORING` (or `options.disableAwsSdkMonitoring`)
+
+Disable automated AWS SDK monitoring
+
 ### Outcome
 
-SDK automatically creates the trace that covers internal process of function invocation and initialization. For all the details check [docs/sdk-trace.md](docs/sdk-trace.md)
+SDK automatically creates the trace that covers internal process of function invocation and initialization.
+
+For details check:
 
-If not disabled request and response payloads are written to logs encoded with protobuf format
+- [AWS Lambda SDK main trace spans](docs/sdk-trace.md).
+- [AWS Lambda SDK internal flow traces](docs/monitoring.md)
+- [Serverless SDK interface](docs/sdk.md)
diff --git a/node/packages/aws-lambda-sdk/docs/monitoring.md b/node/packages/aws-lambda-sdk/docs/monitoring.md
new file mode 100644
index 000000000..b8b768c8b
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/docs/monitoring.md
@@ -0,0 +1,100 @@
+# AWS Lambda SDK internal flow monitoring
+
+## Request and response data
+
+_DIsable with `SLS_DISABLE_REQUEST_MONITORING` and `SLS_DISABLE_RESPONSE_MONITORING` environment variables respectively_
+
+SDK reads and writes to logs request (lambda event) and response data. This data is written with a log line looking as:
+
+```
+SERVERLESS_TELEMETRY.R.<base64 encoded payload>
+```
+
+## HTTP(S) requests
+
+_Disable with `SLS_DISABLE_HTTP_MONITORING` environment variable_
+
+All HTTP and HTTPS requests are monitored and stored as `node.http.request` & `node.https.request` trace spans
+
+#### Trace span tags:
+
+| Name                  | Value                                           |
+| --------------------- | ----------------------------------------------- |
+| `http[s].method`      | Request method (e.g. `GET`)                     |
+| `http[s].protocol`    | Currently `HTTP/1.1` in all cases               |
+| `http[s].host`        | Domain name and port name if custom             |
+| `http[s].path`        | Request pathname (query string is not included) |
+| `http[s].query`       | Query string (if provided)                      |
+| `http[s].status_code` | Response status code                            |
+| `http[s].error_code`  | If request errored, its error code              |
+
+## AWS SDK requests
+
+_Disable with `SLS_DISABLE_AWS_SDK_MONITORING` environment variable_
+
+AWS SDK requests that go to SNS, SQS and DynamoDb services are traced.
+
+Tracing is turned on automatically for AWS SDK clients that are normally loaded via CJS require.
+
+However if AWS SDK is bundled or imported via ESM import, then instrumentation needs to be turned on manually with following steps:
+
+```javascript
+import AWS from 'aws-sdk';
+
+// Instrument AWS SDK v2:
+serverlessSdk.instrument.awsSdkV2(AWS);
+
+import { Lambda } from '@aws/client-lambda';
+const lambda = new Lambda({ region: process.env.AWS_REGION });
+
+// Instrument AWS SDK v3 Client
+serverlessSdk.instrument.awsSdkV3Client(lambda);
+```
+
+Covered AWS SDK requests are reflected in `aws.sdk.<service-name>` spans
+
+#### Base span tags
+
+Tags that apply to all AWS SDK requests:
+
+| Name                        | Value                                                                                  |
+| --------------------------- | -------------------------------------------------------------------------------------- |
+| `aws.sdk.region`            | Region to which request is made                                                        |
+| `aws.sdk.signature_version` | Signature version of request authentication (for latest versions of SDK it'll be "v4") |
+| `aws.sdk.service`           | Service to which request is made                                                       |
+| `aws.sdk.operation`         | Operation name (e.g. `listtopics`)                                                     |
+| `aws.sdk.request_id`        | AWS reqeust id                                                                         |
+| `aws.sdk.error`             | If request ends with error, the error message                                          |
+
+#### `aws.sdk.sns` span tags`
+
+| Name                      | Value                    |
+| ------------------------- | ------------------------ |
+| `aws.sdk.sns.topic_name`  | Topic name               |
+| `aws.sdk.sns.message_ids` | Ids of affected messages |
+
+#### `aws.sdk.sqs` span tags`
+
+| Name                      | Value                    |
+| ------------------------- | ------------------------ |
+| `aws.sdk.sqs.queue_name`  | Queue name               |
+| `aws.sdk.sqs.message_ids` | Ids of affected messages |
+
+#### `aws.sdk.dynamodb` span tags`
+
+| Name                                 | Value                                                       |
+| ------------------------------------ | ----------------------------------------------------------- |
+| `aws.sdk.dynamodb.table_name`        | Affected table name                                         |
+| `aws.sdk.dynamodb.consistent_read`   | The value of the `ConsistentRead` request parameter         |
+| `aws.sdk.dynamodb.limit`             | The value of the `Limit` request parameter                  |
+| `aws.sdk.dynamodb.attributes_to_get` | The value of the `AttributesToGet` request parameter        |
+| `aws.sdk.dynamodb.projection`        | The value of the `ProjectionExpression` request parameter   |
+| `aws.sdk.dynamodb.index_name`        | The value of the `IndexName` request parameter              |
+| `aws.sdk.dynamodb.scan_forward`      | The value of the `ScanIndexForward` request parameter       |
+| `aws.sdk.dynamodb.select`            | The value of the `Select` request parameter                 |
+| `aws.sdk.dynamodb.filter`            | The value of the `FilterExpression` request parameter       |
+| `aws.sdk.dynamodb.key_condition`     | The value of the `KeyConditionExpression` request parameter |
+| `aws.sdk.dynamodb.segment`           | The value of the `Segment` request parameter                |
+| `aws.sdk.dynamodb.total_segments`    | The value of the `TotalSegments` request parameter          |
+| `aws.sdk.dynamodb.count`             | The value of the `Count` response parameter                 |
+| `aws.sdk.dynamodb.scanned_count`     | The value of the `ScannedCount` response parameter          |
diff --git a/node/packages/aws-lambda-sdk/docs/sdk-trace.md b/node/packages/aws-lambda-sdk/docs/sdk-trace.md
index 2bce5f932..ff8e01f6b 100644
--- a/node/packages/aws-lambda-sdk/docs/sdk-trace.md
+++ b/node/packages/aws-lambda-sdk/docs/sdk-trace.md
@@ -139,20 +139,4 @@ _None_
 
 ---
 
-### `node.http.request` & `node.https.request`
-
-_If not disabled by [configuration settings](../README.md#configuration-options)_
-
-Covers HTTP and HTTPS requests as made in context of function logic
-
-#### Tags
-
-| Name                  | Value                                           |
-| --------------------- | ----------------------------------------------- |
-| `http[s].method`      | Request method (e.g. `GET`)                     |
-| `http[s].protocol`    | Currently `HTTP/1.1` in all cases               |
-| `http[s].host`        | Domain name and port name if custom             |
-| `http[s].path`        | Request pathname (query string is not included) |
-| `http[s].query`       | Query string (if provided)                      |
-| `http[s].status_code` | Response status code                            |
-| `http[s].error_code`  | If request errored, its error code              |
+For documentation on trace spans that cover internal lambda flow check [monitoring](./monitoring.md) documentation.
diff --git a/node/packages/aws-lambda-sdk/docs/sdk.md b/node/packages/aws-lambda-sdk/docs/sdk.md
new file mode 100644
index 000000000..a71b38f1b
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/docs/sdk.md
@@ -0,0 +1,22 @@
+# Serverless SDK
+
+If function is instrumented via internal extension, then Serverless SDK is accessible at `serverlessSdk` global variable.
+
+In other cases it can be required (or imported) from `@serverless/aws-lambda-sdk` (note: do not load SDK that way if it's already accessible globally)
+
+## Properties and methods of Serverless SDK
+
+### `serverlessSdk.traceSpans`
+
+_For detailed info on spans check [trace-spans.md](./trace-spans.md)_
+
+- `awsLambda` - Root AWS lambda span
+- `awsLambdaInitialization` - Initialization span
+- `awsLambdaInvocation` - Invocation span (not available at _initialization_ phase)
+
+### `serverlessSdk.instrument`
+
+Most of the instrumentation is setup automatically, still there are scenarios when it's difficult to ensure that (e.g. when target modules are imported as ESM, or come from bundles). In such case instrumentation need to be set manually, and then following utilities should be used:
+
+- `awsSdkV2(AWS)` - Instrument AWS SDK v2 (takes instance of SDK as the argument)
+- `awsSdkV3Client(client)` - Instrument AWS SDK v3 client
diff --git a/node/packages/aws-lambda-sdk/index.js b/node/packages/aws-lambda-sdk/index.js
index 72ce1023e..4f9d4086d 100644
--- a/node/packages/aws-lambda-sdk/index.js
+++ b/node/packages/aws-lambda-sdk/index.js
@@ -26,10 +26,13 @@ const resolveSettings = (options = {}) => {
   serverlessSdk._settings.disableResponseMonitoring = Boolean(
     process.env.SLS_DISABLE_RESPONSE_MONITORING || options.disableResponseMonitoring
   );
+  serverlessSdk._settings.disableAwsSdkMonitoring = Boolean(
+    process.env.SLS_DISABLE_AWS_SDK_MONITORING || options.disableAwsSdkMonitoring
+  );
 };
 
 let isInitialized = false;
-serverlessSdk.initialize = (options = {}) => {
+serverlessSdk._initialize = (options = {}) => {
   if (isInitialized) return module.exports;
   isInitialized = true;
   resolveSettings(options);
@@ -39,5 +42,15 @@ serverlessSdk.initialize = (options = {}) => {
     require('./lib/instrument/http').install();
   }
 
+  if (!settings.disableAwsSdkMonitoring) {
+    // Auto generate AWS SDK request spans
+    require('./lib/instrument/aws-sdk').install();
+  }
+
   return serverlessSdk;
 };
+
+serverlessSdk.instrument = {
+  awsSdkV2: require('./instrument/aws-sdk-v2'),
+  awsSdkV3Client: require('./instrument/aws-sdk-v3-client'),
+};
diff --git a/node/packages/aws-lambda-sdk/instrument.js b/node/packages/aws-lambda-sdk/instrument.js
index 32c897dc4..2c0abd4c5 100644
--- a/node/packages/aws-lambda-sdk/instrument.js
+++ b/node/packages/aws-lambda-sdk/instrument.js
@@ -89,7 +89,7 @@ const writeTrace = () => {
 
 module.exports = (originalHandler, options = {}) => {
   ensurePlainFunction(originalHandler, { name: 'originalHandler' });
-  serverlessSdk.initialize(options);
+  serverlessSdk._initialize(options);
   if (!serverlessSdk.orgId) {
     throw new Error(
       'Serverless SDK Error: Cannot instrument function: "orgId" not provided. ' +
diff --git a/node/packages/aws-lambda-sdk/instrument/aws-sdk-v2.js b/node/packages/aws-lambda-sdk/instrument/aws-sdk-v2.js
new file mode 100644
index 000000000..03f3a5750
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/instrument/aws-sdk-v2.js
@@ -0,0 +1,74 @@
+'use strict';
+
+const ensureObject = require('type/object/ensure');
+const ensureConstructor = require('type/constructor/ensure');
+
+const doNotInstrumentFollowingHttpRequest =
+  require('../lib/instrument/http').ignoreFollowingRequest;
+const serviceMapper = require('../lib/instrument/aws-sdk/service-mapper');
+
+const instrumentedSdks = new WeakMap();
+
+module.exports.install = (Sdk) => {
+  if (instrumentedSdks.has(Sdk)) return instrumentedSdks.get(Sdk);
+  ensureObject(Sdk, { errorMessage: '%v is not an instance of AWS SDK' });
+  ensureConstructor(Sdk.Request, { errorMessage: 'Passed argument is not an instance of AWS SDK' });
+  if (typeof Sdk.VERSION !== 'string') {
+    throw new TypeError('Passed argument is not an instance of AWS SDK');
+  }
+  if (!Sdk.VERSION.startsWith('2.')) {
+    throw new TypeError(`Unsupported AWS SDK version: ${Sdk.VERSION}`);
+  }
+  const originalRunTo = Sdk.Request.prototype.runTo;
+  Sdk.Request.prototype.runTo = function runTo(state, done) {
+    // identifier
+    const serviceName =
+      this.service.constructor.serviceIdentifier ||
+      this.service.constructor.__super__.serviceIdentifier;
+    const tagMapper = serviceMapper.get(serviceName);
+    if (tagMapper) {
+      const operationName = this.operation.toLowerCase();
+      const params = this.params;
+      const traceSpan = (
+        traceSpans.awsLambdaInvocation || traceSpans.awsLambdaInitialization
+      ).createSubSpan(`aws.sdk.${serviceName}.${operationName}`, {
+        tags: {
+          'aws.sdk.region': this.service.config.region,
+          'aws.sdk.signature_version': this.service.config.signatureVersion,
+          'aws.sdk.service': serviceName,
+          'aws.sdk.operation': operationName,
+        },
+        onCloseByParent: () => {
+          process.stderr.write(
+            "Serverless SDK Warning: AWS SDK request didn't end before end of " +
+              'lambda invocation (or initialization)\n'
+          );
+        },
+      });
+      tagMapper.params?.(traceSpan, params);
+      this.on('complete', (response) => {
+        if (response.requestId) traceSpan.tags.set('aws.sdk.request_id', response.requestId);
+        if (response.error) traceSpan.tags.set('aws.sdk.error', response.error.message);
+        else tagMapper.responseData?.(traceSpan, response.data);
+        if (!traceSpan.endTime) traceSpan.close();
+      });
+      doNotInstrumentFollowingHttpRequest();
+    }
+    return originalRunTo.call(this, state, done);
+  };
+
+  const uninstall = () => {
+    if (!instrumentedSdks.has(Sdk)) return;
+    Sdk.Request.prototype.runTo = originalRunTo;
+    instrumentedSdks.delete(Sdk);
+  };
+  instrumentedSdks.set(Sdk, uninstall);
+  return uninstall;
+};
+
+module.exports.uninstall = (client) => {
+  const uninstall = instrumentedSdks.get(client);
+  if (uninstall) uninstall();
+};
+
+const { traceSpans } = require('../');
diff --git a/node/packages/aws-lambda-sdk/instrument/aws-sdk-v3-client.js b/node/packages/aws-lambda-sdk/instrument/aws-sdk-v3-client.js
new file mode 100644
index 000000000..77d200739
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/instrument/aws-sdk-v3-client.js
@@ -0,0 +1,99 @@
+'use strict';
+
+const ensureObject = require('type/object/ensure');
+
+const doNotInstrumentFollowingHttpRequest =
+  require('../lib/instrument/http').ignoreFollowingRequest;
+
+const instrumentedClients = new WeakMap();
+
+const serviceMapper = require('../lib/instrument/aws-sdk/service-mapper');
+
+module.exports.install = (client) => {
+  ensureObject(client, { errorMessage: '%v is not an instance of AWS SDK v3 client' });
+  ensureObject(client.middlewareStack, {
+    errorMessage: 'Passed argument is not an instance of AWS SDK v3 client',
+  });
+  if (instrumentedClients.has(client)) return instrumentedClients.get(client);
+  const serviceName = (
+    client.constructor.name.endsWith('Client')
+      ? client.constructor.name.slice(0, -'Client'.length)
+      : client.constructor.name
+  ).toLowerCase();
+  if (!serviceMapper.has(serviceName)) {
+    const uninstall = () => {};
+    instrumentedClients.set(client, uninstall);
+    return uninstall;
+  }
+  let uninstall;
+  client.middlewareStack.use({
+    applyToStack: (stack) => {
+      // Middleware added to mark start and end of an complete API call.
+      const awsRequestMiddleware = (next, context) => async (args) => {
+        const operationName = context.commandName.slice(0, -'Command'.length).toLowerCase();
+        const tagMapper = serviceMapper.get(serviceName);
+        const traceSpan = (
+          traceSpans.awsLambdaInvocation || traceSpans.awsLambdaInitialization
+        ).createSubSpan(`aws.sdk.${serviceName}.${operationName}`, {
+          tags: {
+            'aws.sdk.service': serviceName,
+            'aws.sdk.operation': operationName,
+            'aws.sdk.signature_version': 'v4',
+          },
+          onCloseByParent: () => {
+            process.stderr.write(
+              "Serverless SDK Warning: AWS SDK request didn't end before end of " +
+                'lambda invocation (or initialization)\n'
+            );
+          },
+        });
+        tagMapper.params?.(traceSpan, args.input);
+        const deferredRegion = client.config
+          .region()
+          .then((region) => traceSpan.tags.set('aws.sdk.region', region));
+        const { response, error } = await (async () => {
+          try {
+            return { response: await next(args) };
+          } catch (requestError) {
+            return { error: requestError };
+          }
+        })();
+        await deferredRegion;
+        if (error) {
+          if (error.$metadata) traceSpan.tags.set('aws.sdk.request_id', error.$metadata.requestId);
+          if (!traceSpan.endTime) traceSpan.close();
+          throw error;
+        } else {
+          traceSpan.tags.set('aws.sdk.request_id', response.output.$metadata.requestId);
+          tagMapper.responseData?.(traceSpan, response.output);
+          if (!traceSpan.endTime) traceSpan.close();
+          return response;
+        }
+      };
+      const httpRequestMiddleware = (next) => async (args) => {
+        doNotInstrumentFollowingHttpRequest();
+        return next(args);
+      };
+
+      stack.add(awsRequestMiddleware);
+      stack.add(httpRequestMiddleware, { step: 'deserialize', priority: 'low' });
+
+      uninstall = () => {
+        if (instrumentedClients.has(client)) return;
+        stack.remove(awsRequestMiddleware);
+        stack.remove(httpRequestMiddleware);
+        instrumentedClients.delete(client);
+      };
+    },
+  });
+
+  instrumentedClients.set(client, uninstall);
+  return uninstall;
+};
+
+module.exports.uninstall = (client) => {
+  const uninstall = instrumentedClients.get(client);
+  if (uninstall) uninstall();
+};
+
+const { traceSpans } = require('../');
diff --git a/node/packages/aws-lambda-sdk/internal-extension/index.js b/node/packages/aws-lambda-sdk/internal-extension/index.js
index dc9403bd5..9edad5052 100644
--- a/node/packages/aws-lambda-sdk/internal-extension/index.js
+++ b/node/packages/aws-lambda-sdk/internal-extension/index.js
@@ -69,7 +69,7 @@ const isEsmContext = (dirname) => {
 };
 
 EvalError.$serverlessAwsLambdaInitializationStartTime = processStartTime;
-global.serverlessSdk = require('../').initialize();
+global.serverlessSdk = require('../')._initialize();
 
 let hasInitializationFailed = false;
 const startTime = process.hrtime.bigint();
diff --git a/node/packages/aws-lambda-sdk/lib/instrument/aws-sdk/index.js b/node/packages/aws-lambda-sdk/lib/instrument/aws-sdk/index.js
new file mode 100644
index 000000000..2bc964c83
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/lib/instrument/aws-sdk/index.js
@@ -0,0 +1,36 @@
+'use strict';
+
+const cjsHook = require('../utils/cjs-hook');
+const instrumentV2Sdk = require('../../../instrument/aws-sdk-v2').install;
+const instrumentV3Client = require('../../../instrument/aws-sdk-v3-client').install;
+
+const instrumentedv3SmithyClients = new WeakMap();
+
+module.exports.install = () => {
+  // AWS SDK v2
+  cjsHook.register('/aws-sdk/lib/core.js', instrumentV2Sdk);
+
+  // AWS SDK v3
+  cjsHook.register('/@aws-sdk/smithy-client/dist-cjs/client.js', ({ Client }) => {
+    if (instrumentedv3SmithyClients.has(Client)) return instrumentedv3SmithyClients.get(Client);
+    const originalSend = Client.prototype.send;
+    const uninstallers = new Set();
+    Client.prototype.send = function send(command, optionsOrCb, cb) {
+      uninstallers.add(instrumentV3Client(this));
+      return originalSend.call(this, command, optionsOrCb, cb);
+    };
+    const uninstall = () => {
+      if (!instrumentedv3SmithyClients.has(Client)) return;
+      for (const uninstallClient of uninstallers) uninstallClient();
+      Client.prototype.send = originalSend;
+      instrumentedv3SmithyClients.delete(Client);
+    };
+    instrumentedv3SmithyClients.set(Client, uninstall);
+    return uninstall;
+  });
+};
+
+module.exports.uninstall = () => {
+  cjsHook.unregister('/aws-sdk/lib/core.js');
+  cjsHook.unregister('/@aws-sdk/smithy-client/dist-cjs/client.js');
+};
diff --git a/node/packages/aws-lambda-sdk/lib/instrument/aws-sdk/service-mapper.js b/node/packages/aws-lambda-sdk/lib/instrument/aws-sdk/service-mapper.js
new file mode 100644
index 000000000..b83a3013d
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/lib/instrument/aws-sdk/service-mapper.js
@@ -0,0 +1,105 @@
+'use strict';
+
+module.exports = new Map([
+  [
+    'sns',
+    {
+      params: (traceSpan, { TopicArn: topicArn }) => {
+        const tags = { message_ids: [] };
+        if (topicArn) tags.topic_name = topicArn.slice(topicArn.lastIndexOf(':') + 1);
+        traceSpan.tags.setMany(tags, { prefix: 'aws.sdk.sns' });
+      },
+      responseData: (
+        traceSpan,
+        { TopicArn: topicArn, MessageId: messageId, Successful: messages }
+      ) => {
+        traceSpan.tags.delete('aws.sdk.sns.message_ids');
+        const tags = {};
+        if (topicArn) tags.topic_name = topicArn.slice(topicArn.lastIndexOf(':') + 1);
+        if (messageId) tags.message_ids = [messageId];
+        else if (messages) tags.message_ids = messages.map(({ MessageId: id }) => id);
+        else tags.message_ids = [];
+        traceSpan.tags.setMany(tags, { prefix: 'aws.sdk.sns' });
+      },
+    },
+  ],
+  [
+    'sqs',
+    {
+      params: (traceSpan, { QueueUrl: queueUrl, QueueName: queueName }) => {
+        const tags = { message_ids: [] };
+        if (queueUrl) tags.queue_name = queueUrl.slice(queueUrl.lastIndexOf('/') + 1);
+        else if (queueName) tags.queue_name = queueName;
+        traceSpan.tags.setMany(tags, { prefix: 'aws.sdk.sqs' });
+      },
+      responseData: (
+        traceSpan,
+        {
+          QueueUrl: queueUrl,
+          MessageId: messageId,
+          Successful: successfulMessages,
+          Messages: messages,
+        }
+      ) => {
+        traceSpan.tags.delete('aws.sdk.sqs.message_ids');
+        const tags = {};
+        if (queueUrl) tags.queue_name = queueUrl.slice(queueUrl.lastIndexOf('/') + 1);
+        if (messageId) {
+          tags.message_ids = [messageId];
+        } else if (successfulMessages || messages) {
+          tags.message_ids = (successfulMessages || messages)
+            .map(({ MessageId: id }) => id)
+            .filter(Boolean);
+        } else {
+          tags.message_ids = [];
+        }
+        traceSpan.tags.setMany(tags, { prefix: 'aws.sdk.sqs' });
+      },
+    },
+  ],
+  [
+    'dynamodb',
+    {
+      params: (
+        traceSpan,
+        {
+          TableName: tableName,
+          GlobalTableName: globalTableName,
+          ConsistentRead: consistentRead,
+          Limit: limit,
+          AttributesToGet: attributesToGet,
+          ProjectionExpression: projectionExpression,
+          IndexName: indexName,
+          ScanIndexForward: scanIndexForward,
+          Select: select,
+          KeyConditionExpression: keyConditionExpression,
+          FilterExpression: filterExpression,
+          Segment: segment,
+          TotalSegments: totalSegments,
+        }
+      ) => {
+        const tags = {};
+        if (tableName) tags.table_name = tableName;
+        else if (globalTableName) tags.table_name = globalTableName;
+        if (consistentRead) tags.consistent_read = consistentRead;
+        if (limit) tags.limit = limit;
+        tags.attributes_to_get = attributesToGet || [];
+        if (projectionExpression) tags.projection = projectionExpression;
+        if (indexName) tags.index_name = indexName;
+        if (scanIndexForward) tags.scan_forward = scanIndexForward;
+        if (select) tags.select = select;
+        if (filterExpression) tags.filter = filterExpression;
+        if (keyConditionExpression) tags.key_condition = keyConditionExpression;
+        if (segment) tags.segment = segment;
+        if (totalSegments) tags.total_segments = totalSegments;
+        traceSpan.tags.setMany(tags, { prefix: 'aws.sdk.dynamodb' });
+      },
+      responseData: (traceSpan, { Count: count, ScannedCount: scannedCount }) => {
+        const tags = {};
+        if (count) tags.count = count;
+        if (scannedCount) tags.scanned_count = scannedCount;
+        traceSpan.tags.setMany(tags, { prefix: 'aws.sdk.dynamodb' });
+      },
+    },
+  ],
+]);
diff --git a/node/packages/aws-lambda-sdk/lib/instrument/http.js b/node/packages/aws-lambda-sdk/lib/instrument/http.js
index 3f433dd0b..9ac3e3def 100644
--- a/node/packages/aws-lambda-sdk/lib/instrument/http.js
+++ b/node/packages/aws-lambda-sdk/lib/instrument/http.js
@@ -3,7 +3,7 @@
 const { searchParamsSymbol } = require('url');
 const { errorMonitor } = require('events');
 
-const { traceSpans } = require('../../');
+let shouldIgnoreFollowingRequest = false;
 
 const urlToHttpOptions = (url) => {
   const options = {
@@ -30,6 +30,10 @@ const install = (protocol, httpModule) => {
 
   const request = function request(url, options, cb) {
     const startTime = process.hrtime.bigint();
+    if (shouldIgnoreFollowingRequest) {
+      shouldIgnoreFollowingRequest = false;
+      return originalRequest.call(this, url, options, cb);
+    }
     const args = [url, options, cb];
 
     let cbIndex = 2;
@@ -130,3 +134,12 @@ module.exports.install = () => {
   uninstallHttp = install('http', require('http'));
   uninstallHttps = install('https', require('https'));
 };
+
+module.exports.ignoreFollowingRequest = () => {
+  shouldIgnoreFollowingRequest = true;
+  process.nextTick(() => {
+    shouldIgnoreFollowingRequest = false;
+  });
+};
+
+const { traceSpans } = require('../../');
diff --git a/node/packages/aws-lambda-sdk/lib/instrument/utils/cjs-hook.js b/node/packages/aws-lambda-sdk/lib/instrument/utils/cjs-hook.js
new file mode 100644
index 000000000..2b02cfe15
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/lib/instrument/utils/cjs-hook.js
@@ -0,0 +1,36 @@
+'use strict';
+
+const Module = require('module');
+
+const installers = new Map();
+const uninstallers = new Map();
+
+Module.prototype.load = ((originalLoad) =>
+  function (filename) {
+    originalLoad.call(this, filename);
+    for (const [relativeFilename, install] of installers.entries()) {
+      if (filename.endsWith(relativeFilename)) {
+        if (!uninstallers.has(relativeFilename)) uninstallers.set(relativeFilename, new Set());
+        uninstallers.get(relativeFilename).add(install(this.exports));
+        return;
+      }
+    }
+  })(Module.prototype.load);
+
+module.exports.register = (relativeFilename, install) => {
+  installers.set(relativeFilename, install);
+  for (const [filename, moduleData] of Object.entries(require.cache)) {
+    if (filename.endsWith(relativeFilename)) {
+      if (!uninstallers.has(relativeFilename)) uninstallers.set(relativeFilename, new Set());
+      uninstallers.get(relativeFilename).add(install(moduleData.exports));
+    }
+  }
+};
+
+module.exports.unregister = (relativeFilename) => {
+  if (uninstallers.has(relativeFilename)) {
+    for (const uninstall of uninstallers.get(relativeFilename)) uninstall();
+    uninstallers.delete(relativeFilename);
+  }
+  installers.delete(relativeFilename);
+};
diff --git a/node/packages/aws-lambda-sdk/lib/trace-span.js b/node/packages/aws-lambda-sdk/lib/trace-span.js
index 6a566de31..a5c8e7adf 100644
--- a/node/packages/aws-lambda-sdk/lib/trace-span.js
+++ b/node/packages/aws-lambda-sdk/lib/trace-span.js
@@ -160,12 +160,26 @@ class StringifiableSet extends Set {
 class TraceSpanTags extends Map {
   set(inputName, value) {
     const name = ensureTagName(inputName);
+    value = ensureTagValue(value, name);
     if (this.has(name)) {
+      const currentValue = this.get(name);
+      if (Array.isArray(value)) {
+        if (Array.isArray(currentValue)) {
+          if (
+            value.length === currentValue.length &&
+            value.every((item, index) => currentValue[index] === item)
+          ) {
+            return this;
+          }
+        }
+      } else if (value === currentValue) {
+        return this;
+      }
       throw Object.assign(new Error(`Cannot set tag: Tag "${inputName}" is already set`), {
         code: 'DUPLICATE_TRACE_SPAN_TAG_NAME',
       });
     }
-    return super.set(name, ensureTagValue(value, name));
+    return super.set(name, value);
   }
   setMany(tags, options = {}) {
     ensurePlainObject(tags, { name: 'tags' });
diff --git a/node/packages/aws-lambda-sdk/package.json b/node/packages/aws-lambda-sdk/package.json
index f23bd1b7b..b33fa090f 100644
--- a/node/packages/aws-lambda-sdk/package.json
+++ b/node/packages/aws-lambda-sdk/package.json
@@ -4,7 +4,7 @@
   "version": "0.3.2",
   "author": "Serverless, Inc.",
   "dependencies": {
-    "@serverless/sdk-schema": "^0.5.0",
+    "@serverless/sdk-schema": "^0.7.0",
     "d": "^1.0.1",
     "ext": "^1.7.0",
     "long": "^5.2.0",


Test patch:
diff --git a/node/packages/aws-lambda-sdk/test/fixtures/lambdas/aws-sdk-v2.js b/node/packages/aws-lambda-sdk/test/fixtures/lambdas/aws-sdk-v2.js
new file mode 100644
index 000000000..b145f5ddd
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/test/fixtures/lambdas/aws-sdk-v2.js
@@ -0,0 +1,70 @@
+'use strict';
+
+// eslint-disable-next-line import/no-unresolved
+const { SQS, SNS, DynamoDB } = require('aws-sdk');
+
+const wait = require('timers-ext/promise/sleep');
+
+const sqs = new SQS();
+const sns = new SNS();
+const dynamoDb = new DynamoDB();
+
+let invocationCount = 0;
+
+module.exports.handler = async () => {
+  try {
+    ++invocationCount;
+
+    // SQS
+    const queueName = `${process.env.AWS_LAMBDA_FUNCTION_NAME}-${invocationCount}.fifo`;
+    const { QueueUrl: queueUrl } = await sqs
+      .createQueue({ QueueName: queueName, Attributes: { FifoQueue: 'true' } })
+      .promise();
+    await sqs
+      .sendMessage({
+        QueueUrl: queueUrl,
+        MessageBody: 'test',
+        MessageGroupId: String(Date.now()),
+        MessageDeduplicationId: String(Date.now()),
+      })
+      .promise();
+    await sqs.deleteQueue({ QueueUrl: queueUrl }).promise();
+
+    // SNS
+    const topicName = `${process.env.AWS_LAMBDA_FUNCTION_NAME}-${invocationCount}`;
+    const { TopicArn: topicArn } = await sns.createTopic({ Name: topicName }).promise();
+    await sns.publish({ TopicArn: topicArn, Message: 'test' }).promise();
+    await sns.deleteTopic({ TopicArn: topicArn }).promise();
+
+    // DynamoDB
+    const tableName = `${process.env.AWS_LAMBDA_FUNCTION_NAME}-${invocationCount}`;
+    await dynamoDb
+      .createTable({
+        TableName: tableName,
+        AttributeDefinitions: [{ AttributeName: 'id', AttributeType: 'S' }],
+        KeySchema: [{ AttributeName: 'id', KeyType: 'HASH' }],
+        BillingMode: 'PAY_PER_REQUEST',
+      })
+      .promise();
+    let status;
+    do {
+      await wait(100);
+      status = (await dynamoDb.describeTable({ TableName: tableName }).promise()).Table.TableStatus;
+    } while (status !== 'ACTIVE');
+    await dynamoDb.putItem({ TableName: tableName, Item: { id: { S: 'test' } } }).promise();
+    await dynamoDb
+      .query({
+        TableName: tableName,
+        KeyConditionExpression: '#id = :id',
+        ExpressionAttributeNames: { '#id': 'id' },
+        ExpressionAttributeValues: { ':id': { S: 'test' } },
+      })
+      .promise();
+    await dynamoDb.deleteTable({ TableName: tableName }).promise();
+
+    return 'ok';
+  } catch (error) {
+    console.log(error);
+    throw error;
+  }
+};
diff --git a/node/packages/aws-lambda-sdk/test/fixtures/lambdas/aws-sdk-v3.js b/node/packages/aws-lambda-sdk/test/fixtures/lambdas/aws-sdk-v3.js
new file mode 100644
index 000000000..46638f0f9
--- /dev/null
+++ b/node/packages/aws-lambda-sdk/test/fixtures/lambdas/aws-sdk-v3.js
@@ -0,0 +1,66 @@
+'use strict';
+
+const { SQS } = require('@aws-sdk/client-sqs');
+const { SNS } = require('@aws-sdk/client-sns');
+const { DynamoDB } = require('@aws-sdk/client-dynamodb');
+
+const wait = require('timers-ext/promise/sleep');
+
+const sqs = new SQS();
+const sns = new SNS();
+const dynamoDb = new DynamoDB();
+
+let invocationCount = 0;
+
+module.exports.handler = async () => {
+  try {
+    ++invocationCount;
+
+    // SQS
+    const queueName = `${process.env.AWS_LAMBDA_FUNCTION_NAME}-${invocationCount}.fifo`;
+    const { QueueUrl: queueUrl } = await sqs.createQueue({
+      QueueName: queueName,
+      Attributes: { FifoQueue: 'true' },
+    });
+    await sqs.sendMessage({
+      QueueUrl: queueUrl,
+      MessageBody: 'test',
+      MessageGroupId: String(Date.now()),
+      MessageDeduplicationId: String(Date.now()),
+    });
+    await sqs.deleteQueue({ QueueUrl: queueUrl });
+
+    // SNS
+    const topicName = `${process.env.AWS_LAMBDA_FUNCTION_NAME}-${invocationCount}`;
+    const { TopicArn: topicArn } = await sns.createTopic({ Name: topicName });
+    await sns.publish({ TopicArn: topicArn, Message: 'test' });
+    await sns.deleteTopic({ TopicArn: topicArn });
+
+    // DynamoDB
+    const tableName = `${process.env.AWS_LAMBDA_FUNCTION_NAME}-${invocationCount}`;
+    await dynamoDb.createTable({
+      TableName: tableName,
+      AttributeDefinitions: [{ AttributeName: 'id', AttributeType: 'S' }],
+      KeySchema: [{ AttributeName: 'id', KeyType: 'HASH' }],
+      BillingMode: 'PAY_PER_REQUEST',
+    });
+    let status;
+    do {
+      await wait(100);
+      status = (await dynamoDb.describeTable({ TableName: tableName })).Table.TableStatus;
+    } while (status !== 'ACTIVE');
+    await dynamoDb.putItem({ TableName: tableName, Item: { id: { S: 'test' } } });
+    await dynamoDb.query({
+      TableName: tableName,
+      KeyConditionExpression: '#id = :id',
+      ExpressionAttributeNames: { '#id': 'id' },
+      ExpressionAttributeValues: { ':id': { S: 'test' } },
+    });
+    await dynamoDb.deleteTable({ TableName: tableName });
+
+    return 'ok';
+  } catch (error) {
+    console.log(error);
+    throw error;
+  }
+};
diff --git a/node/packages/aws-lambda-sdk/test/fixtures/lambdas/package.json b/node/packages/aws-lambda-sdk/test/fixtures/lambdas/package.json
index 0967ef424..8c9c92d40 100644
--- a/node/packages/aws-lambda-sdk/test/fixtures/lambdas/package.json
+++ b/node/packages/aws-lambda-sdk/test/fixtures/lambdas/package.json
@@ -1,1 +1,8 @@
-{}
+{
+  "dependencies": {
+    "@aws-sdk/client-dynamodb": "^3.172.0",
+    "@aws-sdk/client-sns": "^3.171.0",
+    "@aws-sdk/client-sqs": "^3.171.0",
+    "timers-ext": "^0.1.7"
+  }
+}
diff --git a/node/packages/aws-lambda-sdk/test/integration/index.test.js b/node/packages/aws-lambda-sdk/test/integration/index.test.js
index 4848dfdf4..1b12b559d 100644
--- a/node/packages/aws-lambda-sdk/test/integration/index.test.js
+++ b/node/packages/aws-lambda-sdk/test/integration/index.test.js
@@ -94,6 +94,167 @@ describe('integration', function () {
     }
   };
 
+  const testAwsSdk = ({ testConfig, invocationsData }) => {
+    for (const [
+      index,
+      {
+        trace: { spans },
+      },
+    ] of invocationsData.entries()) {
+      spans.shift();
+      if (!index) spans.shift();
+      const [
+        invocationSpan,
+        sqsCreateSpan,
+        sqsSendSpan,
+        sqsDeleteSpan,
+        snsCreateSpan,
+        snsPublishSpan,
+        snsDeleteSpan,
+        dynamodbCreateSpan,
+        dynamodbDescribeSpan,
+        ...dynamodbSpans
+      ] = spans;
+
+      // SNS
+      const queueName = `${testConfig.configuration.FunctionName}-${index + 1}.fifo`;
+      // Create
+      expect(sqsCreateSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(sqsCreateSpan.name).to.equal('aws.sdk.sqs.createqueue');
+      let sdkTags = sqsCreateSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('sqs');
+      expect(sdkTags.operation).to.equal('createqueue');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.sqs.queueName).to.equal(queueName);
+      // Send
+      expect(sqsSendSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(sqsSendSpan.name).to.equal('aws.sdk.sqs.sendmessage');
+      sdkTags = sqsSendSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('sqs');
+      expect(sdkTags.operation).to.equal('sendmessage');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.sqs.queueName).to.equal(queueName);
+      expect(sdkTags.sqs.messageIds.length).to.equal(1);
+      // Delete
+      expect(sqsDeleteSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(sqsDeleteSpan.name).to.equal('aws.sdk.sqs.deletequeue');
+      sdkTags = sqsDeleteSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('sqs');
+      expect(sdkTags.operation).to.equal('deletequeue');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.sqs.queueName).to.equal(queueName);
+
+      // SQS
+      const topicName = `${testConfig.configuration.FunctionName}-${index + 1}`;
+      // Create
+      expect(snsCreateSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(snsCreateSpan.name).to.equal('aws.sdk.sns.createtopic');
+      sdkTags = snsCreateSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('sns');
+      expect(sdkTags.operation).to.equal('createtopic');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.sns.topicName).to.equal(topicName);
+      // Send
+      expect(snsPublishSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(snsPublishSpan.name).to.equal('aws.sdk.sns.publish');
+      sdkTags = snsPublishSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('sns');
+      expect(sdkTags.operation).to.equal('publish');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.sns.topicName).to.equal(topicName);
+      expect(sdkTags.sns.messageIds.length).to.equal(1);
+      // Delete
+      expect(snsDeleteSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(snsDeleteSpan.name).to.equal('aws.sdk.sns.deletetopic');
+      sdkTags = snsDeleteSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('sns');
+      expect(sdkTags.operation).to.equal('deletetopic');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.sns.topicName).to.equal(topicName);
+
+      // Dynamodb
+      const tableName = `${testConfig.configuration.FunctionName}-${index + 1}`;
+      // Create
+      expect(dynamodbCreateSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(dynamodbCreateSpan.name).to.equal('aws.sdk.dynamodb.createtable');
+      sdkTags = dynamodbCreateSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('dynamodb');
+      expect(sdkTags.operation).to.equal('createtable');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.dynamodb.tableName).to.equal(tableName);
+      // Describe
+      expect(dynamodbDescribeSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(dynamodbDescribeSpan.name).to.equal('aws.sdk.dynamodb.describetable');
+      sdkTags = dynamodbDescribeSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('dynamodb');
+      expect(sdkTags.operation).to.equal('describetable');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.dynamodb.tableName).to.equal(tableName);
+      while (dynamodbSpans[0].name === 'aws.sdk.dynamodb.describetable') {
+        dynamodbSpans.shift();
+      }
+      const [dynamodbPutItemSpan, dynamodbQuerySpan, dynamodbDeleteSpan] = dynamodbSpans;
+      // Put item
+      expect(dynamodbPutItemSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(dynamodbPutItemSpan.name).to.equal('aws.sdk.dynamodb.putitem');
+      sdkTags = dynamodbPutItemSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('dynamodb');
+      expect(sdkTags.operation).to.equal('putitem');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.dynamodb.tableName).to.equal(tableName);
+      // Query
+      expect(dynamodbQuerySpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(dynamodbQuerySpan.name).to.equal('aws.sdk.dynamodb.query');
+      sdkTags = dynamodbQuerySpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('dynamodb');
+      expect(sdkTags.operation).to.equal('query');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.dynamodb.tableName).to.equal(tableName);
+      expect(sdkTags.dynamodb.keyCondition).to.equal('#id = :id');
+      // Delete
+      expect(dynamodbDeleteSpan.parentSpanId.toString()).to.equal(invocationSpan.id.toString());
+      expect(dynamodbDeleteSpan.name).to.equal('aws.sdk.dynamodb.deletetable');
+      sdkTags = dynamodbDeleteSpan.tags.aws.sdk;
+      expect(sdkTags.region).to.equal(process.env.AWS_REGION);
+      expect(sdkTags.signatureVersion).to.equal('v4');
+      expect(sdkTags.service).to.equal('dynamodb');
+      expect(sdkTags.operation).to.equal('deletetable');
+      expect(sdkTags).to.have.property('requestId');
+      expect(sdkTags).to.not.have.property('error');
+      expect(sdkTags.dynamodb.tableName).to.equal(tableName);
+    }
+  };
+
   const useCasesConfig = new Map([
     [
       'esm-callback/index',
@@ -708,6 +869,8 @@ describe('integration', function () {
         ]),
       },
     ],
+    ['aws-sdk-v2', { test: testAwsSdk }],
+    ['aws-sdk-v3', { test: testAwsSdk }],
   ]);
 
   const testVariantsConfig = resolveTestVariantsConfig(useCasesConfig);
diff --git a/node/packages/aws-lambda-sdk/test/lib/cleanup.js b/node/packages/aws-lambda-sdk/test/lib/cleanup.js
index 33352bd59..82d6aae6e 100644
--- a/node/packages/aws-lambda-sdk/test/lib/cleanup.js
+++ b/node/packages/aws-lambda-sdk/test/lib/cleanup.js
@@ -7,6 +7,7 @@ const { ApiGatewayV2 } = require('@aws-sdk/client-apigatewayv2');
 const { SQS } = require('@aws-sdk/client-sqs');
 const { SNS } = require('@aws-sdk/client-sns');
 const { STS } = require('@aws-sdk/client-sts');
+const { DynamoDB } = require('@aws-sdk/client-dynamodb');
 const log = require('log').get('test');
 const awsRequest = require('../utils/aws-request');
 const basename = require('./basename');
@@ -143,6 +144,26 @@ const deleteSnsTopics = async () => {
   );
 };
 
+const getAllDynamoDbTables = async (exclusiveStartTableName) => {
+  const result = await awsRequest(DynamoDB, 'listTables', {
+    ExclusiveStartTableName: exclusiveStartTableName,
+  });
+  const tableNames = result.TableNames.filter((tableName) => tableName.startsWith(`${basename}-`));
+
+  if (result.LastEvaluatedTableName) {
+    tableNames.push(...Array.from(await getAllDynamoDbTables(result.LastEvaluatedTableName)));
+  }
+  return new Set(tableNames);
+};
+const deleteDynamoDbTables = async () => {
+  await Promise.all(
+    Array.from(await getAllDynamoDbTables(), async (tableName) => {
+      await awsRequest(DynamoDB, 'deleteTable', { TableName: tableName });
+      log.notice('Deleted DynamoDb table %s', tableName);
+    })
+  );
+};
+
 const listLayerVersions = async (layerName, nextMarker = undefined) => {
   const response = await awsRequest(Lambda, 'listLayerVersions', {
     LayerName: layerName,
@@ -219,6 +240,7 @@ module.exports = async (options = {}) => {
       deleteEventSourceMappings(),
       deleteSqsQueues(),
       deleteSnsTopics(),
+      deleteDynamoDbTables(),
     ]);
   }
   await Promise.all([deleteAllLayers(), deleteRole()]);
diff --git a/node/packages/aws-lambda-sdk/test/lib/create-core-resources.js b/node/packages/aws-lambda-sdk/test/lib/create-core-resources.js
index 1395fcea5..da373c8b0 100644
--- a/node/packages/aws-lambda-sdk/test/lib/create-core-resources.js
+++ b/node/packages/aws-lambda-sdk/test/lib/create-core-resources.js
@@ -88,17 +88,7 @@ const createRole = async (config) => {
         Statement: [
           {
             Effect: 'Allow',
-            Action: ['logs:CreateLogStream', 'logs:CreateLogGroup'],
-            Resource: `arn:*:logs:*:*:log-group:/aws/lambda/${basename}*:*`,
-          },
-          {
-            Effect: 'Allow',
-            Action: ['logs:PutLogEvents'],
-            Resource: `arn:*:logs:*:*:log-group:/aws/lambda/${basename}*:*:*`,
-          },
-          {
-            Effect: 'Allow',
-            Action: ['sqs:*'],
+            Action: '*',
             Resource: '*',
           },
         ],
diff --git a/node/packages/aws-lambda-sdk/test/lib/process-function.js b/node/packages/aws-lambda-sdk/test/lib/process-function.js
index 048fc0caf..a0f1c38ce 100644
--- a/node/packages/aws-lambda-sdk/test/lib/process-function.js
+++ b/node/packages/aws-lambda-sdk/test/lib/process-function.js
@@ -41,6 +41,7 @@ const create = async (testConfig, coreConfig) => {
         SLS_SDK_DEBUG: '1',
       },
     },
+    Timeout: 10,
     ...configuration,
   };
   if (process.env.SERVERLESS_PLATFORM_STAGE === 'dev') {
diff --git a/node/packages/aws-lambda-sdk/test/unit/lib/trace-span.test.js b/node/packages/aws-lambda-sdk/test/unit/lib/trace-span.test.js
index 68d3db923..6b40370e5 100644
--- a/node/packages/aws-lambda-sdk/test/unit/lib/trace-span.test.js
+++ b/node/packages/aws-lambda-sdk/test/unit/lib/trace-span.test.js
@@ -207,12 +207,18 @@ describe('lib/trace-span.test.js', () => {
       ]);
     });
 
-    it('should reject setting tag that alraedy exists', () => {
+    it('should reject setting with different value tag that alraedy exists', () => {
       const childSpan = rootSpan.createSubSpan('child');
       childSpan.tags.set('tag', true);
       expect(() => childSpan.tags.set('tag', 'again'))
         .to.throw(Error)
         .with.property('code', 'DUPLICATE_TRACE_SPAN_TAG_NAME');
     });
+
+    it('should ignore resetting tag with same value', () => {
+      const childSpan = rootSpan.createSubSpan('child');
+      childSpan.tags.set('tag', true);
+      expect(childSpan.tags.set('tag', true)).to.equal(childSpan.tags);
+    });
   });
 });