From 5be411f37ec4b609296713898e7786eb02a9022b Mon Sep 17 00:00:00 2001 From: Alex Robbin Date: Sat, 14 Jan 2023 14:19:09 -0500 Subject: [PATCH] add Stripe instrumentation https://github.com/stripe/stripe-ruby --- Appraisals | 7 ++ Rakefile | 2 + docs/GettingStarted.md | 22 ++++++ gemfiles/jruby_9.2.21.0_contrib.gemfile | 1 + gemfiles/jruby_9.2.21.0_contrib.gemfile.lock | 2 + gemfiles/jruby_9.3.9.0_contrib.gemfile | 1 + gemfiles/jruby_9.3.9.0_contrib.gemfile.lock | 2 + gemfiles/jruby_9.4.0.0_contrib.gemfile | 1 + gemfiles/jruby_9.4.0.0_contrib.gemfile.lock | 2 + gemfiles/ruby_2.3.8_contrib.gemfile | 1 + gemfiles/ruby_2.3.8_contrib.gemfile.lock | 2 + gemfiles/ruby_2.4.10_contrib.gemfile | 1 + gemfiles/ruby_2.4.10_contrib.gemfile.lock | 2 + gemfiles/ruby_2.5.9_contrib.gemfile | 1 + gemfiles/ruby_2.5.9_contrib.gemfile.lock | 2 + gemfiles/ruby_2.6.10_contrib.gemfile | 1 + gemfiles/ruby_2.6.10_contrib.gemfile.lock | 2 + gemfiles/ruby_2.7.6_contrib.gemfile | 1 + gemfiles/ruby_2.7.6_contrib.gemfile.lock | 2 + gemfiles/ruby_3.0.4_contrib.gemfile | 1 + gemfiles/ruby_3.0.4_contrib.gemfile.lock | 2 + gemfiles/ruby_3.1.2_contrib.gemfile | 1 + gemfiles/ruby_3.1.2_contrib.gemfile.lock | 2 + gemfiles/ruby_3.2.0_contrib.gemfile | 1 + gemfiles/ruby_3.2.0_contrib.gemfile.lock | 2 + lib/datadog/tracing/contrib.rb | 1 + .../contrib/stripe/configuration/settings.rb | 33 +++++++++ lib/datadog/tracing/contrib/stripe/ext.rb | 26 +++++++ .../tracing/contrib/stripe/integration.rb | 43 +++++++++++ lib/datadog/tracing/contrib/stripe/patcher.rb | 29 ++++++++ lib/datadog/tracing/contrib/stripe/request.rb | 67 +++++++++++++++++ sorbet/rbi/todo.rbi | 2 + .../contrib/stripe/integration_spec.rb | 73 ++++++++++++++++++ .../tracing/contrib/stripe/patcher_spec.rb | 22 ++++++ .../tracing/contrib/stripe/request_spec.rb | 74 +++++++++++++++++++ 35 files changed, 434 insertions(+) create mode 100644 lib/datadog/tracing/contrib/stripe/configuration/settings.rb create mode 100644 lib/datadog/tracing/contrib/stripe/ext.rb create mode 100644 lib/datadog/tracing/contrib/stripe/integration.rb create mode 100644 lib/datadog/tracing/contrib/stripe/patcher.rb create mode 100644 lib/datadog/tracing/contrib/stripe/request.rb create mode 100644 spec/datadog/tracing/contrib/stripe/integration_spec.rb create mode 100644 spec/datadog/tracing/contrib/stripe/patcher_spec.rb create mode 100644 spec/datadog/tracing/contrib/stripe/request_spec.rb diff --git a/Appraisals b/Appraisals index f401d813f50..93b2fbff608 100644 --- a/Appraisals +++ b/Appraisals @@ -550,6 +550,7 @@ elsif ruby_version?('2.3') gem 'sidekiq' gem 'sneakers', '>= 2.12.0' gem 'sqlite3', '~> 1.3.6' + gem 'stripe', '~> 5.15' gem 'sucker_punch' gem 'typhoeus' gem 'que', '>= 1.0.0', '< 2.0.0' @@ -683,6 +684,7 @@ elsif ruby_version?('2.4') gem 'sidekiq' gem 'sneakers', '>= 2.12.0' gem 'sqlite3', '~> 1.3.6' + gem 'stripe', '~> 6.0' gem 'sucker_punch' gem 'typhoeus' gem 'que', '>= 1.0.0', '< 2.0.0' @@ -951,6 +953,7 @@ elsif ruby_version?('2.5') gem 'sneakers', '>= 2.12.0' gem 'bunny', '~> 2.19.0' # uninitialized constant OpenSSL::SSL::TLS1_3_VERSION for jruby, https://github.com/ruby-amqp/bunny/issues/645 gem 'sqlite3', '~> 1.4.1', platform: :ruby + gem 'stripe', '~> 7.0' gem 'jdbc-sqlite3', '>= 3.28', platform: :jruby gem 'sucker_punch' gem 'typhoeus' @@ -1199,6 +1202,7 @@ elsif ruby_version?('2.6') gem 'sidekiq', '~> 6.5' gem 'sneakers', '>= 2.12.0' gem 'sqlite3', '~> 1.4.1', platform: :ruby + gem 'stripe', '~> 8.0' gem 'jdbc-sqlite3', '>= 3.28', platform: :jruby gem 'sucker_punch' gem 'typhoeus' @@ -1426,6 +1430,7 @@ elsif ruby_version?('2.7') gem 'sidekiq', '~> 6' # TODO: Support sidekiq 7.x gem 'sneakers', '>= 2.12.0' gem 'sqlite3', '~> 1.4.1' + gem 'stripe' gem 'sucker_punch' gem 'typhoeus' gem 'que', '>= 1.0.0' @@ -1557,6 +1562,7 @@ elsif ruby_version?('3.0') || ruby_version?('3.1') gem 'sidekiq', '~> 6' # TODO: Support sidekiq 7.x gem 'sneakers', '>= 2.12.0' gem 'sqlite3', '>= 1.4.2', platform: :ruby + gem 'stripe' gem 'jdbc-sqlite3', '>= 3.28', platform: :jruby gem 'sucker_punch' gem 'typhoeus' @@ -1688,6 +1694,7 @@ elsif ruby_version?('3.2') gem 'sidekiq' gem 'sneakers', '>= 2.12.0' gem 'sqlite3', '>= 1.4.2' + gem 'stripe' gem 'sucker_punch' gem 'typhoeus' gem 'que', '>= 1.0.0' diff --git a/Rakefile b/Rakefile index 755579378dd..7935696c7c0 100644 --- a/Rakefile +++ b/Rakefile @@ -160,6 +160,7 @@ namespace :spec do :sidekiq, :sinatra, :sneakers, + :stripe, :sucker_punch, :suite ].each do |contrib| @@ -313,6 +314,7 @@ task :ci do declare '❌ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:shoryuken' declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:sidekiq' declare '❌ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:sneakers' + declare '❌ 2.1 / ❌ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:stripe' declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:sucker_punch' declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:suite' diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 14f9e264138..4ec281af6a0 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -79,6 +79,7 @@ To contribute, check out the [contribution guidelines][contribution docs] and [d - [Sidekiq](#sidekiq) - [Sinatra](#sinatra) - [Sneakers](#sneakers) + - [Stripe](#stripe) - [Sucker Punch](#sucker-punch) - [Additional configuration](#additional-configuration) - [Custom logging](#custom-logging) @@ -515,6 +516,7 @@ For a list of available integrations, and their configuration options, please re | Sidekiq | `sidekiq` | `>= 3.5.4` | `>= 3.5.4` | *[Link](#sidekiq)* | *[Link](https://github.com/mperham/sidekiq)* | | Sinatra | `sinatra` | `>= 1.4` | `>= 1.4` | *[Link](#sinatra)* | *[Link](https://github.com/sinatra/sinatra)* | | Sneakers | `sneakers` | `>= 2.12.0` | `>= 2.12.0` | *[Link](#sneakers)* | *[Link](https://github.com/jondot/sneakers)* | +| Stripe | `stripe` | `>= 5.15.0` | `>= 5.15.0` | *[Link](#stripe)* | *[Link](https://github.com/stripe/stripe-ruby)* | | Sucker Punch | `sucker_punch` | `>= 2.0` | `>= 2.0` | *[Link](#sucker-punch)* | *[Link](https://github.com/brandonhilkert/sucker_punch)* | #### CI Visibility @@ -2032,6 +2034,26 @@ end | `tag_body` | Enable tagging of job message. `true` for on, `false` for off. | `false` | | `error_handler` | Custom error handler invoked when a job raises an error. Provided `span` and `error` as arguments. Sets error on the span by default. Useful for ignoring transient errors. | `proc { |span, error| span.set_error(error) unless span.nil? }` | +### Stripe + +The Stripe integration traces Stripe API requests. + +You can enable it through `Datadog.configure`: + +```ruby +require 'ddtrace' + +Datadog.configure do |c| + c.tracing.instrument :stripe, **options +end +``` + +`options` are the following keyword arguments: + +| Key | Description | Default | +| --- | ----------- | ------- | +| `enabled` | Defines whether Stripe should be traced. Useful for temporarily disabling tracing. `true` or `false` | `true` | + ### Sucker Punch The `sucker_punch` integration traces all scheduled jobs: diff --git a/gemfiles/jruby_9.2.21.0_contrib.gemfile b/gemfiles/jruby_9.2.21.0_contrib.gemfile index 34bf252a6ec..a1b003c041d 100644 --- a/gemfiles/jruby_9.2.21.0_contrib.gemfile +++ b/gemfiles/jruby_9.2.21.0_contrib.gemfile @@ -73,6 +73,7 @@ gem "sneakers", ">= 2.12.0" gem "bunny", "~> 2.19.0" gem "sqlite3", "~> 1.4.1", platform: :ruby gem "jdbc-sqlite3", ">= 3.28", platform: :jruby +gem "stripe", "~> 7.0" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0", "< 2.0.0" diff --git a/gemfiles/jruby_9.2.21.0_contrib.gemfile.lock b/gemfiles/jruby_9.2.21.0_contrib.gemfile.lock index 316a45edffd..b0b5457379d 100644 --- a/gemfiles/jruby_9.2.21.0_contrib.gemfile.lock +++ b/gemfiles/jruby_9.2.21.0_contrib.gemfile.lock @@ -1749,6 +1749,7 @@ GEM sorted_set (1.0.3-java) spoon (0.0.6) ffi + stripe (7.1.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1845,6 +1846,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1.0) sneakers (>= 2.12.0) sqlite3 (~> 1.4.1) + stripe (~> 7.0) sucker_punch typhoeus warning (~> 1) diff --git a/gemfiles/jruby_9.3.9.0_contrib.gemfile b/gemfiles/jruby_9.3.9.0_contrib.gemfile index e077bb3e332..6102787b6b1 100644 --- a/gemfiles/jruby_9.3.9.0_contrib.gemfile +++ b/gemfiles/jruby_9.3.9.0_contrib.gemfile @@ -72,6 +72,7 @@ gem "sidekiq", "~> 6.5" gem "sneakers", ">= 2.12.0" gem "sqlite3", "~> 1.4.1", platform: :ruby gem "jdbc-sqlite3", ">= 3.28", platform: :jruby +gem "stripe", "~> 8.0" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0", "< 2.0.0" diff --git a/gemfiles/jruby_9.3.9.0_contrib.gemfile.lock b/gemfiles/jruby_9.3.9.0_contrib.gemfile.lock index 87db93494d8..f8411b4dcfe 100644 --- a/gemfiles/jruby_9.3.9.0_contrib.gemfile.lock +++ b/gemfiles/jruby_9.3.9.0_contrib.gemfile.lock @@ -1731,6 +1731,7 @@ GEM sorted_set (1.0.3-java) spoon (0.0.6) ffi + stripe (8.1.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1826,6 +1827,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1.0) sneakers (>= 2.12.0) sqlite3 (~> 1.4.1) + stripe (~> 8.0) sucker_punch typhoeus warning (~> 1) diff --git a/gemfiles/jruby_9.4.0.0_contrib.gemfile b/gemfiles/jruby_9.4.0.0_contrib.gemfile index fb556660fa2..dcfc29deb50 100644 --- a/gemfiles/jruby_9.4.0.0_contrib.gemfile +++ b/gemfiles/jruby_9.4.0.0_contrib.gemfile @@ -71,6 +71,7 @@ gem "sidekiq", "~> 6" gem "sneakers", ">= 2.12.0" gem "sqlite3", ">= 1.4.2", platform: :ruby gem "jdbc-sqlite3", ">= 3.28", platform: :jruby +gem "stripe" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0" diff --git a/gemfiles/jruby_9.4.0.0_contrib.gemfile.lock b/gemfiles/jruby_9.4.0.0_contrib.gemfile.lock index 892d5377e9b..e820c1861f5 100644 --- a/gemfiles/jruby_9.4.0.0_contrib.gemfile.lock +++ b/gemfiles/jruby_9.4.0.0_contrib.gemfile.lock @@ -1731,6 +1731,7 @@ GEM sorted_set (1.0.3-java) spoon (0.0.6) ffi + stripe (8.1.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1826,6 +1827,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1.0) sneakers (>= 2.12.0) sqlite3 (>= 1.4.2) + stripe sucker_punch typhoeus warning (~> 1) diff --git a/gemfiles/ruby_2.3.8_contrib.gemfile b/gemfiles/ruby_2.3.8_contrib.gemfile index d781cd906d4..acfaca33c42 100644 --- a/gemfiles/ruby_2.3.8_contrib.gemfile +++ b/gemfiles/ruby_2.3.8_contrib.gemfile @@ -65,6 +65,7 @@ gem "shoryuken" gem "sidekiq" gem "sneakers", ">= 2.12.0" gem "sqlite3", "~> 1.3.6" +gem "stripe", "~> 5.15" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0", "< 2.0.0" diff --git a/gemfiles/ruby_2.3.8_contrib.gemfile.lock b/gemfiles/ruby_2.3.8_contrib.gemfile.lock index a8b47c3eb9b..f899a39f0de 100644 --- a/gemfiles/ruby_2.3.8_contrib.gemfile.lock +++ b/gemfiles/ruby_2.3.8_contrib.gemfile.lock @@ -1641,6 +1641,7 @@ GEM rbtree set (~> 1.0) sqlite3 (1.3.13) + stripe (5.55.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) thor (1.2.1) @@ -1734,6 +1735,7 @@ DEPENDENCIES simplecov-html (~> 0.10.2) sneakers (>= 2.12.0) sqlite3 (~> 1.3.6) + stripe (~> 5.15) sucker_punch typhoeus webmock (>= 3.10.0) diff --git a/gemfiles/ruby_2.4.10_contrib.gemfile b/gemfiles/ruby_2.4.10_contrib.gemfile index 34d0ff56a46..e4879cb2100 100644 --- a/gemfiles/ruby_2.4.10_contrib.gemfile +++ b/gemfiles/ruby_2.4.10_contrib.gemfile @@ -70,6 +70,7 @@ gem "shoryuken" gem "sidekiq" gem "sneakers", ">= 2.12.0" gem "sqlite3", "~> 1.3.6" +gem "stripe", "~> 6.0" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0", "< 2.0.0" diff --git a/gemfiles/ruby_2.4.10_contrib.gemfile.lock b/gemfiles/ruby_2.4.10_contrib.gemfile.lock index c89d69a573d..0ad73a87f9b 100644 --- a/gemfiles/ruby_2.4.10_contrib.gemfile.lock +++ b/gemfiles/ruby_2.4.10_contrib.gemfile.lock @@ -1750,6 +1750,7 @@ GEM rbtree set (~> 1.0) sqlite3 (1.3.13) + stripe (6.5.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1846,6 +1847,7 @@ DEPENDENCIES simplecov (~> 0.17) sneakers (>= 2.12.0) sqlite3 (~> 1.3.6) + stripe (~> 6.0) sucker_punch typhoeus webmock (>= 3.10.0) diff --git a/gemfiles/ruby_2.5.9_contrib.gemfile b/gemfiles/ruby_2.5.9_contrib.gemfile index bf23a96c425..b75a6a79358 100644 --- a/gemfiles/ruby_2.5.9_contrib.gemfile +++ b/gemfiles/ruby_2.5.9_contrib.gemfile @@ -78,6 +78,7 @@ gem "sneakers", ">= 2.12.0" gem "bunny", "~> 2.19.0" gem "sqlite3", "~> 1.4.1", platform: :ruby gem "jdbc-sqlite3", ">= 3.28", platform: :jruby +gem "stripe", "~> 7.0" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0", "< 2.0.0" diff --git a/gemfiles/ruby_2.5.9_contrib.gemfile.lock b/gemfiles/ruby_2.5.9_contrib.gemfile.lock index 5d9a242397a..7e944af7923 100644 --- a/gemfiles/ruby_2.5.9_contrib.gemfile.lock +++ b/gemfiles/ruby_2.5.9_contrib.gemfile.lock @@ -1762,6 +1762,7 @@ GEM rbtree set (~> 1.0) sqlite3 (1.4.4) + stripe (7.1.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1866,6 +1867,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1.0) sneakers (>= 2.12.0) sqlite3 (~> 1.4.1) + stripe (~> 7.0) sucker_punch typhoeus warning (~> 1) diff --git a/gemfiles/ruby_2.6.10_contrib.gemfile b/gemfiles/ruby_2.6.10_contrib.gemfile index e68b3eb0bff..d039f15f719 100644 --- a/gemfiles/ruby_2.6.10_contrib.gemfile +++ b/gemfiles/ruby_2.6.10_contrib.gemfile @@ -77,6 +77,7 @@ gem "sidekiq", "~> 6.5" gem "sneakers", ">= 2.12.0" gem "sqlite3", "~> 1.4.1", platform: :ruby gem "jdbc-sqlite3", ">= 3.28", platform: :jruby +gem "stripe", "~> 8.0" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0", "< 2.0.0" diff --git a/gemfiles/ruby_2.6.10_contrib.gemfile.lock b/gemfiles/ruby_2.6.10_contrib.gemfile.lock index 2ab694770e7..9e01d4b34c6 100644 --- a/gemfiles/ruby_2.6.10_contrib.gemfile.lock +++ b/gemfiles/ruby_2.6.10_contrib.gemfile.lock @@ -1748,6 +1748,7 @@ GEM rbtree set (~> 1.0) sqlite3 (1.4.4) + stripe (8.1.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1851,6 +1852,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1.0) sneakers (>= 2.12.0) sqlite3 (~> 1.4.1) + stripe (~> 8.0) sucker_punch typhoeus warning (~> 1) diff --git a/gemfiles/ruby_2.7.6_contrib.gemfile b/gemfiles/ruby_2.7.6_contrib.gemfile index 55c1c4b3d52..76475347c0f 100644 --- a/gemfiles/ruby_2.7.6_contrib.gemfile +++ b/gemfiles/ruby_2.7.6_contrib.gemfile @@ -72,6 +72,7 @@ gem "shoryuken" gem "sidekiq", "~> 6" gem "sneakers", ">= 2.12.0" gem "sqlite3", "~> 1.4.1" +gem "stripe" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0" diff --git a/gemfiles/ruby_2.7.6_contrib.gemfile.lock b/gemfiles/ruby_2.7.6_contrib.gemfile.lock index d1258eabc32..5cfc5192676 100644 --- a/gemfiles/ruby_2.7.6_contrib.gemfile.lock +++ b/gemfiles/ruby_2.7.6_contrib.gemfile.lock @@ -1744,6 +1744,7 @@ GEM rbtree set (~> 1.0) sqlite3 (1.4.4) + stripe (8.1.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1842,6 +1843,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1.0) sneakers (>= 2.12.0) sqlite3 (~> 1.4.1) + stripe sucker_punch typhoeus warning (~> 1) diff --git a/gemfiles/ruby_3.0.4_contrib.gemfile b/gemfiles/ruby_3.0.4_contrib.gemfile index 8c1339078f4..6e9a6ee50ec 100644 --- a/gemfiles/ruby_3.0.4_contrib.gemfile +++ b/gemfiles/ruby_3.0.4_contrib.gemfile @@ -76,6 +76,7 @@ gem "sidekiq", "~> 6" gem "sneakers", ">= 2.12.0" gem "sqlite3", ">= 1.4.2", platform: :ruby gem "jdbc-sqlite3", ">= 3.28", platform: :jruby +gem "stripe" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0" diff --git a/gemfiles/ruby_3.0.4_contrib.gemfile.lock b/gemfiles/ruby_3.0.4_contrib.gemfile.lock index a80eabdc092..5e45e062d39 100644 --- a/gemfiles/ruby_3.0.4_contrib.gemfile.lock +++ b/gemfiles/ruby_3.0.4_contrib.gemfile.lock @@ -1749,6 +1749,7 @@ GEM set (~> 1.0) sqlite3 (1.6.0-aarch64-linux) sqlite3 (1.6.0-x86_64-linux) + stripe (8.1.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1852,6 +1853,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1.0) sneakers (>= 2.12.0) sqlite3 (>= 1.4.2) + stripe sucker_punch typhoeus warning (~> 1) diff --git a/gemfiles/ruby_3.1.2_contrib.gemfile b/gemfiles/ruby_3.1.2_contrib.gemfile index 8c1339078f4..6e9a6ee50ec 100644 --- a/gemfiles/ruby_3.1.2_contrib.gemfile +++ b/gemfiles/ruby_3.1.2_contrib.gemfile @@ -76,6 +76,7 @@ gem "sidekiq", "~> 6" gem "sneakers", ">= 2.12.0" gem "sqlite3", ">= 1.4.2", platform: :ruby gem "jdbc-sqlite3", ">= 3.28", platform: :jruby +gem "stripe" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0" diff --git a/gemfiles/ruby_3.1.2_contrib.gemfile.lock b/gemfiles/ruby_3.1.2_contrib.gemfile.lock index a80eabdc092..5e45e062d39 100644 --- a/gemfiles/ruby_3.1.2_contrib.gemfile.lock +++ b/gemfiles/ruby_3.1.2_contrib.gemfile.lock @@ -1749,6 +1749,7 @@ GEM set (~> 1.0) sqlite3 (1.6.0-aarch64-linux) sqlite3 (1.6.0-x86_64-linux) + stripe (8.1.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1852,6 +1853,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1.0) sneakers (>= 2.12.0) sqlite3 (>= 1.4.2) + stripe sucker_punch typhoeus warning (~> 1) diff --git a/gemfiles/ruby_3.2.0_contrib.gemfile b/gemfiles/ruby_3.2.0_contrib.gemfile index bf8c096d409..255fc775a3d 100644 --- a/gemfiles/ruby_3.2.0_contrib.gemfile +++ b/gemfiles/ruby_3.2.0_contrib.gemfile @@ -71,6 +71,7 @@ gem "shoryuken" gem "sidekiq" gem "sneakers", ">= 2.12.0" gem "sqlite3", ">= 1.4.2" +gem "stripe" gem "sucker_punch" gem "typhoeus" gem "que", ">= 1.0.0" diff --git a/gemfiles/ruby_3.2.0_contrib.gemfile.lock b/gemfiles/ruby_3.2.0_contrib.gemfile.lock index bd68e605654..0108ff23234 100644 --- a/gemfiles/ruby_3.2.0_contrib.gemfile.lock +++ b/gemfiles/ruby_3.2.0_contrib.gemfile.lock @@ -1738,6 +1738,7 @@ GEM set (~> 1.0) sqlite3 (1.6.0-aarch64-linux) sqlite3 (1.6.0-x86_64-linux) + stripe (8.1.0) sucker_punch (3.1.0) concurrent-ruby (~> 1.0) sys-uname (1.2.2) @@ -1836,6 +1837,7 @@ DEPENDENCIES simplecov-cobertura (~> 2.1.0) sneakers (>= 2.12.0) sqlite3 (>= 1.4.2) + stripe sucker_punch typhoeus warning (~> 1) diff --git a/lib/datadog/tracing/contrib.rb b/lib/datadog/tracing/contrib.rb index 7ed87924038..e40c959e553 100644 --- a/lib/datadog/tracing/contrib.rb +++ b/lib/datadog/tracing/contrib.rb @@ -75,4 +75,5 @@ module Contrib require_relative 'contrib/sidekiq/integration' require_relative 'contrib/sinatra/integration' require_relative 'contrib/sneakers/integration' +require_relative 'contrib/stripe/integration' require_relative 'contrib/sucker_punch/integration' diff --git a/lib/datadog/tracing/contrib/stripe/configuration/settings.rb b/lib/datadog/tracing/contrib/stripe/configuration/settings.rb new file mode 100644 index 00000000000..c0b656a584a --- /dev/null +++ b/lib/datadog/tracing/contrib/stripe/configuration/settings.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require_relative '../../configuration/settings' +require_relative '../ext' + +module Datadog + module Tracing + module Contrib + module Stripe + module Configuration + # Custom settings for the Stripe integration + # @public_api + class Settings < Contrib::Configuration::Settings + option :enabled do |o| + o.default { env_to_bool(Ext::ENV_ENABLED, true) } + o.lazy + end + + option :analytics_enabled do |o| + o.default { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) } + o.lazy + end + + option :analytics_sample_rate do |o| + o.default { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) } + o.lazy + end + end + end + end + end + end +end diff --git a/lib/datadog/tracing/contrib/stripe/ext.rb b/lib/datadog/tracing/contrib/stripe/ext.rb new file mode 100644 index 00000000000..5b67af8001e --- /dev/null +++ b/lib/datadog/tracing/contrib/stripe/ext.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Datadog + module Tracing + module Contrib + module Stripe + # Stripe integration constants + # @public_api Changing resource names, tag names, or environment variables creates breaking changes. + module Ext + ENV_ENABLED = 'DD_TRACE_STRIPE_ENABLED' + ENV_ANALYTICS_ENABLED = 'DD_TRACE_STRIPE_ANALYTICS_ENABLED' + ENV_ANALYTICS_SAMPLE_RATE = 'DD_TRACE_STRIPE_ANALYTICS_SAMPLE_RATE' + SPAN_REQUEST = 'stripe.request' + SPAN_TYPE_REQUEST = 'custom' + TAG_COMPONENT = 'stripe' + TAG_OPERATION_REQUEST = 'request' + TAG_REQUEST_HTTP_STATUS = 'stripe.request.http_status' + TAG_REQUEST_ID = 'stripe.request.id' + TAG_REQUEST_METHOD = 'stripe.request.method' + TAG_REQUEST_NUM_RETRIES = 'stripe.request.num_retries' + TAG_REQUEST_PATH = 'stripe.request.path' + end + end + end + end +end diff --git a/lib/datadog/tracing/contrib/stripe/integration.rb b/lib/datadog/tracing/contrib/stripe/integration.rb new file mode 100644 index 00000000000..7b98baa14b4 --- /dev/null +++ b/lib/datadog/tracing/contrib/stripe/integration.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative '../integration' +require_relative 'configuration/settings' +require_relative 'patcher' + +module Datadog + module Tracing + module Contrib + module Stripe + # Description of Stripe integration + class Integration + include Contrib::Integration + + MINIMUM_VERSION = Gem::Version.new('5.15.0') + + # @public_api Changing the integration name or integration options can cause breaking changes + register_as :stripe + + def self.version + Gem.loaded_specs['stripe'] && Gem.loaded_specs['stripe'].version + end + + def self.loaded? + !defined?(::Stripe).nil? + end + + def self.compatible? + super && version >= MINIMUM_VERSION + end + + def new_configuration + Configuration::Settings.new + end + + def patcher + Patcher + end + end + end + end + end +end diff --git a/lib/datadog/tracing/contrib/stripe/patcher.rb b/lib/datadog/tracing/contrib/stripe/patcher.rb new file mode 100644 index 00000000000..0c12caa0b3a --- /dev/null +++ b/lib/datadog/tracing/contrib/stripe/patcher.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true +# typed: false + +require_relative '../patcher' +require_relative 'request' + +module Datadog + module Tracing + module Contrib + module Stripe + # Provides instrumentation for `stripe` through the Stripe instrumentation framework + module Patcher + include Contrib::Patcher + + module_function + + def target_version + Integration.version + end + + def patch + ::Stripe::Instrumentation.subscribe(:request_begin, :datadog_tracing) { |event| Request.start_span(event) } + ::Stripe::Instrumentation.subscribe(:request_end, :datadog_tracing) { |event| Request.finish_span(event) } + end + end + end + end + end +end diff --git a/lib/datadog/tracing/contrib/stripe/request.rb b/lib/datadog/tracing/contrib/stripe/request.rb new file mode 100644 index 00000000000..4fd53aa8500 --- /dev/null +++ b/lib/datadog/tracing/contrib/stripe/request.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require_relative '../../metadata/ext' +require_relative '../../analytics' +require_relative 'ext' + +module Datadog + module Tracing + module Contrib + module Stripe + # Defines instrumentation for Stripe requests + module Request + module_function + + def start_span(event) + # Start a trace + Tracing.trace(Ext::SPAN_REQUEST).tap do |span| + event.user_data[:datadog_span] = span + end + end + + def finish_span(event) + span = event.user_data[:datadog_span] + # If no active span, return. + return nil if span.nil? + + begin + tag_span(span, event) + ensure + # Finish the span + span.finish + end + end + + def tag_span(span, event) + # dependent upon stripe/stripe-ruby#1168 + span.resource = "stripe.#{event.object_name}" if event.respond_to?(:object_name) && event.object_name + + span.span_type = Ext::SPAN_TYPE_REQUEST + span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT) + span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_REQUEST) + + # Set analytics sample rate + if Contrib::Analytics.enabled?(configuration[:analytics_enabled]) + Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate]) + end + + # Measure service stats + Contrib::Analytics.set_measured(span) + + span.set_tag(Ext::TAG_REQUEST_ID, event.request_id) + span.set_tag(Ext::TAG_REQUEST_HTTP_STATUS, event.http_status.to_s) + span.set_tag(Ext::TAG_REQUEST_METHOD, event.method) + span.set_tag(Ext::TAG_REQUEST_PATH, event.path) + span.set_tag(Ext::TAG_REQUEST_NUM_RETRIES, event.num_retries.to_s) + rescue StandardError => e + Datadog.logger.debug(e.message) + end + + def configuration + Datadog.configuration.tracing[:stripe] + end + end + end + end + end +end diff --git a/sorbet/rbi/todo.rbi b/sorbet/rbi/todo.rbi index 3042c87662c..add7c40db00 100644 --- a/sorbet/rbi/todo.rbi +++ b/sorbet/rbi/todo.rbi @@ -72,6 +72,8 @@ module ::Sinatra::Base; end module ::Sinatra::Request; end module ::Sinatra::Response; end module ::Sneakers; end +module ::Stripe; end +module ::Stripe::Instrumentation; end module ::SuckerPunch; end module ::SuckerPunch::Job::ClassMethods; end module ActionController::Metal; end diff --git a/spec/datadog/tracing/contrib/stripe/integration_spec.rb b/spec/datadog/tracing/contrib/stripe/integration_spec.rb new file mode 100644 index 00000000000..95e6d6c6fb4 --- /dev/null +++ b/spec/datadog/tracing/contrib/stripe/integration_spec.rb @@ -0,0 +1,73 @@ +# typed: ignore + +require 'datadog/tracing/contrib/support/spec_helper' +require 'datadog/tracing/contrib/stripe/integration' + +RSpec.describe Datadog::Tracing::Contrib::Stripe::Integration do + extend ConfigurationHelpers + + let(:integration) { described_class.new(:stripe) } + + describe '.version' do + subject(:version) { described_class.version } + + context 'when the "stripe" gem is loaded' do + include_context 'loaded gems', stripe: described_class::MINIMUM_VERSION + it { is_expected.to be_a_kind_of(Gem::Version) } + end + + context 'when "stripe" gem is not loaded' do + include_context 'loaded gems', stripe: nil + it { is_expected.to be nil } + end + end + + describe '.loaded?' do + subject(:loaded?) { described_class.loaded? } + + context 'when Stripe is defined' do + before { stub_const('Stripe', Class.new) } + + it { is_expected.to be true } + end + + context 'when Stripe is not defined' do + before { hide_const('Stripe') } + + it { is_expected.to be false } + end + end + + describe '.compatible?' do + subject(:compatible?) { described_class.compatible? } + + context 'when "stripe" gem is loaded with a version' do + context 'that is less than the minimum' do + include_context 'loaded gems', stripe: decrement_gem_version(described_class::MINIMUM_VERSION) + it { is_expected.to be false } + end + + context 'that meets the minimum version' do + include_context 'loaded gems', stripe: described_class::MINIMUM_VERSION + it { is_expected.to be true } + end + end + + context 'when gem is not loaded' do + include_context 'loaded gems', stripe: nil + it { is_expected.to be false } + end + end + + describe '#default_configuration' do + subject(:default_configuration) { integration.default_configuration } + + it { is_expected.to be_a_kind_of(Datadog::Tracing::Contrib::Stripe::Configuration::Settings) } + end + + describe '#patcher' do + subject(:patcher) { integration.patcher } + + it { is_expected.to be Datadog::Tracing::Contrib::Stripe::Patcher } + end +end diff --git a/spec/datadog/tracing/contrib/stripe/patcher_spec.rb b/spec/datadog/tracing/contrib/stripe/patcher_spec.rb new file mode 100644 index 00000000000..9c11c993199 --- /dev/null +++ b/spec/datadog/tracing/contrib/stripe/patcher_spec.rb @@ -0,0 +1,22 @@ +# typed: ignore + +require 'datadog/tracing/contrib/support/spec_helper' +require 'ddtrace' +require 'stripe' +require 'datadog/tracing/contrib/stripe/patcher' + +RSpec.describe Datadog::Tracing::Contrib::Stripe::Patcher do + describe '.patch' do + it 'adds a request_begin subscriber to Stripe::Instrumentation' do + described_class.patch + + expect(Stripe::Instrumentation.send(:subscribers)[:request_begin].keys).to include(:datadog_tracing) + end + + it 'adds a request_end subscriber to Stripe::Instrumentation' do + described_class.patch + + expect(Stripe::Instrumentation.send(:subscribers)[:request_end].keys).to include(:datadog_tracing) + end + end +end diff --git a/spec/datadog/tracing/contrib/stripe/request_spec.rb b/spec/datadog/tracing/contrib/stripe/request_spec.rb new file mode 100644 index 00000000000..3f71cf7e832 --- /dev/null +++ b/spec/datadog/tracing/contrib/stripe/request_spec.rb @@ -0,0 +1,74 @@ +# typed: ignore + +require 'datadog/tracing/contrib/support/spec_helper' +require 'ddtrace' +require 'stripe' + +RSpec.describe Datadog::Tracing::Contrib::Stripe::Request do + before do + WebMock.enable! + WebMock.disable_net_connect! + + Stripe.api_key = 'sk_test_123' + + Datadog.configure do |c| + c.tracing.instrument :stripe + end + + stub_request(:get, 'https://api.stripe.com/v1/customers/cus_123') + .with(headers: { 'Authorization' => 'Bearer sk_test_123' }) + .to_return( + status: 200, + body: { id: 'cus_123', object: 'customer' }.to_json, + headers: { 'Request-Id' => 'abc-123-def-456' }, + ) + end + + around do |example| + # Reset before and after each example; don't allow global state to linger. + Datadog.registry[:stripe].reset_configuration! + example.run + Datadog.registry[:stripe].reset_configuration! + end + + after do + WebMock.allow_net_connect! + WebMock.reset! + WebMock.disable! + end + + it 'traces the request' do + Stripe::Customer.retrieve('cus_123') + + expect(spans).to have(1).items + expect(span.name).to eq('stripe.request') + expect(span.resource).to eq('stripe.request') + expect(span.get_tag('stripe.request.id')).to eq('abc-123-def-456') + expect(span.get_tag('stripe.request.http_status')).to eq('200') + expect(span.get_tag('stripe.request.method')).to eq('get') + expect(span.get_tag('stripe.request.path')).to eq('/v1/customers/cus_123') + expect(span.get_tag('stripe.request.num_retries')).to eq('0') + expect(span.status).to eq(0) + end + + # dependent upon stripe/stripe-ruby#1168 + context 'when the stripe library includes the object name in the event' do + before do + allow_any_instance_of(Stripe::Instrumentation::RequestEndEvent).to receive(:object_name).and_return('customer') + end + + it 'traces the request' do + Stripe::Customer.retrieve('cus_123') + + expect(spans).to have(1).items + expect(span.name).to eq('stripe.request') + expect(span.resource).to eq('stripe.customer') + expect(span.get_tag('stripe.request.id')).to eq('abc-123-def-456') + expect(span.get_tag('stripe.request.http_status')).to eq('200') + expect(span.get_tag('stripe.request.method')).to eq('get') + expect(span.get_tag('stripe.request.path')).to eq('/v1/customers/cus_123') + expect(span.get_tag('stripe.request.num_retries')).to eq('0') + expect(span.status).to eq(0) + end + end +end