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.
@@ -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, :add_field_to_trace
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|
@@ -3,7 +3,7 @@
3
3
  module Honeycomb
4
4
  module Beeline
5
5
  NAME = "honeycomb-beeline".freeze
6
- VERSION = "1.1.0".freeze
6
+ VERSION = "2.1.0".freeze
7
7
  USER_AGENT_SUFFIX = "#{NAME}/#{VERSION}".freeze
8
8
  end
9
9
  end
@@ -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
- @client = configuration.client
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
- @client.instance_variable_set(:@user_agent_addition,
16
- Honeycomb::Beeline::USER_AGENT_SUFFIX)
17
- @client.add_field "meta.beeline_version", Honeycomb::Beeline::VERSION
18
- @client.add_field "meta.local_hostname", configuration.host_name
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
- @client.add_field "service_name", configuration.service_name
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
- client.close
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: client.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("request.error", e.class.name)
57
- context.current_span.add_field("request.error_detail", e.message)
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 :client, :context
92
+ attr_reader :context
82
93
  end
83
94
  end
@@ -33,7 +33,7 @@ module Honeycomb
33
33
 
34
34
  @client ||
35
35
  (debug && Libhoney::LogClient.new) ||
36
- Libhoney::Client.new(options)
36
+ Libhoney::Client.new(**options)
37
37
  end
38
38
 
39
39
  def after_initialize(client)
@@ -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
- span.add_field("#{name}.#{key}", value.to_s)
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
- handlers[name].call(name, span, payload)
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