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.
@@ -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