honeycomb-beeline 1.1.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{bundler_version.sh → .circleci/bundler_version.sh} +1 -2
- data/.circleci/config.yml +132 -0
- data/.circleci/setup-rubygems.sh +3 -0
- data/.rubocop.yml +2 -0
- data/Appraisals +27 -2
- data/CONTRIBUTORS.md +2 -1
- data/Gemfile.lock +45 -42
- data/README.md +3 -1
- data/honeycomb-beeline.gemspec +3 -2
- data/lib/generators/honeycomb/honeycomb_generator.rb +14 -0
- data/lib/honeycomb-beeline.rb +4 -1
- data/lib/honeycomb/beeline/version.rb +1 -1
- data/lib/honeycomb/client.rb +22 -11
- data/lib/honeycomb/configuration.rb +1 -1
- data/lib/honeycomb/integrations/active_support.rb +14 -2
- data/lib/honeycomb/integrations/aws.rb +400 -0
- data/lib/honeycomb/integrations/faraday.rb +1 -1
- data/lib/honeycomb/integrations/rack.rb +15 -4
- data/lib/honeycomb/integrations/rails.rb +67 -11
- data/lib/honeycomb/integrations/railtie.rb +2 -3
- data/lib/honeycomb/integrations/redis.rb +356 -0
- data/lib/honeycomb/integrations/warden.rb +2 -2
- metadata +34 -12
- data/.travis.yml +0 -57
data/lib/honeycomb-beeline.rb
CHANGED
@@ -10,11 +10,13 @@ require "honeycomb/trace"
|
|
10
10
|
module Honeycomb
|
11
11
|
INTEGRATIONS = %i[
|
12
12
|
active_support
|
13
|
+
aws
|
13
14
|
faraday
|
14
15
|
rack
|
15
16
|
rails
|
16
17
|
railtie
|
17
18
|
rake
|
19
|
+
redis
|
18
20
|
sequel
|
19
21
|
sinatra
|
20
22
|
].freeze
|
@@ -23,7 +25,8 @@ module Honeycomb
|
|
23
25
|
extend Forwardable
|
24
26
|
attr_reader :client
|
25
27
|
|
26
|
-
def_delegators :@client, :start_span, :add_field,
|
28
|
+
def_delegators :@client, :libhoney, :start_span, :add_field,
|
29
|
+
:add_field_to_trace, :current_span, :current_trace
|
27
30
|
|
28
31
|
def configure
|
29
32
|
Configuration.new.tap do |config|
|
data/lib/honeycomb/client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "forwardable"
|
3
4
|
require "honeycomb/beeline/version"
|
4
5
|
require "honeycomb/configuration"
|
5
6
|
require "honeycomb/context"
|
@@ -7,18 +8,28 @@ require "honeycomb/context"
|
|
7
8
|
module Honeycomb
|
8
9
|
# The Honeycomb Beeline client
|
9
10
|
class Client
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
attr_reader :libhoney
|
14
|
+
|
15
|
+
def_delegators :@context, :current_span, :current_trace
|
16
|
+
|
10
17
|
def initialize(configuration:)
|
11
|
-
@
|
18
|
+
@libhoney = configuration.client
|
12
19
|
# attempt to set the user_agent_addition, this will only work if the
|
13
20
|
# client has not sent an event prior to being passed in here. This should
|
14
21
|
# be most cases
|
15
|
-
@
|
16
|
-
|
17
|
-
@
|
18
|
-
@
|
22
|
+
@libhoney.instance_variable_set(:@user_agent_addition,
|
23
|
+
Honeycomb::Beeline::USER_AGENT_SUFFIX)
|
24
|
+
@libhoney.add_field "meta.beeline_version", Honeycomb::Beeline::VERSION
|
25
|
+
@libhoney.add_field "meta.local_hostname", configuration.host_name
|
26
|
+
|
27
|
+
integrations = Honeycomb.integrations_to_load
|
28
|
+
@libhoney.add_field "meta.instrumentations_count", integrations.count
|
29
|
+
@libhoney.add_field "meta.instrumentations", integrations.map(&:to_s).to_s
|
19
30
|
|
20
31
|
# maybe make `service_name` a required parameter
|
21
|
-
@
|
32
|
+
@libhoney.add_field "service_name", configuration.service_name
|
22
33
|
@context = Context.new
|
23
34
|
|
24
35
|
@additional_trace_options = {
|
@@ -29,14 +40,14 @@ module Honeycomb
|
|
29
40
|
configuration.after_initialize(self)
|
30
41
|
|
31
42
|
at_exit do
|
32
|
-
|
43
|
+
libhoney.close
|
33
44
|
end
|
34
45
|
end
|
35
46
|
|
36
47
|
def start_span(name:, serialized_trace: nil, **fields)
|
37
48
|
if context.current_trace.nil?
|
38
49
|
Trace.new(serialized_trace: serialized_trace,
|
39
|
-
builder:
|
50
|
+
builder: libhoney.builder,
|
40
51
|
context: context,
|
41
52
|
**@additional_trace_options)
|
42
53
|
else
|
@@ -53,8 +64,8 @@ module Honeycomb
|
|
53
64
|
begin
|
54
65
|
yield context.current_span
|
55
66
|
rescue StandardError => e
|
56
|
-
context.current_span.add_field("
|
57
|
-
context.current_span.add_field("
|
67
|
+
context.current_span.add_field("error", e.class.name)
|
68
|
+
context.current_span.add_field("error_detail", e.message)
|
58
69
|
raise e
|
59
70
|
ensure
|
60
71
|
context.current_span.send
|
@@ -78,6 +89,6 @@ module Honeycomb
|
|
78
89
|
|
79
90
|
private
|
80
91
|
|
81
|
-
attr_reader :
|
92
|
+
attr_reader :context
|
82
93
|
end
|
83
94
|
end
|
@@ -34,7 +34,9 @@ module Honeycomb
|
|
34
34
|
on_notification_event.call(name, span, payload)
|
35
35
|
else
|
36
36
|
payload.each do |key, value|
|
37
|
-
|
37
|
+
# Make ActionController::Parameters parseable by libhoney.
|
38
|
+
value = value.to_unsafe_hash if value.respond_to?(:to_unsafe_hash)
|
39
|
+
span.add_field("#{name}.#{key}", value)
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
@@ -63,7 +65,7 @@ module Honeycomb
|
|
63
65
|
def finish(name, id, payload)
|
64
66
|
return unless (span = spans[id].pop)
|
65
67
|
|
66
|
-
|
68
|
+
handler_for(name).call(name, span, payload)
|
67
69
|
|
68
70
|
span.send
|
69
71
|
end
|
@@ -75,6 +77,16 @@ module Honeycomb
|
|
75
77
|
def spans
|
76
78
|
Thread.current[key] ||= Hash.new { |h, id| h[id] = [] }
|
77
79
|
end
|
80
|
+
|
81
|
+
def handler_for(name)
|
82
|
+
handlers.fetch(name) do
|
83
|
+
handlers[
|
84
|
+
handlers.keys.detect do |key|
|
85
|
+
key.is_a?(Regexp) && key =~ name
|
86
|
+
end
|
87
|
+
]
|
88
|
+
end
|
89
|
+
end
|
78
90
|
end
|
79
91
|
end
|
80
92
|
end
|
@@ -0,0 +1,400 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "aws-sdk-core"
|
4
|
+
|
5
|
+
module Honeycomb
|
6
|
+
module Aws
|
7
|
+
# The version of the aws-sdk-core gem.
|
8
|
+
#
|
9
|
+
# Aws::VERSION was removed in aws-sdk-core v3.2.1 in favor of
|
10
|
+
# Aws::CORE_GEM_VERSION. However, it's still present in aws-sdk v2.
|
11
|
+
#
|
12
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/d0c5f6e5a3e83eeda2d1c81f5dd80e5ac562a6dc/gems/aws-sdk-core/CHANGELOG.md#321-2017-09-06
|
13
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/4c40f6e67e763a0f392ba5b1449254426b68a600/aws-sdk-core/lib/aws-sdk-core/version.rb#L2
|
14
|
+
SDK_VERSION =
|
15
|
+
defined?(::Aws::VERSION) ? ::Aws::VERSION : ::Aws::CORE_GEM_VERSION
|
16
|
+
|
17
|
+
# Instruments AWS clients with Honeycomb events.
|
18
|
+
#
|
19
|
+
# This plugin is automatically added to any aws-sdk client class your app
|
20
|
+
# uses. It uses {SdkHandler} to wrap a Honeycomb span around each
|
21
|
+
# invocation of a client object method. Within that span, there is
|
22
|
+
# typically at least one actual HTTP API call to Amazon, so each API call
|
23
|
+
# is wrapped in a separate span by {ApiHandler}.
|
24
|
+
#
|
25
|
+
# This plugin adds the following options which you can use to configure
|
26
|
+
# your aws-sdk clients:
|
27
|
+
#
|
28
|
+
# * `:honeycomb` - Boolean indicating whether to enable Honeycomb
|
29
|
+
# instrumentation. Defaults to `true`.
|
30
|
+
#
|
31
|
+
# * `:honeycomb_client` - Allows you to set a custom Honeycomb client
|
32
|
+
# object. Defaults to the global {Honeycomb.client}.
|
33
|
+
#
|
34
|
+
# @example Disable the Honeycomb AWS integration globally.
|
35
|
+
# Aws.config.update(honeycomb: false)
|
36
|
+
#
|
37
|
+
# @example Disable the Honeycomb AWS integration locally.
|
38
|
+
# s3 = Aws::S3::Client.new(honeycomb: false)
|
39
|
+
#
|
40
|
+
# @example Use a different client globally.
|
41
|
+
# Aws.config.update(honeycomb_client: custom)
|
42
|
+
#
|
43
|
+
# @example Use a different client locally.
|
44
|
+
# dynamodb = Aws::DynamoDB::Client.new(honeycomb_client: custom)
|
45
|
+
#
|
46
|
+
# @see https://docs.aws.amazon.com/sdk-for-ruby/v3/api/index.html#Configuration_Options
|
47
|
+
# @see SdkHandler
|
48
|
+
# @see ApiHandler
|
49
|
+
class Plugin < Seahorse::Client::Plugin
|
50
|
+
option(
|
51
|
+
:honeycomb,
|
52
|
+
default: true,
|
53
|
+
doc_type: "Boolean",
|
54
|
+
docstring: "When `true`, emit Honeycomb events for every SDK/API call.",
|
55
|
+
)
|
56
|
+
|
57
|
+
option(
|
58
|
+
:honeycomb_client,
|
59
|
+
doc_type: Honeycomb::Client,
|
60
|
+
docstring: "The Honeycomb client used for sending SDK/API call events.",
|
61
|
+
) { Honeycomb.client }
|
62
|
+
|
63
|
+
def add_handlers(handlers, config)
|
64
|
+
return unless config.honeycomb && config.honeycomb_client
|
65
|
+
|
66
|
+
handlers.add(SdkHandler, step: :initialize)
|
67
|
+
handlers.add(ApiHandler, step: :sign, priority: 39)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# An AWS plugin handler that creates spans around SDK calls.
|
72
|
+
#
|
73
|
+
# Each aws-sdk client provides a one-to-one mapping of methods to logical
|
74
|
+
# API operations. {SdkHandler} is responsible for starting the root level
|
75
|
+
# AWS span for the SDK method being called.
|
76
|
+
#
|
77
|
+
# {Plugin} accomplishes this by adding the handler early in the process,
|
78
|
+
# around the initialization step. This doesn't necessarily represent the
|
79
|
+
# network latency of the API requests being made to Amazon, since SDK calls
|
80
|
+
# might involve several underlying operations: request signing, response
|
81
|
+
# parsing, retries, redirects, etc. Thus, {ApiHandler} is responsible for
|
82
|
+
# creating child spans around individual HTTP API calls. The span created
|
83
|
+
# by {SdkHandler} represents the overall result of the method call you made
|
84
|
+
# to the aws-sdk client.
|
85
|
+
class SdkHandler < Seahorse::Client::Handler
|
86
|
+
def call(context)
|
87
|
+
setup(context)
|
88
|
+
response = @handler.call(context)
|
89
|
+
teardown(context, response.error)
|
90
|
+
response
|
91
|
+
rescue StandardError => e
|
92
|
+
teardown(context, e)
|
93
|
+
raise e
|
94
|
+
ensure
|
95
|
+
finish(context)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def setup(context)
|
101
|
+
span = context.config.honeycomb_client.start_span(name: "aws-sdk")
|
102
|
+
context[:honeycomb_aws_sdk_span] = span
|
103
|
+
context[:honeycomb_aws_sdk_data] = {
|
104
|
+
"meta.package" => context[:gem_name] || "aws-sdk",
|
105
|
+
"meta.package_version" => context[:gem_version] || SDK_VERSION,
|
106
|
+
"aws.region" => context.config.region,
|
107
|
+
"aws.service" => context.client.class.identifier,
|
108
|
+
"aws.operation" => context.operation_name,
|
109
|
+
}
|
110
|
+
|
111
|
+
context.params && context.params.each do |key, value|
|
112
|
+
context[:honeycomb_aws_sdk_data]["aws.params.#{key}"] = value
|
113
|
+
end
|
114
|
+
|
115
|
+
span.add context[:honeycomb_aws_sdk_data]
|
116
|
+
end
|
117
|
+
|
118
|
+
def teardown(context, error)
|
119
|
+
span = context[:honeycomb_aws_sdk_span]
|
120
|
+
span.add_field "aws.request_id", context[:request_id]
|
121
|
+
span.add_field "aws.retries", context.retries
|
122
|
+
span.add_field "aws.retry_limit", context.config.retry_limit
|
123
|
+
span.add_field "aws.error", error.class.name if error
|
124
|
+
span.add_field "aws.error_detail", error.message if error
|
125
|
+
end
|
126
|
+
|
127
|
+
def finish(context)
|
128
|
+
span = context.metadata.delete(:honeycomb_aws_sdk_span)
|
129
|
+
span.send
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# An AWS plugin handler that creates spans around API calls.
|
134
|
+
#
|
135
|
+
# Each aws-sdk client provides a one-to-one mapping of methods to API
|
136
|
+
# operations. However, this doesn't mean that each method results in only
|
137
|
+
# one HTTP request to Amazon's servers. There may be request errors,
|
138
|
+
# retries, redirects, etc. So whereas {SdkHandler} wraps the logical
|
139
|
+
# operation in a span, {ApiHandler} wraps the individual API requests in
|
140
|
+
# separate child spans.
|
141
|
+
#
|
142
|
+
# {Plugin} accomplishes this by adding {ApiHandler} as close to sending as
|
143
|
+
# possible, before the client retries requests, follows redirects, or even
|
144
|
+
# parses out response errors. That way, a new span is created for every
|
145
|
+
# literal HTTP request. But it also means we have to take care to propagate
|
146
|
+
# error information to the span correctly, since the stock AWS error
|
147
|
+
# handlers are upstream from this one.
|
148
|
+
#
|
149
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/767a96db5cb98424a78249dca3f0be802148372e/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/s3_signer.rb#L36
|
150
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/767a96db5cb98424a78249dca3f0be802148372e/gems/aws-sdk-core/lib/aws-sdk-core/plugins/client_metrics_send_plugin.rb#L9-L11
|
151
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/97b28ccf18558fc908fd56f52741cf3329de9869/gems/aws-sdk-core/lib/seahorse/client/plugins/raise_response_errors.rb
|
152
|
+
class ApiHandler < Seahorse::Client::Handler
|
153
|
+
def call(context)
|
154
|
+
context.config.honeycomb_client.start_span(name: "aws-api") do |span|
|
155
|
+
instrument(span, context)
|
156
|
+
@handler.call(context)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def instrument(span, context)
|
163
|
+
context[:honeycomb_aws_api_span] = span
|
164
|
+
handle_request(context)
|
165
|
+
handle_response(context)
|
166
|
+
end
|
167
|
+
|
168
|
+
def handle_request(context)
|
169
|
+
span = context[:honeycomb_aws_api_span]
|
170
|
+
add_aws_api_fields(span, context)
|
171
|
+
add_request_fields(span, context)
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_aws_api_fields(span, context)
|
175
|
+
span.add context[:honeycomb_aws_sdk_data]
|
176
|
+
span.add_field "aws.attempt", context.retries + 1
|
177
|
+
add_credentials(span, context) if context.config.credentials
|
178
|
+
handle_redirect(span, context) if context[:redirect_region]
|
179
|
+
end
|
180
|
+
|
181
|
+
def add_credentials(span, context)
|
182
|
+
credentials = context.config.credentials.credentials
|
183
|
+
span.add_field "aws.access_key_id", credentials.access_key_id
|
184
|
+
span.add_field "aws.session_token", credentials.session_token
|
185
|
+
end
|
186
|
+
|
187
|
+
def handle_redirect(span, context)
|
188
|
+
span.add_field "aws.region", context[:redirect_region]
|
189
|
+
end
|
190
|
+
|
191
|
+
def add_request_fields(span, context)
|
192
|
+
request = context.http_request
|
193
|
+
span.add_field "request.method", request.http_method
|
194
|
+
span.add_field "request.scheme", request.endpoint.scheme
|
195
|
+
span.add_field "request.host", request.endpoint.host
|
196
|
+
span.add_field "request.path", request.endpoint.path
|
197
|
+
span.add_field "request.query", request.endpoint.query
|
198
|
+
span.add_field "request.user_agent", request.headers["user-agent"]
|
199
|
+
end
|
200
|
+
|
201
|
+
def handle_response(context)
|
202
|
+
on_headers(context)
|
203
|
+
on_error(context)
|
204
|
+
on_done(context)
|
205
|
+
end
|
206
|
+
|
207
|
+
def on_headers(context)
|
208
|
+
context.http_response.on_headers do |status_code, headers|
|
209
|
+
span = context[:honeycomb_aws_api_span]
|
210
|
+
span.add_field "response.status_code", status_code
|
211
|
+
headers.each do |header, value|
|
212
|
+
if header.start_with?("x-amz-", "x-amzn-")
|
213
|
+
field = "response.#{header.tr('-', '_')}"
|
214
|
+
span.add_field(field, value)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def on_error(context)
|
221
|
+
context.http_response.on_error do |error|
|
222
|
+
span = context[:honeycomb_aws_api_span]
|
223
|
+
span.add_field "response.error", error.class.name
|
224
|
+
span.add_field "response.error_detail", error.message
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def on_done(context)
|
229
|
+
context.http_response.on_done(300..599) do
|
230
|
+
process_api_error(context)
|
231
|
+
process_s3_region(context)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def process_api_error(context)
|
236
|
+
span = context[:honeycomb_aws_api_span]
|
237
|
+
error = parse_api_error_from(context)
|
238
|
+
add_api_error_fields(span, error) if error
|
239
|
+
end
|
240
|
+
|
241
|
+
def add_api_error_fields(span, error)
|
242
|
+
span.add_field "response.error", error.code
|
243
|
+
span.add_field "response.error_detail", error.message
|
244
|
+
end
|
245
|
+
|
246
|
+
# Runs a limited subset of response parsing for AWS-specific errors.
|
247
|
+
#
|
248
|
+
# Because XML/JSON error handlers are inserted at priority 50 of the
|
249
|
+
# :sign step, they're upstream from {ApiHandler} (at priority 39), so we
|
250
|
+
# won't have access to the error saved in Seahorse::Client::Response yet.
|
251
|
+
# We only have the error in the Seahorse::Client::Http::Response object.
|
252
|
+
# But Seahorse::Client::NetHttp::Handler only triggers an HTTP response
|
253
|
+
# error for rescued exceptions (e.g., timeouts). We might still get back
|
254
|
+
# successful HTTP 3xx, 4xx, or 5xx responses that should be interpreted
|
255
|
+
# as aws-api errors.
|
256
|
+
#
|
257
|
+
# So we have to duplicate the logic of either Aws::Xml::ErrorHandler or
|
258
|
+
# Aws::Json::ErrorHandler depending on which one is being used by the
|
259
|
+
# current client. We can determine this by their "protocol" metadata.
|
260
|
+
#
|
261
|
+
# Note that there are still a few straggling errors that might occur from
|
262
|
+
# HTTP 2xx responses. Since those aren't really API call failures, we
|
263
|
+
# won't worry about parsing them out for the aws-api span. Once the
|
264
|
+
# upstream handlers process those errors, they'll be propagated to the
|
265
|
+
# aws-sdk span anyway (since {SdkHandler} will actually have access to
|
266
|
+
# the Seahorse::Client::Response#error).
|
267
|
+
#
|
268
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/d0c5f6e5a3e83eeda2d1c81f5dd80e5ac562a6dc/gems/aws-sdk-core/lib/aws-sdk-core/client_stubs.rb#L298-L307
|
269
|
+
# @see https://github.com/aws/aws-sdk-ruby/tree/b0ade445ce18b24c53a4548074b214e732b8b627/gems/aws-sdk-core/lib/aws-sdk-core/plugins/protocols
|
270
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/354d36792e47f2e81b4889f322928e848e062818/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/http_200_errors.rb
|
271
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/354d36792e47f2e81b4889f322928e848e062818/gems/aws-sdk-dynamodb/lib/aws-sdk-dynamodb/plugins/crc32_validation.rb
|
272
|
+
def parse_api_error_from(context)
|
273
|
+
case context.config.api.metadata["protocol"]
|
274
|
+
when "query", "rest-xml", "ec2"
|
275
|
+
XmlError.new(context)
|
276
|
+
when "json", "rest-json"
|
277
|
+
JsonError.new(context)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# @private
|
282
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/d0c5f6e5a3e83eeda2d1c81f5dd80e5ac562a6dc/gems/aws-sdk-core/lib/aws-sdk-core/xml/error_handler.rb
|
283
|
+
class XmlError < ::Aws::Xml::ErrorHandler
|
284
|
+
attr_reader :code, :message
|
285
|
+
|
286
|
+
def initialize(context)
|
287
|
+
body = context.http_response.body_contents
|
288
|
+
@code = error_code(body, context)
|
289
|
+
@message = error_message(body)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# @private
|
294
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/d0c5f6e5a3e83eeda2d1c81f5dd80e5ac562a6dc/gems/aws-sdk-core/lib/aws-sdk-core/json/error_handler.rb
|
295
|
+
class JsonError < ::Aws::Json::ErrorHandler
|
296
|
+
attr_reader :code, :message
|
297
|
+
|
298
|
+
def initialize(context)
|
299
|
+
body = context.http_response.body_contents
|
300
|
+
json = ::Aws::Json.load(body) || {}
|
301
|
+
@code = error_code(json, context)
|
302
|
+
@message = error_message(code, json)
|
303
|
+
rescue ::Aws::Json::ParseError
|
304
|
+
@code = http_status_error_code(context)
|
305
|
+
@message = ""
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# Propagates S3 region redirect information to the next aws-api span.
|
310
|
+
#
|
311
|
+
# When the AWS S3 client is configured with the wrong region, Amazon
|
312
|
+
# responds to API requests with an HTTP 400 indicating the correct region
|
313
|
+
# for the bucket.
|
314
|
+
#
|
315
|
+
# This error is normally caught upstream by stock plugins that trigger a
|
316
|
+
# new API request with the right region, which will create another
|
317
|
+
# aws-api span after this one. However, since the aws.region field set by
|
318
|
+
# {#add_aws_api_fields} comes from the aws-sdk configuration, its value
|
319
|
+
# would continue being wrong in the next aws-api span. Instead, we want
|
320
|
+
# this span to have the wrong region (that triggered the error) and the
|
321
|
+
# next span to have the right region (which won't come from the config).
|
322
|
+
#
|
323
|
+
# To update aws.region to the right value, {#handle_redirect} looks for a
|
324
|
+
# value stashed in the Seahorse::Client::Context#metadata. This is set by
|
325
|
+
# aws-sdk v3 (via the aws-sdk-s3 gem) but not by aws-sdk v2. So, we have
|
326
|
+
# to duplicate some of the upstream v3 logic in order to propagate the
|
327
|
+
# redirected region in the v2 case. We only do this in the v2 case in the
|
328
|
+
# hopes that eventually we don't have to maintain the duplicated logic.
|
329
|
+
#
|
330
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/379d338406873b0f4b53f118c83fe40761e297ab/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/s3_signer.rb#L151
|
331
|
+
def process_s3_region(context)
|
332
|
+
return unless SDK_VERSION.start_with?("2.")
|
333
|
+
|
334
|
+
redirect = S3Redirect.new(context)
|
335
|
+
context[:redirect_region] = redirect.region if redirect.happening?
|
336
|
+
end
|
337
|
+
|
338
|
+
# @private
|
339
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/379d338406873b0f4b53f118c83fe40761e297ab/gems/aws-sdk-s3/lib/aws-sdk-s3/plugins/s3_signer.rb#L102-L182
|
340
|
+
# @see https://github.com/aws/aws-sdk-ruby/blob/4c40f6e67e763a0f392ba5b1449254426b68a600/aws-sdk-core/lib/aws-sdk-core/plugins/s3_request_signer.rb#L81-L153
|
341
|
+
class S3Redirect
|
342
|
+
REGION_TAG = %r{<Region>(.+?)</Region>}.freeze
|
343
|
+
|
344
|
+
def initialize(context)
|
345
|
+
@context = context
|
346
|
+
end
|
347
|
+
|
348
|
+
def original_host
|
349
|
+
@context.http_request.endpoint.host
|
350
|
+
end
|
351
|
+
|
352
|
+
def status
|
353
|
+
@context.http_response.status_code
|
354
|
+
end
|
355
|
+
|
356
|
+
def happening?
|
357
|
+
status == 400 && region && !original_host.include?("fips")
|
358
|
+
end
|
359
|
+
|
360
|
+
def region
|
361
|
+
@region ||= region_from_headers || region_from_body
|
362
|
+
end
|
363
|
+
|
364
|
+
def region_from_headers
|
365
|
+
@context.http_response.headers["x-amz-bucket-region"]
|
366
|
+
end
|
367
|
+
|
368
|
+
def region_from_body
|
369
|
+
body = @context.http_response.body_contents
|
370
|
+
body.match(REGION_TAG) { |tag| tag[1] }
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# Add the plugin to all aws-sdk client classes.
|
378
|
+
#
|
379
|
+
# Since client classes are dynamically created at load time by
|
380
|
+
# Seahorse::Client::Base.define, it's necessary to call .add_plugin on each
|
381
|
+
# class individually. For example, say we required aws-sdk-s3 before loading
|
382
|
+
# honeycomb-beeline. Then Aws::S3::Client would have already been defined
|
383
|
+
# without knowing about our plugin. So adding Honeycomb::Aws::Plugin to just
|
384
|
+
# Seahorse::Client::Base is insufficent. We have to call
|
385
|
+
# Aws::S3::Client.add_plugin as well.
|
386
|
+
#
|
387
|
+
# This loop will still add the plugin to Seahorse::Client::Base, which covers
|
388
|
+
# us if/when any future aws-sdk client classes get defined.
|
389
|
+
#
|
390
|
+
# This loop will *not* patch any instances of client objects that were created
|
391
|
+
# prior to loading honeycomb-beeline. While we could loop through
|
392
|
+
# ObjectSpace.each_object(Seahorse::Client::Base), it's much more awkward to
|
393
|
+
# reinitialize an existing instance. E.g., at the time the client instance was
|
394
|
+
# created, its configuration wouldn't have responded to the options defined by
|
395
|
+
# Honeycomb::Aws::Plugin, so we can't retroactively configure the plugin. In
|
396
|
+
# practice, this probably isn't a big deal: you'll likely load aws-sdk +
|
397
|
+
# honeycomb-beeline via bundler before ever instantiating an AWS client object.
|
398
|
+
ObjectSpace.each_object(Seahorse::Client::Base.singleton_class) do |client|
|
399
|
+
client.add_plugin(Honeycomb::Aws::Plugin)
|
400
|
+
end
|