stripe 5.8.0 → 5.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5a17039dd8609885e953ff5fd576a532f77d02b555d6a3f5d1b6c8437252a34
4
- data.tar.gz: c9ae5618e79a97607fd55563ecca60a3a79de58b5f46f99977c8ed28441b35e8
3
+ metadata.gz: b59015578d0b75db3e8b02959010a9d3a7a3c4212823ad8b51790a869e32b92a
4
+ data.tar.gz: cc8dd57b285effb4814596431b8da59fe92d1a7e34a76d5dc738250a10673142
5
5
  SHA512:
6
- metadata.gz: 6535270353329e05c49b0e3e7853cf096dd4e35ac1e677c781f9005c89a0432141176c5e68cde123381083237d9ff3d460a3fb82bb3373863950217c7496fc21
7
- data.tar.gz: cfc98fa53ba0707c4ad798fc42a168a10eb4b62cb1e57cf6e08d17a9815d0a5c7bfd1057f6d2e40cc9a379210c12f11007c8ac3aafc307676a6e23445500a4ed
6
+ metadata.gz: 6154e0332f7b4c7d66c2238d4474ce904c0faa129dea9a1e1d03668a03d8b03796c593ddb7f04a76082251e6d08520996a28bd2427d897cb1dd769dfc1c3f8aa
7
+ data.tar.gz: 3d4bf185574c11c8c867b1998e4f07f1bce7b4dcaab166d475a5e2406e0cf2275e6d9890bf0df7f94523d36cc58c577819edaa9d301ba96b0d722d4806cbcdac
@@ -1,5 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.9.0 - 2019-11-07
4
+ * [#870](https://github.com/stripe/stripe-ruby/pull/870) Add request instrumentation callback (see `README.md` for usage example)
5
+
3
6
  ## 5.8.0 - 2019-11-05
4
7
  * [#879](https://github.com/stripe/stripe-ruby/pull/879) Add support for `Mandate`
5
8
  * [#876](https://github.com/stripe/stripe-ruby/pull/876) Add additional per-request configuration documentation
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Stripe Ruby Library
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/stripe.svg)](https://badge.fury.io/rb/stripe)
3
4
  [![Build Status](https://travis-ci.org/stripe/stripe-ruby.svg?branch=master)](https://travis-ci.org/stripe/stripe-ruby)
4
5
  [![Coverage Status](https://coveralls.io/repos/github/stripe/stripe-ruby/badge.svg?branch=master)](https://coveralls.io/github/stripe/stripe-ruby?branch=master)
5
6
 
@@ -221,6 +222,31 @@ There are a few options for enabling it:
221
222
  Stripe.log_level = Stripe::LEVEL_INFO
222
223
  ```
223
224
 
225
+ ### Instrumentation
226
+
227
+ The library has a hook for when a HTTP call is made which can be used for
228
+ monitoring. The callback receives a `RequestEvent` object with the following
229
+ data:
230
+ - HTTP method (`Symbol`)
231
+ - request path (`String`)
232
+ - HTTP response code (`Integer`) if available, or `nil` in case of a lower
233
+ level network error
234
+ - request duration in seconds (`Float`)
235
+ - the number of retries (`Integer`)
236
+
237
+ For example:
238
+ ```ruby
239
+ Stripe::Instrumentation.subscribe(:request) do |request_event|
240
+ tags = {
241
+ method: request_event.method,
242
+ resource: request_event.path.split("/")[2],
243
+ code: request_event.http_status,
244
+ retries: request_event.num_retries
245
+ }
246
+ StatsD.distribution('stripe_request', request_event.duration, tags: tags)
247
+ end
248
+ ```
249
+
224
250
  ### Writing a Plugin
225
251
 
226
252
  If you're writing a plugin that uses the library, we'd appreciate it if you
data/VERSION CHANGED
@@ -1 +1 @@
1
- 5.8.0
1
+ 5.9.0
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stripe
4
+ class Instrumentation
5
+ class RequestEvent
6
+ attr_reader :duration
7
+ attr_reader :http_status
8
+ attr_reader :method
9
+ attr_reader :num_retries
10
+ attr_reader :path
11
+
12
+ def initialize(duration:, http_status:, method:, num_retries:, path:)
13
+ @duration = duration
14
+ @http_status = http_status
15
+ @method = method
16
+ @num_retries = num_retries
17
+ @path = path
18
+ freeze
19
+ end
20
+ end
21
+
22
+ def self.subscribe(topic, name = rand, &block)
23
+ subscribers[topic][name] = block
24
+ name
25
+ end
26
+
27
+ def self.unsubscribe(topic, name)
28
+ subscribers[topic].delete(name)
29
+ end
30
+
31
+ def self.notify(topic, event)
32
+ subscribers[topic].each_value { |subscriber| subscriber.call(event) }
33
+ end
34
+
35
+ def self.subscribers
36
+ @subscribers ||= Hash.new { |hash, key| hash[key] = {} }
37
+ end
38
+ private_class_method :subscribers
39
+ end
40
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "stripe/instrumentation"
4
+
3
5
  module Stripe
4
6
  # StripeClient executes requests against the Stripe API and allows a user to
5
7
  # recover both a resource a call returns as well as a response object that
@@ -452,15 +454,18 @@ module Stripe
452
454
  request_start = Util.monotonic_time
453
455
  log_request(context, num_retries)
454
456
  resp = yield
457
+ request_duration = Util.monotonic_time - request_start
458
+ http_status = resp.code.to_i
455
459
  context = context.dup_from_response_headers(resp)
456
460
 
457
- handle_error_response(resp, context) if resp.code.to_i >= 400
461
+ handle_error_response(resp, context) if http_status >= 400
458
462
 
459
- log_response(context, request_start, resp.code.to_i, resp.body)
463
+ log_response(context, request_start, http_status, resp.body)
464
+ notify_subscribers(request_duration, http_status, context,
465
+ num_retries)
460
466
 
461
467
  if Stripe.enable_telemetry? && context.request_id
462
- request_duration_ms =
463
- ((Util.monotonic_time - request_start) * 1000).to_int
468
+ request_duration_ms = (request_duration * 1000).to_i
464
469
  @last_request_metrics =
465
470
  StripeRequestMetrics.new(context.request_id, request_duration_ms)
466
471
  end
@@ -472,14 +477,18 @@ module Stripe
472
477
  # If we modify context we copy it into a new variable so as not to
473
478
  # taint the original on a retry.
474
479
  error_context = context
480
+ request_duration = Util.monotonic_time - request_start
481
+ http_status = nil
475
482
 
476
483
  if e.is_a?(Stripe::StripeError)
477
484
  error_context = context.dup_from_response_headers(e.http_headers)
485
+ http_status = resp.code.to_i
478
486
  log_response(error_context, request_start,
479
487
  e.http_status, e.http_body)
480
488
  else
481
489
  log_response_error(error_context, request_start, e)
482
490
  end
491
+ notify_subscribers(request_duration, http_status, context, num_retries)
483
492
 
484
493
  if self.class.should_retry?(e, method: method, num_retries: num_retries)
485
494
  num_retries += 1
@@ -503,6 +512,17 @@ module Stripe
503
512
  resp
504
513
  end
505
514
 
515
+ private def notify_subscribers(duration, http_status, context, num_retries)
516
+ request_event = Instrumentation::RequestEvent.new(
517
+ duration: duration,
518
+ http_status: http_status,
519
+ method: context.method,
520
+ num_retries: num_retries,
521
+ path: context.path
522
+ )
523
+ Stripe::Instrumentation.notify(:request, request_event)
524
+ end
525
+
506
526
  private def general_api_error(status, body)
507
527
  APIError.new("Invalid response object from API: #{body.inspect} " \
508
528
  "(HTTP response code was #{status})",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stripe
4
- VERSION = "5.8.0"
4
+ VERSION = "5.9.0"
5
5
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../test_helper"
4
+
5
+ module Stripe
6
+ class InstrumentationTest < Test::Unit::TestCase
7
+ context ".notify" do
8
+ teardown do
9
+ Stripe::Instrumentation.send(:subscribers).clear
10
+ end
11
+
12
+ should "notify subscribers for the right topic" do
13
+ sub1_events = []
14
+ Stripe::Instrumentation.subscribe(:test1, :sub1) { |event| sub1_events << event }
15
+ sub2_events = []
16
+ Stripe::Instrumentation.subscribe(:test2, :sub2) { |event| sub2_events << event }
17
+
18
+ Stripe::Instrumentation.notify(:test1, "hello")
19
+ assert_equal(1, sub1_events.size)
20
+ assert_equal(0, sub2_events.size)
21
+ end
22
+
23
+ should "notify multiple subscribers of the same topic" do
24
+ sub1_events = []
25
+ Stripe::Instrumentation.subscribe(:test, :sub1) { |event| sub1_events << event }
26
+ sub2_events = []
27
+ Stripe::Instrumentation.subscribe(:test, :sub2) { |event| sub2_events << event }
28
+
29
+ Stripe::Instrumentation.notify(:test, "hello")
30
+ assert_equal(1, sub1_events.size)
31
+ assert_equal(1, sub2_events.size)
32
+ end
33
+
34
+ should "not notify a subscriber once it has unsubscribed" do
35
+ events = []
36
+ Stripe::Instrumentation.subscribe(:test, :sub) { |event| events << event }
37
+
38
+ Stripe::Instrumentation.notify(:test, "hello")
39
+ assert_equal(1, events.size)
40
+
41
+ Stripe::Instrumentation.unsubscribe(:test, :sub)
42
+ Stripe::Instrumentation.notify(:test, "hello")
43
+ assert_equal(1, events.size)
44
+ end
45
+ end
46
+
47
+ context "RequestEvent" do
48
+ should "return a frozen object" do
49
+ event = Stripe::Instrumentation::RequestEvent.new(
50
+ duration: 0.1,
51
+ http_status: 200,
52
+ method: :get,
53
+ num_retries: 0,
54
+ path: "/v1/test"
55
+ )
56
+
57
+ assert(event.frozen?)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1144,6 +1144,76 @@ module Stripe
1144
1144
  assert(!trace_payload["last_request_metrics"]["request_duration_ms"].nil?)
1145
1145
  end
1146
1146
  end
1147
+
1148
+ context "instrumentation" do
1149
+ teardown do
1150
+ Stripe::Instrumentation.unsubscribe(:request, :test)
1151
+ end
1152
+
1153
+ should "notify a subscriber of a successful HTTP request" do
1154
+ events = []
1155
+ Stripe::Instrumentation.subscribe(:request, :test) { |event| events << event }
1156
+
1157
+ stub_request(:get, "#{Stripe.api_base}/v1/charges")
1158
+ .to_return(body: JSON.generate(object: "charge"))
1159
+ Stripe::Charge.list
1160
+
1161
+ assert_equal(1, events.size)
1162
+ event = events.first
1163
+ assert_equal(:get, event.method)
1164
+ assert_equal("/v1/charges", event.path)
1165
+ assert_equal(200, event.http_status)
1166
+ assert(event.duration.positive?)
1167
+ assert_equal(0, event.num_retries)
1168
+ end
1169
+
1170
+ should "notify a subscriber of a StripeError" do
1171
+ events = []
1172
+ Stripe::Instrumentation.subscribe(:request, :test) { |event| events << event }
1173
+
1174
+ error = {
1175
+ code: "code",
1176
+ message: "message",
1177
+ param: "param",
1178
+ type: "type",
1179
+ }
1180
+ stub_request(:get, "#{Stripe.api_base}/v1/charges")
1181
+ .to_return(
1182
+ body: JSON.generate(error: error),
1183
+ status: 500
1184
+ )
1185
+ assert_raises(Stripe::APIError) do
1186
+ Stripe::Charge.list
1187
+ end
1188
+
1189
+ assert_equal(1, events.size)
1190
+ event = events.first
1191
+ assert_equal(:get, event.method)
1192
+ assert_equal("/v1/charges", event.path)
1193
+ assert_equal(500, event.http_status)
1194
+ assert(event.duration.positive?)
1195
+ assert_equal(0, event.num_retries)
1196
+ end
1197
+
1198
+ should "notify a subscriber of a network error" do
1199
+ events = []
1200
+ Stripe::Instrumentation.subscribe(:request, :test) { |event| events << event }
1201
+
1202
+ stub_request(:get, "#{Stripe.api_base}/v1/charges")
1203
+ .to_raise(Net::OpenTimeout)
1204
+ assert_raises(Stripe::APIConnectionError) do
1205
+ Stripe::Charge.list
1206
+ end
1207
+
1208
+ assert_equal(1, events.size)
1209
+ event = events.first
1210
+ assert_equal(:get, event.method)
1211
+ assert_equal("/v1/charges", event.path)
1212
+ assert_nil(event.http_status)
1213
+ assert(event.duration.positive?)
1214
+ assert_equal(0, event.num_retries)
1215
+ end
1216
+ end
1147
1217
  end
1148
1218
 
1149
1219
  class SystemProfilerTest < Test::Unit::TestCase
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stripe
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.8.0
4
+ version: 5.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-06 00:00:00.000000000 Z
11
+ date: 2019-11-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Stripe is the easiest way to accept payments online. See https://stripe.com
14
14
  for details.
@@ -49,6 +49,7 @@ files:
49
49
  - lib/stripe/connection_manager.rb
50
50
  - lib/stripe/error_object.rb
51
51
  - lib/stripe/errors.rb
52
+ - lib/stripe/instrumentation.rb
52
53
  - lib/stripe/list_object.rb
53
54
  - lib/stripe/multipart_encoder.rb
54
55
  - lib/stripe/oauth.rb
@@ -167,6 +168,7 @@ files:
167
168
  - test/stripe/exchange_rate_test.rb
168
169
  - test/stripe/file_link_test.rb
169
170
  - test/stripe/file_test.rb
171
+ - test/stripe/instrumentation_test.rb
170
172
  - test/stripe/invoice_item_test.rb
171
173
  - test/stripe/invoice_line_item_test.rb
172
174
  - test/stripe/invoice_test.rb
@@ -249,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
249
251
  - !ruby/object:Gem::Version
250
252
  version: '0'
251
253
  requirements: []
252
- rubygems_version: 3.0.3
254
+ rubygems_version: 3.0.6
253
255
  signing_key:
254
256
  specification_version: 4
255
257
  summary: Ruby bindings for the Stripe API
@@ -283,6 +285,7 @@ test_files:
283
285
  - test/stripe/exchange_rate_test.rb
284
286
  - test/stripe/file_link_test.rb
285
287
  - test/stripe/file_test.rb
288
+ - test/stripe/instrumentation_test.rb
286
289
  - test/stripe/invoice_item_test.rb
287
290
  - test/stripe/invoice_line_item_test.rb
288
291
  - test/stripe/invoice_test.rb