Skip to content

Commit ccc7fd7

Browse files
committed
feat: AWS S3 instrumentation
1 parent fc0a760 commit ccc7fd7

File tree

3 files changed

+127
-2
lines changed

3 files changed

+127
-2
lines changed

lib/instrumentation/modules/aws-sdk.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
'use strict'
22
const semver = require('semver')
33
const shimmer = require('../shimmer')
4+
const { instrumentationS3 } = require('./aws-sdk/s3')
45
const { instrumentationSqs } = require('./aws-sdk/sqs')
56

7+
const instrumentorFromSvcId = {
8+
s3: instrumentationS3,
9+
sqs: instrumentationSqs
10+
}
11+
612
// Called in place of AWS.Request.send and AWS.Request.promise
713
//
814
// Determines which amazon service an API request is for
915
// and then passes call on to an appropriate instrumentation
1016
// function.
1117
function instrumentOperation (orig, origArguments, request, AWS, agent, { version, enabled }) {
12-
if (request.service.serviceIdentifier === 'sqs') {
13-
return instrumentationSqs(orig, origArguments, request, AWS, agent, { version, enabled })
18+
const instrumentor = instrumentorFromSvcId[request.service.serviceIdentifier]
19+
if (instrumentor) {
20+
return instrumentor(orig, origArguments, request, AWS, agent, { version, enabled })
1421
}
1522

1623
// if we're still here, then we still need to call the original method
24+
console.warn('XXX serviceIdentifier', request.service.serviceIdentifier)
1725
return orig.apply(request, origArguments)
1826
}
1927

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
'use strict'
2+
3+
// Instrument AWS S3 operations via the 'aws-sdk' package.
4+
5+
const constants = require('../../../constants')
6+
7+
const TYPE = 'storage'
8+
const SUBTYPE = 's3'
9+
10+
// This regex adapted from "OutpostArn" pattern at
11+
// https://docs.aws.amazon.com/outposts/latest/APIReference/API_Outpost.html
12+
const ARN_REGEX = /^arn:aws([a-z-]+)?:(s3|s3-outposts):[a-z\d-]+:\d{12}:(.*?)$/
13+
14+
// Instrument an awk-sdk@2.x operation (i.e. a AWS.Request.send or
15+
// AWS.Request.promise).
16+
//
17+
// @param {AWS.Request} request https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Request.html
18+
function instrumentationS3 (orig, origArguments, request, AWS, agent, { version, enabled }) {
19+
// console.warn('XXX endpoint: ', request.service.endpoint)
20+
// console.warn('XXX service.config before: ', request.service.config)
21+
22+
// XXX action is 'listBuckets' from the SDK. Our spec is 'ListBuckets'. Is a
23+
// simple string.capitalize sufficient? TODO: Find a aws-sdk-js-v3 ref for that.
24+
const action = request.operation[0].toUpperCase() + request.operation.slice(1)
25+
let name = 'S3 ' + action
26+
let resource = null // The bucket name or normalized Access Point/Outpost ARN.
27+
if (request.params && request.params.Bucket) {
28+
resource = request.params.Bucket
29+
if (resource.startsWith('arn:')) {
30+
// This is an Access Point or Outpost ARN, e.g.:
31+
// - arn:aws:s3:region:account-id:accesspoint/resource
32+
// - arn:aws:s3-outposts:<region>:<account>:outpost/<outpost-id>/bucket/<bucket-name>
33+
// - arn:aws:s3-outposts:<region>:<account>:outpost/<outpost-id>/accesspoint/<accesspoint-name></accesspoint-name>
34+
const match = ARN_REGEX.exec(resource)
35+
if (match) {
36+
resource = match[3]
37+
}
38+
}
39+
name += ' ' + resource
40+
}
41+
42+
const span = agent.startSpan(name, TYPE, SUBTYPE, action)
43+
if (span) {
44+
// Determining the bucket region is not always possible.
45+
// - `request.service.config.region` can be "us-west-2" and talk to a bucket
46+
// in "us-west-1"
47+
// - `request.service.endpoint` can be to a regional endpoint, e.g.
48+
// "https://s3.us-east-2.amazonaws.com", to access a bucket hosted in a
49+
// different region.
50+
// - The `x-amz-bucket-region` response header is set for HeadBucket calls.
51+
// - A separate API call to GetBucketLocation will return the
52+
// "LocationConstraint" specified at CreateBucket. API docs
53+
// (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getBucketLocation-property)
54+
// state "Buckets in Region us-east-1 have a LocationConstraint of null."
55+
// though the empty string has been observed.
56+
// - If there is a `response.error` set, it sometimes has a 'region' prop.
57+
span.setDbContext({
58+
type: 's3'
59+
// instance: region // XXX see above
60+
})
61+
62+
// Destination context.
63+
const endpoint = request.service.endpoint
64+
const destContext = {
65+
address: endpoint.hostname,
66+
port: endpoint.port,
67+
service: {
68+
name: 's3',
69+
type: 'storage'
70+
}
71+
}
72+
if (resource) {
73+
destContext.service.resource = resource
74+
}
75+
// TODO: destination.cloud.region is pending https://github.com/elastic/ecs/issues/1282
76+
span.setDestinationContext(destContext)
77+
78+
request.on('complete', function (response) {
79+
// console.warn('XXX response: ', response)
80+
// console.warn('XXX response info: ',
81+
// 'retryCount', response.retryCount,
82+
// 'redirectCount', response.redirectCount,
83+
// 'statusCode', response.httpResponse.statusCode,
84+
// 'headers', response.httpResponse.headers
85+
// )
86+
if (response && response.error) {
87+
console.warn('XXX response.error ', response.error)
88+
// XXX is skipOutcome necessary for S3 error responses?
89+
const errOpts = {
90+
skipOutcome: true
91+
}
92+
agent.captureError(response.error, errOpts)
93+
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE)
94+
}
95+
96+
// Workaround a bug in the agent's handling of `span.sync`.
97+
//
98+
// The bug: Currently this span.sync is not set `false` because there is
99+
// an HTTP span created (for this S3 request) in the same async op. That
100+
// HTTP span becomes the "active span" for this async op, and *it* gets
101+
// marked as sync=false in `before()` in async-hooks.js.
102+
// TODO: move this to a separate issue.
103+
span.sync = false
104+
105+
span.end()
106+
})
107+
}
108+
109+
const origResult = orig.apply(request, origArguments)
110+
111+
return origResult
112+
}
113+
114+
module.exports = {
115+
instrumentationS3
116+
}

lib/instrumentation/modules/aws-sdk/sqs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ function instrumentationSqs (orig, origArguments, request, AWS, agent, { version
152152
const action = getActionFromRequest(request)
153153
const name = getSpanNameFromRequest(request)
154154
const span = agent.startSpan(name, type, subtype, action)
155+
// XXX span can be *null* here if no trans, right?!
155156
span.setDestinationContext(getMessageDestinationContextFromRequest(request))
156157
span.setMessageContext(getMessageContextFromRequest(request))
157158

0 commit comments

Comments
 (0)