-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(server-utils): Port S3, Kinesis, DynamoDB, SecretsManager and StepFunctions aws-sdk extensions #22164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(server-utils): Port S3, Kinesis, DynamoDB, SecretsManager and StepFunctions aws-sdk extensions #22164
Changes from all commits
e3f57f7
ae89490
9591440
773a988
50ce000
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,11 @@ | ||
| import type { Span } from '@sentry/core'; | ||
| import type { NormalizedRequest, NormalizedResponse, RequestMetadata } from '../types'; | ||
| import { DynamodbServiceExtension } from './dynamodb'; | ||
| import { KinesisServiceExtension } from './kinesis'; | ||
| import { S3ServiceExtension } from './s3'; | ||
| import { SecretsManagerServiceExtension } from './secretsmanager'; | ||
| import type { ServiceExtension } from './ServiceExtension'; | ||
| import { StepFunctionsServiceExtension } from './stepfunctions'; | ||
|
|
||
| export class ServicesExtensions implements ServiceExtension { | ||
| private _services: Map<string, ServiceExtension>; | ||
|
|
@@ -9,6 +14,11 @@ export class ServicesExtensions implements ServiceExtension { | |
| // Per-service extensions, keyed by the client's `serviceId` (e.g. `'S3'`). Services without a | ||
| // registered extension still get the base rpc span from the subscriber. | ||
| this._services = new Map(); | ||
| this._services.set('SecretsManager', new SecretsManagerServiceExtension()); | ||
| this._services.set('SFN', new StepFunctionsServiceExtension()); | ||
| this._services.set('DynamoDB', new DynamodbServiceExtension()); | ||
| this._services.set('S3', new S3ServiceExtension()); | ||
| this._services.set('Kinesis', new KinesisServiceExtension()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feature lacks integration testsLow Severity This Triggered by project rule: PR Review Guidelines for Cursor Bot Reviewed by Cursor Bugbot for commit 0e20b44. Configure here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing integration or E2E testsMedium Severity This feat PR registers new AWS service span attribute extensions but the diff adds no unit, integration, or E2E test that exercises the orchestrion aws-sdk channel and asserts attributes such as Triggered by project rule: PR Review Guidelines for Cursor Bot Reviewed by Cursor Bugbot for commit 979ee5c. Configure here.
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| public requestPreSpanHook(request: NormalizedRequest): RequestMetadata { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| import type { Span } from '@sentry/core'; | ||
| import { SPAN_KIND } from '@sentry/core'; | ||
| import { DB_NAME, DB_OPERATION, DB_SYSTEM } from '@sentry/conventions/attributes'; | ||
| import { | ||
| ATTR_AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS, | ||
| ATTR_AWS_DYNAMODB_CONSISTENT_READ, | ||
| ATTR_AWS_DYNAMODB_CONSUMED_CAPACITY, | ||
| ATTR_AWS_DYNAMODB_COUNT, | ||
| ATTR_AWS_DYNAMODB_EXCLUSIVE_START_TABLE, | ||
| ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES, | ||
| ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES, | ||
| ATTR_AWS_DYNAMODB_INDEX_NAME, | ||
| ATTR_AWS_DYNAMODB_ITEM_COLLECTION_METRICS, | ||
| ATTR_AWS_DYNAMODB_LIMIT, | ||
| ATTR_AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES, | ||
| ATTR_AWS_DYNAMODB_PROJECTION, | ||
| ATTR_AWS_DYNAMODB_PROVISIONED_READ_CAPACITY, | ||
| ATTR_AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY, | ||
| ATTR_AWS_DYNAMODB_SCAN_FORWARD, | ||
| ATTR_AWS_DYNAMODB_SCANNED_COUNT, | ||
| ATTR_AWS_DYNAMODB_SEGMENT, | ||
| ATTR_AWS_DYNAMODB_SELECT, | ||
| ATTR_AWS_DYNAMODB_TABLE_COUNT, | ||
| ATTR_AWS_DYNAMODB_TABLE_NAMES, | ||
| ATTR_AWS_DYNAMODB_TOTAL_SEGMENTS, | ||
| DB_SYSTEM_VALUE_DYNAMODB, | ||
| } from '../constants'; | ||
| import type { NormalizedRequest, NormalizedResponse } from '../types'; | ||
| import type { RequestMetadata, ServiceExtension } from './ServiceExtension'; | ||
|
|
||
| function toArray<T>(values: T | T[]): T[] { | ||
| return Array.isArray(values) ? values : [values]; | ||
| } | ||
|
|
||
| export class DynamodbServiceExtension implements ServiceExtension { | ||
| public requestPreSpanHook(normalizedRequest: NormalizedRequest): RequestMetadata { | ||
| const operation = normalizedRequest.commandName; | ||
| const tableName = normalizedRequest.commandInput?.TableName; | ||
|
|
||
| const spanAttributes: Record<string, unknown> = {}; | ||
|
|
||
| /* oxlint-disable typescript/no-deprecated -- old-semconv db.* attributes, matched to the OTel aws-sdk integration */ | ||
| spanAttributes[DB_SYSTEM] = DB_SYSTEM_VALUE_DYNAMODB; | ||
| spanAttributes[DB_NAME] = tableName; | ||
| spanAttributes[DB_OPERATION] = operation; | ||
| /* oxlint-enable typescript/no-deprecated */ | ||
|
|
||
| // `RequestItems` is undefined when no table names are returned; its keys are the table names. | ||
| if (normalizedRequest.commandInput?.TableName) { | ||
| // Necessary for commands with only 1 table name (e.g. CreateTable). Attribute is `TableName`, not keys of `RequestItems`. | ||
| spanAttributes[ATTR_AWS_DYNAMODB_TABLE_NAMES] = [normalizedRequest.commandInput.TableName]; | ||
| } else if (normalizedRequest.commandInput?.RequestItems) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_TABLE_NAMES] = Object.keys(normalizedRequest.commandInput.RequestItems); | ||
| } | ||
|
|
||
| if (operation === 'CreateTable' || operation === 'UpdateTable') { | ||
| // only check for ProvisionedThroughput since ReadCapacityUnits and WriteCapacityUnits are required attributes | ||
| if (normalizedRequest.commandInput?.ProvisionedThroughput) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_PROVISIONED_READ_CAPACITY] = | ||
| normalizedRequest.commandInput.ProvisionedThroughput.ReadCapacityUnits; | ||
| spanAttributes[ATTR_AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY] = | ||
| normalizedRequest.commandInput.ProvisionedThroughput.WriteCapacityUnits; | ||
| } | ||
| } | ||
|
|
||
| if (operation === 'GetItem' || operation === 'Scan' || operation === 'Query') { | ||
| if (normalizedRequest.commandInput?.ConsistentRead) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_CONSISTENT_READ] = normalizedRequest.commandInput.ConsistentRead; | ||
| } | ||
| } | ||
|
|
||
| if (operation === 'Query' || operation === 'Scan') { | ||
| if (normalizedRequest.commandInput?.ProjectionExpression) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_PROJECTION] = normalizedRequest.commandInput.ProjectionExpression; | ||
| } | ||
| } | ||
|
|
||
| if (operation === 'CreateTable') { | ||
| if (normalizedRequest.commandInput?.GlobalSecondaryIndexes) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES] = toArray( | ||
| normalizedRequest.commandInput.GlobalSecondaryIndexes, | ||
| ).map((x: unknown) => JSON.stringify(x)); | ||
| } | ||
|
|
||
| if (normalizedRequest.commandInput?.LocalSecondaryIndexes) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES] = toArray( | ||
| normalizedRequest.commandInput.LocalSecondaryIndexes, | ||
| ).map((x: unknown) => JSON.stringify(x)); | ||
| } | ||
| } | ||
|
|
||
| if (operation === 'ListTables' || operation === 'Query' || operation === 'Scan') { | ||
| if (normalizedRequest.commandInput?.Limit) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_LIMIT] = normalizedRequest.commandInput.Limit; | ||
| } | ||
| } | ||
|
|
||
| if (operation === 'ListTables') { | ||
| if (normalizedRequest.commandInput?.ExclusiveStartTableName) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_EXCLUSIVE_START_TABLE] = | ||
| normalizedRequest.commandInput.ExclusiveStartTableName; | ||
| } | ||
| } | ||
|
|
||
| if (operation === 'Query') { | ||
| if (normalizedRequest.commandInput?.ScanIndexForward) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_SCAN_FORWARD] = normalizedRequest.commandInput.ScanIndexForward; | ||
| } | ||
|
|
||
| if (normalizedRequest.commandInput?.IndexName) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_INDEX_NAME] = normalizedRequest.commandInput.IndexName; | ||
| } | ||
|
|
||
| if (normalizedRequest.commandInput?.Select) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_SELECT] = normalizedRequest.commandInput.Select; | ||
| } | ||
| } | ||
|
|
||
| if (operation === 'Scan') { | ||
| if (normalizedRequest.commandInput?.Segment) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_SEGMENT] = normalizedRequest.commandInput?.Segment; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scan segment zero skippedLow Severity For Reviewed by Cursor Bugbot for commit e88a652. Configure here. |
||
|
|
||
| if (normalizedRequest.commandInput?.TotalSegments) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_TOTAL_SEGMENTS] = normalizedRequest.commandInput?.TotalSegments; | ||
| } | ||
|
|
||
| if (normalizedRequest.commandInput?.IndexName) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_INDEX_NAME] = normalizedRequest.commandInput.IndexName; | ||
| } | ||
|
|
||
| if (normalizedRequest.commandInput?.Select) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_SELECT] = normalizedRequest.commandInput.Select; | ||
| } | ||
| } | ||
|
|
||
| if (operation === 'UpdateTable') { | ||
| if (normalizedRequest.commandInput?.AttributeDefinitions) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS] = toArray( | ||
| normalizedRequest.commandInput.AttributeDefinitions, | ||
| ).map((x: unknown) => JSON.stringify(x)); | ||
| } | ||
|
|
||
| if (normalizedRequest.commandInput?.GlobalSecondaryIndexUpdates) { | ||
| spanAttributes[ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES] = toArray( | ||
| normalizedRequest.commandInput.GlobalSecondaryIndexUpdates, | ||
| ).map((x: unknown) => JSON.stringify(x)); | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| spanAttributes, | ||
| spanKind: SPAN_KIND.CLIENT, | ||
| // Matches what the exporter infers from `db.system` for the OTel DynamoDB spans. | ||
| spanOp: 'db', | ||
| }; | ||
| } | ||
|
|
||
| public responseHook(response: NormalizedResponse, span: Span): void { | ||
| if (response.data?.ConsumedCapacity) { | ||
| span.setAttribute( | ||
| ATTR_AWS_DYNAMODB_CONSUMED_CAPACITY, | ||
| toArray(response.data.ConsumedCapacity).map((x: unknown) => JSON.stringify(x)), | ||
| ); | ||
| } | ||
|
|
||
| if (response.data?.ItemCollectionMetrics) { | ||
| span.setAttribute( | ||
| ATTR_AWS_DYNAMODB_ITEM_COLLECTION_METRICS, | ||
| toArray(response.data.ItemCollectionMetrics).map((x: unknown) => JSON.stringify(x)), | ||
| ); | ||
| } | ||
|
|
||
| if (response.data?.TableNames) { | ||
| span.setAttribute(ATTR_AWS_DYNAMODB_TABLE_COUNT, response.data?.TableNames.length); | ||
| } | ||
|
|
||
| if (response.data?.Count) { | ||
| span.setAttribute(ATTR_AWS_DYNAMODB_COUNT, response.data?.Count); | ||
| } | ||
|
|
||
| if (response.data?.ScannedCount) { | ||
| span.setAttribute(ATTR_AWS_DYNAMODB_SCANNED_COUNT, response.data?.ScannedCount); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Truthy checks skip DynamoDB attributesMedium Severity Several DynamoDB span attributes are only set inside truthy Additional Locations (1)Reviewed by Cursor Bugbot for commit 03cef18. Configure here. |
||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Zero counts omitted from spansLow Severity
Reviewed by Cursor Bugbot for commit e88a652. Configure here. |
||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feature lacks integration testsMedium Severity This feature adds several AWS service span attribute extensions but the diff includes no unit, integration, or E2E coverage. Per SDK testing conventions for Additional Locations (2)Triggered by project rule: PR Review Guidelines for Cursor Bot Reviewed by Cursor Bugbot for commit e88a652. Configure here. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { SPAN_KIND } from '@sentry/core'; | ||
| import { AWS_KINESIS_STREAM_NAME } from '../constants'; | ||
| import type { NormalizedRequest } from '../types'; | ||
| import type { RequestMetadata, ServiceExtension } from './ServiceExtension'; | ||
|
|
||
| export class KinesisServiceExtension implements ServiceExtension { | ||
| public requestPreSpanHook(request: NormalizedRequest): RequestMetadata { | ||
| const streamName = request.commandInput?.StreamName; | ||
| const spanAttributes: Record<string, unknown> = {}; | ||
|
|
||
| if (streamName) { | ||
| spanAttributes[AWS_KINESIS_STREAM_NAME] = streamName; | ||
| } | ||
|
|
||
| return { | ||
| spanAttributes, | ||
| spanKind: SPAN_KIND.CLIENT, | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { SPAN_KIND } from '@sentry/core'; | ||
| import { AWS_S3_BUCKET } from '../constants'; | ||
| import type { NormalizedRequest } from '../types'; | ||
| import type { RequestMetadata, ServiceExtension } from './ServiceExtension'; | ||
|
|
||
| export class S3ServiceExtension implements ServiceExtension { | ||
| public requestPreSpanHook(request: NormalizedRequest): RequestMetadata { | ||
| const bucketName = request.commandInput?.Bucket; | ||
| const spanAttributes: Record<string, unknown> = {}; | ||
|
|
||
| if (bucketName) { | ||
| spanAttributes[AWS_S3_BUCKET] = bucketName; | ||
| } | ||
|
|
||
| return { | ||
| spanAttributes, | ||
| spanKind: SPAN_KIND.CLIENT, | ||
| }; | ||
| } | ||
| } |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feat PR lacks integration tests
Low Severity
This feature adds five AWS service span extensions and registers them in
ServicesExtensions, but the diff includes no integration or E2E test asserting the new span attributes (similar to existing orchestrion channel tests for Postgres).Triggered by project rule: PR Review Guidelines for Cursor Bot
Reviewed by Cursor Bugbot for commit 03cef18. Configure here.