-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathclient-s3.js
More file actions
209 lines (187 loc) · 6.32 KB
/
client-s3.js
File metadata and controls
209 lines (187 loc) · 6.32 KB
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
* Copyright Elasticsearch B.V. and other contributors where applicable.
* Licensed under the BSD 2-Clause License; you may not use this file except in
* compliance with the BSD 2-Clause License.
*/
'use strict'
const constants = require('../../../constants')
const NAME = 'S3'
const TYPE = 'storage'
const SUBTYPE = 's3'
const elasticAPMStash = Symbol('elasticAPMStash')
/**
* Gets the region from the ARN
*
* @param {String} s3Arn
* @returns {String}
*/
function regionFromS3Arn (s3Arn) {
return s3Arn.split(':')[3]
}
/**
* 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
*
* @param {String} bucket The bucket string
* @returns {String | null}
*/
function resourceFromBucket (bucket) {
let resource = null
if (bucket) {
resource = bucket
if (resource.startsWith('arn:')) {
resource = bucket.split(':').slice(5).join(':')
}
}
return resource
}
/**
* Returns middlewares to instrument an S3Client instance
*
* @param {import('@aws-sdk/client-s3').S3Client} client
* @param {any} agent
* @returns {import('./smithy-client').AWSMiddlewareEntry[]}
*/
function s3MiddlewareFactory (client, agent) {
return [
{
middleware: (next, context) => async (args) => {
const input = args.input
const bucket = input && input.Bucket
const resource = resourceFromBucket(bucket)
const span = agent._instrumentation.currSpan()
if (!span) {
return await next(args)
}
// The given span comes with the operation name and we need to
// add the resource if applies
if (resource) {
span.name += ' ' + resource
span.setServiceTarget('s3', resource)
}
// As for now OTel spec defines attributes for operations that require a Bucket
// if that changes we should review this guard
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/semantic_conventions/trace/instrumentation/aws-sdk.yml#L435
if (bucket) {
const otelAttrs = span._getOTelAttributes()
otelAttrs['aws.s3.bucket'] = bucket
if (input.Key) {
otelAttrs['aws.s3.key'] = input.Key
}
}
let err
let result
let response
let statusCode
try {
result = await next(args)
response = result && result.response
statusCode = response && response.statusCode
} catch (ex) {
// Save the error for use in `finally` below, but re-throw it to
// not impact code flow.
err = ex
// This code path happens with a GetObject conditional request
// that returns a 304 Not Modified.
statusCode = err && err.$metadata && err.$metadata.httpStatusCode
throw ex
} finally {
if (statusCode) {
span._setOutcomeFromHttpStatusCode(statusCode)
} else {
span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE)
}
if (err && (!statusCode || statusCode >= 400)) {
agent.captureError(err, { skipOutcome: true })
}
// Set the httpContext
if (statusCode) {
const httpContext = {
status_code: statusCode
}
if (response && response.headers && response.headers['content-length']) {
const encodedBodySize = Number(response.headers['content-length'])
if (!isNaN(encodedBodySize)) {
httpContext.response = { encoded_body_size: encodedBodySize }
}
}
span.setHttpContext(httpContext)
}
// Configuring `new S3Client({useArnRegion:true})` allows one to
// use an Access Point bucket ARN for a region *other* than the
// one for which the client is configured. Therefore, we attempt
// to get the bucket region from the ARN first.
const config = client.config
let useArnRegion
if (typeof config.useArnRegion === 'boolean') {
useArnRegion = config.useArnRegion
} else {
useArnRegion = await config.useArnRegion()
}
let region
if (useArnRegion && bucket && bucket.startsWith('arn:')) {
region = regionFromS3Arn(args.input.Bucket)
} else {
region = typeof config.region === 'boolean' ? region : await config.region()
}
// Destination context.
const destContext = {
address: context[elasticAPMStash].hostname,
port: context[elasticAPMStash].port,
service: {
name: SUBTYPE,
type: TYPE
}
}
if (resource) {
destContext.service.resource = resource
}
if (region) {
destContext.cloud = { region }
}
span._setDestinationContext(destContext)
span.end()
}
return result
},
options: { step: 'initialize', priority: 'high', name: 'elasticAPMSpan' }
},
{
middleware: (next, context) => async (args) => {
const req = args.request
let port = req.port
// Resolve port for HTTP(S) protocols
if (port === undefined) {
if (req.protocol === 'https:') {
port = 443
} else if (req.protocol === 'http:') {
port = 80
}
}
context[elasticAPMStash] = {
protocol: req.protocol,
hostname: req.hostname,
port
}
return next(args)
},
options: { step: 'finalizeRequest', name: 'elasticAPMHTTPInfo' }
}
]
}
module.exports = {
S3_NAME: NAME,
S3_TYPE: TYPE,
S3_SUBTYPE: SUBTYPE,
s3MiddlewareFactory
}