-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This integration traces AWS service calls as spans.
- Loading branch information
1 parent
832d1ec
commit e899040
Showing
8 changed files
with
293 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
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; | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public setupOnce(): void { | ||
const awsModule = require('aws-sdk/global') as typeof AWS; | ||
fill( | ||
awsModule.Service.prototype, | ||
'makeRequest', | ||
<TService extends AWSService, TResult>( | ||
orig: MakeRequestFunction<GenericParams, TResult>, | ||
): MakeRequestFunction<GenericParams, TResult> => | ||
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: 'request', | ||
}); | ||
} | ||
}); | ||
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import * as AWS from 'aws-sdk'; | ||
import * as nock from 'nock'; | ||
|
||
import * as Sentry from '../src'; | ||
import { AWSServices } from '../src/awsservices'; | ||
|
||
/** | ||
* Why @ts-ignore some Sentry.X calls | ||
* | ||
* A hack-ish way to contain everything related to mocks in the same __mocks__ file. | ||
* Thanks to this, we don't have to do more magic than necessary. Just add and export desired method and assert on it. | ||
*/ | ||
|
||
describe('AWSServices', () => { | ||
beforeAll(() => { | ||
new AWSServices().setupOnce(); | ||
}); | ||
afterEach(() => { | ||
// @ts-ignore see "Why @ts-ignore" note | ||
Sentry.resetMocks(); | ||
}); | ||
afterAll(() => { | ||
nock.restore(); | ||
}); | ||
|
||
describe('S3', () => { | ||
const s3 = new AWS.S3({ accessKeyId: '-', secretAccessKey: '-' }); | ||
|
||
test('getObject', async () => { | ||
nock('https://foo.s3.amazonaws.com') | ||
.get('/bar') | ||
.reply(200, 'contents'); | ||
const data = await s3.getObject({ Bucket: 'foo', Key: 'bar' }).promise(); | ||
expect(data.Body?.toString('utf-8')).toEqual('contents'); | ||
// @ts-ignore see "Why @ts-ignore" note | ||
expect(Sentry.fakeTransaction.startChild).toBeCalledWith({ op: 'request', description: 'aws.s3.getObject foo' }); | ||
// @ts-ignore see "Why @ts-ignore" note | ||
expect(Sentry.fakeSpan.finish).toBeCalled(); | ||
}); | ||
|
||
test('getObject with callback', done => { | ||
expect.assertions(3); | ||
nock('https://foo.s3.amazonaws.com') | ||
.get('/bar') | ||
.reply(200, 'contents'); | ||
s3.getObject({ Bucket: 'foo', Key: 'bar' }, (err, data) => { | ||
expect(err).toBeNull(); | ||
expect(data.Body?.toString('utf-8')).toEqual('contents'); | ||
done(); | ||
}); | ||
// @ts-ignore see "Why @ts-ignore" note | ||
expect(Sentry.fakeTransaction.startChild).toBeCalledWith({ op: 'request', description: 'aws.s3.getObject foo' }); | ||
}); | ||
}); | ||
|
||
describe('Lambda', () => { | ||
const lambda = new AWS.Lambda({ accessKeyId: '-', secretAccessKey: '-', region: 'eu-north-1' }); | ||
|
||
test('invoke', async () => { | ||
nock('https://lambda.eu-north-1.amazonaws.com') | ||
.post('/2015-03-31/functions/foo/invocations') | ||
.reply(201, 'reply'); | ||
const data = await lambda.invoke({ FunctionName: 'foo' }).promise(); | ||
expect(data.Payload?.toString('utf-8')).toEqual('reply'); | ||
// @ts-ignore see "Why @ts-ignore" note | ||
expect(Sentry.fakeTransaction.startChild).toBeCalledWith({ op: 'request', description: 'aws.lambda.invoke foo' }); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.