-
Notifications
You must be signed in to change notification settings - Fork 227
/
Copy paths3.js
129 lines (114 loc) · 4.69 KB
/
s3.js
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
119
120
121
122
123
124
125
126
127
128
129
'use strict'
// Instrument AWS S3 operations via the 'aws-sdk' package.
const constants = require('../../../constants')
const TYPE = 'storage'
const SUBTYPE = 's3'
// Return the PascalCase operation name from `request.operation` by undoing to
// `lowerFirst()` from
// https://github.com/aws/aws-sdk-js/blob/c0c44b8a4e607aae521686898f39a3e359f727e4/lib/model/api.js#L63-L65
//
// For example: 'headBucket' -> 'HeadBucket'
function opNameFromOperation (operation) {
return operation[0].toUpperCase() + operation.slice(1)
}
// Return an APM "resource" string for the bucket, Access Point ARN, or Outpost
// ARN. ARNs are normalized to a shorter resource name.
//
// Known ARN patterns:
// - arn:aws:s3:<region>:<account-id>:accesspoint/<accesspoint-name>
// - arn:aws:s3-outposts:<region>:<account>:outpost/<outpost-id>/bucket/<bucket-name>
// - arn:aws:s3-outposts:<region>:<account>:outpost/<outpost-id>/accesspoint/<accesspoint-name>
//
// In general that is:
// arn:$partition:$service:$region:$accountId:$resource
//
// This parses using the same "split on colon" used by the JavaScript AWS SDK v3.
// https://github.com/aws/aws-sdk-js-v3/blob/v3.18.0/packages/util-arn-parser/src/index.ts#L14-L37
function resourceFromBucket (bucket) {
let resource = null
if (bucket) {
resource = bucket
if (resource.startsWith('arn:')) {
resource = bucket.split(':').slice(5).join(':')
}
}
return resource
}
// Instrument an awk-sdk@2.x operation (i.e. a AWS.Request.send or
// AWS.Request.promise).
//
// @param {AWS.Request} request https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Request.html
function instrumentationS3 (orig, origArguments, request, AWS, agent, { version, enabled }) {
const opName = opNameFromOperation(request.operation)
let name = 'S3 ' + opName
const resource = resourceFromBucket(request.params && request.params.Bucket)
if (resource) {
name += ' ' + resource
}
const span = agent.startSpan(name, TYPE, SUBTYPE, opName)
if (span) {
request.on('complete', function onComplete (response) {
// `response` is an AWS.Response
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Response.html
// Determining the bucket's region.
// `request.httpRequest.region` isn't documented, but the aws-sdk@2
// lib/services/s3.js will set it to the bucket's determined region.
// This can be asynchronously determined -- e.g. if it differs from the
// configured service endpoint region -- so this won't be set until
// 'complete'.
const region = request.httpRequest && request.httpRequest.region
// Destination context.
// '.httpRequest.endpoint' might differ from '.service.endpoint' if
// the bucket is in a different region.
const endpoint = request.httpRequest && request.httpRequest.endpoint
const destContext = {
service: {
name: SUBTYPE,
type: TYPE
}
}
if (endpoint) {
destContext.address = endpoint.hostname
destContext.port = endpoint.port
}
if (resource) {
destContext.service.resource = resource
}
if (region) {
destContext.cloud = { region }
}
span.setDestinationContext(destContext)
if (response) {
// Follow the spec for HTTP client span outcome.
// https://github.com/elastic/apm/blob/master/specs/agents/tracing-instrumentation-http.md#outcome
//
// For example, a S3 GetObject conditional request (e.g. using the
// IfNoneMatch param) will respond with response.error=NotModifed and
// statusCode=304. This is a *successful* outcome.
const statusCode = response.httpResponse && response.httpResponse.statusCode
if (statusCode) {
span._setOutcomeFromHttpStatusCode(statusCode)
} else {
// `statusCode` will be undefined for errors before sending a request, e.g.:
// InvalidConfiguration: Custom endpoint is not compatible with access point ARN
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE)
}
if (response.error && (!statusCode || statusCode >= 400)) {
agent.captureError(response.error, { skipOutcome: true })
}
}
// Workaround a bug in the agent's handling of `span.sync`.
//
// The bug: Currently this span.sync is not set `false` because there is
// an HTTP span created (for this S3 request) in the same async op. That
// HTTP span becomes the "active span" for this async op, and *it* gets
// marked as sync=false in `before()` in async-hooks.js.
span.sync = false
span.end()
})
}
return orig.apply(request, origArguments)
}
module.exports = {
instrumentationS3
}