Skip to content
Open
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
10 changes: 9 additions & 1 deletion .oxlintrc.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,15 @@
{
"files": ["**/integrations/tracing-channel/aws-sdk/**/*.ts"],
"rules": {
"typescript/no-explicit-any": "off"
"typescript/no-explicit-any": "off",
"typescript/no-unsafe-member-access": "off"
}
},
{
"files": ["**/integrations/tracing-channel/aws-sdk/services/**/*.ts"],
"rules": {
"max-lines": "off",
"complexity": "off"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ module.exports = [
import: createImport('init', 'experimentalUseDiagnosticsChannelInjection'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '142 KB',
limit: '144 KB',
disablePlugins: ['@size-limit/esbuild'],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@

/** The span origin every aws-sdk channel span carries, mirroring the uniform OTel `auto.otel.aws`. */
export const AWS_SDK_ORIGIN = 'auto.aws.orchestrion.aws_sdk';

/** DynamoDB `db.system` value (an attribute value, not a key, so not covered by conventions). */
export const DB_SYSTEM_VALUE_DYNAMODB = 'dynamodb';
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
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 {
// 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.
private _services: Map<string, ServiceExtension> = new Map();
private _services: Map<string, ServiceExtension> = new Map<string, ServiceExtension>([
['SecretsManager', new SecretsManagerServiceExtension()],
['SFN', new StepFunctionsServiceExtension()],
['DynamoDB', new DynamodbServiceExtension()],
['S3', new S3ServiceExtension()],
['Kinesis', new KinesisServiceExtension()],
]);

public requestPreSpanHook(request: NormalizedRequest): RequestMetadata {
const serviceExtension = this._services.get(request.serviceName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import type { Span } from '@sentry/core';
import { SPAN_KIND } from '@sentry/core';
import {
AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS as ATTR_AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS,
AWS_DYNAMODB_CONSISTENT_READ as ATTR_AWS_DYNAMODB_CONSISTENT_READ,
AWS_DYNAMODB_CONSUMED_CAPACITY as ATTR_AWS_DYNAMODB_CONSUMED_CAPACITY,
AWS_DYNAMODB_COUNT as ATTR_AWS_DYNAMODB_COUNT,
AWS_DYNAMODB_EXCLUSIVE_START_TABLE as ATTR_AWS_DYNAMODB_EXCLUSIVE_START_TABLE,
AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES as ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES,
AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES as ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES,
AWS_DYNAMODB_INDEX_NAME as ATTR_AWS_DYNAMODB_INDEX_NAME,
AWS_DYNAMODB_ITEM_COLLECTION_METRICS as ATTR_AWS_DYNAMODB_ITEM_COLLECTION_METRICS,
AWS_DYNAMODB_LIMIT as ATTR_AWS_DYNAMODB_LIMIT,
AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES as ATTR_AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES,
AWS_DYNAMODB_PROJECTION as ATTR_AWS_DYNAMODB_PROJECTION,
AWS_DYNAMODB_PROVISIONED_READ_CAPACITY as ATTR_AWS_DYNAMODB_PROVISIONED_READ_CAPACITY,
AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY as ATTR_AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY,
AWS_DYNAMODB_SCAN_FORWARD as ATTR_AWS_DYNAMODB_SCAN_FORWARD,
AWS_DYNAMODB_SCANNED_COUNT as ATTR_AWS_DYNAMODB_SCANNED_COUNT,
AWS_DYNAMODB_SEGMENT as ATTR_AWS_DYNAMODB_SEGMENT,
AWS_DYNAMODB_SELECT as ATTR_AWS_DYNAMODB_SELECT,
AWS_DYNAMODB_TABLE_COUNT as ATTR_AWS_DYNAMODB_TABLE_COUNT,
AWS_DYNAMODB_TABLE_NAMES as ATTR_AWS_DYNAMODB_TABLE_NAMES,
AWS_DYNAMODB_TOTAL_SEGMENTS as ATTR_AWS_DYNAMODB_TOTAL_SEGMENTS,
DB_NAME,
DB_OPERATION,
DB_SYSTEM,
} from '@sentry/conventions/attributes';
import { 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;
}
Comment thread
andreiborza marked this conversation as resolved.

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)),
);
}
Comment thread
sentry[bot] marked this conversation as resolved.

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);
Comment thread
andreiborza marked this conversation as resolved.
}
Comment thread
andreiborza marked this conversation as resolved.
}
}
Comment thread
andreiborza marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SPAN_KIND } from '@sentry/core';
import { _AWS_KINESIS_STREAM_NAME as AWS_KINESIS_STREAM_NAME } from '@sentry/conventions/attributes';
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) {
// oxlint-disable-next-line typescript/no-deprecated -- old-semconv aws.kinesis.stream.name, matched to the OTel aws-sdk integration
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 '@sentry/conventions/attributes';
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,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Span } from '@sentry/core';
import { SPAN_KIND } from '@sentry/core';
import { AWS_SECRETSMANAGER_SECRET_ARN as ATTR_AWS_SECRETSMANAGER_SECRET_ARN } from '@sentry/conventions/attributes';
import type { NormalizedRequest, NormalizedResponse } from '../types';
import type { RequestMetadata, ServiceExtension } from './ServiceExtension';

export class SecretsManagerServiceExtension implements ServiceExtension {
public requestPreSpanHook(request: NormalizedRequest): RequestMetadata {
const secretId = request.commandInput?.SecretId;
const spanAttributes: Record<string, unknown> = {};
if (typeof secretId === 'string' && secretId.startsWith('arn:aws:secretsmanager:')) {
spanAttributes[ATTR_AWS_SECRETSMANAGER_SECRET_ARN] = secretId;
}

return {
spanAttributes,
spanKind: SPAN_KIND.CLIENT,
};
}

public responseHook(response: NormalizedResponse, span: Span): void {
const secretArn = response.data?.ARN;
if (secretArn) {
span.setAttribute(ATTR_AWS_SECRETSMANAGER_SECRET_ARN, secretArn);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { SPAN_KIND } from '@sentry/core';
import {
AWS_STEP_FUNCTIONS_ACTIVITY_ARN as ATTR_AWS_STEP_FUNCTIONS_ACTIVITY_ARN,
AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN as ATTR_AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN,
} from '@sentry/conventions/attributes';
import type { NormalizedRequest } from '../types';
import type { RequestMetadata, ServiceExtension } from './ServiceExtension';

export class StepFunctionsServiceExtension implements ServiceExtension {
public requestPreSpanHook(request: NormalizedRequest): RequestMetadata {
const stateMachineArn = request.commandInput?.stateMachineArn;
const activityArn = request.commandInput?.activityArn;
const spanAttributes: Record<string, unknown> = {};

if (stateMachineArn) {
spanAttributes[ATTR_AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN] = stateMachineArn;
}

if (activityArn) {
spanAttributes[ATTR_AWS_STEP_FUNCTIONS_ACTIVITY_ARN] = activityArn;
}

return {
spanAttributes,
spanKind: SPAN_KIND.CLIENT,
};
}
}
Loading