forked from getsentry/sentry-javascript
/
awsservices.ts
118 lines (107 loc) · 3.51 KB
/
awsservices.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { getCurrentHub } from '@sentry/node';
import { Integration, Span, Transaction } from '@sentry/types';
import { fill } from '@sentry/utils';
// 'aws-sdk/global' import is expected to be type-only so it's erased in the final .js file.
// When TypeScript compiler is upgraded, use `import type` syntax to explicitly assert that we don't want to load a module here.
import * as AWS from 'aws-sdk/global';
type GenericParams = { [key: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any
type MakeRequestCallback<TResult> = (err: AWS.AWSError, data: TResult) => void;
// This interace could be replaced with just type alias once the `strictBindCallApply` mode is enabled.
interface MakeRequestFunction<TParams, TResult> extends CallableFunction {
(operation: string, params?: TParams, callback?: MakeRequestCallback<TResult>): AWS.Request<TResult, AWS.AWSError>;
}
interface AWSService {
serviceIdentifier: string;
}
/** AWS service requests tracking */
export class AWSServices implements Integration {
/**
* @inheritDoc
*/
public static id: string = 'AWSServices';
/**
* @inheritDoc
*/
public name: string = AWSServices.id;
private readonly _optional: boolean;
public constructor(options: { optional?: boolean } = {}) {
this._optional = options.optional || false;
}
/**
* @inheritDoc
*/
public setupOnce(): void {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const awsModule = require('aws-sdk/global') as typeof AWS;
fill(awsModule.Service.prototype, 'makeRequest', wrapMakeRequest);
} catch (e) {
if (!this._optional) {
throw e;
}
}
}
}
/** */
function wrapMakeRequest<TService extends AWSService, TResult>(
orig: MakeRequestFunction<GenericParams, TResult>,
): MakeRequestFunction<GenericParams, TResult> {
return function (this: TService, operation: string, params?: GenericParams, callback?: MakeRequestCallback<TResult>) {
let transaction: Transaction | undefined;
let span: Span | undefined;
const scope = getCurrentHub().getScope();
if (scope) {
transaction = scope.getTransaction();
}
const req = orig.call(this, operation, params);
req.on('afterBuild', () => {
if (transaction) {
span = transaction.startChild({
description: describe(this, operation, params),
op: 'http.client',
});
}
});
req.on('complete', () => {
if (span) {
span.finish();
}
});
if (callback) {
req.send(callback);
}
return req;
};
}
/** Describes an operation on generic AWS service */
function describe<TService extends AWSService>(service: TService, operation: string, params?: GenericParams): string {
let ret = `aws.${service.serviceIdentifier}.${operation}`;
if (params === undefined) {
return ret;
}
switch (service.serviceIdentifier) {
case 's3':
ret += describeS3Operation(operation, params);
break;
case 'lambda':
ret += describeLambdaOperation(operation, params);
break;
}
return ret;
}
/** Describes an operation on AWS Lambda service */
function describeLambdaOperation(_operation: string, params: GenericParams): string {
let ret = '';
if ('FunctionName' in params) {
ret += ` ${params.FunctionName}`;
}
return ret;
}
/** Describes an operation on AWS S3 service */
function describeS3Operation(_operation: string, params: GenericParams): string {
let ret = '';
if ('Bucket' in params) {
ret += ` ${params.Bucket}`;
}
return ret;
}