stripe 5.8.0 → 5.9.0

Sign up to get free protection for your applications and to get access to all the features.
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