honeycomb-beeline 1.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|