sentry-rails 5.9.0 → 5.26.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/Gemfile +40 -26
  4. data/README.md +1 -1
  5. data/Rakefile +13 -10
  6. data/app/jobs/sentry/send_event_job.rb +2 -1
  7. data/bin/console +1 -0
  8. data/lib/generators/sentry_generator.rb +31 -0
  9. data/lib/sentry/rails/action_cable.rb +12 -6
  10. data/lib/sentry/rails/active_job.rb +71 -10
  11. data/lib/sentry/rails/background_worker.rb +2 -0
  12. data/lib/sentry/rails/backtrace_cleaner.rb +7 -7
  13. data/lib/sentry/rails/breadcrumb/active_support_logger.rb +5 -7
  14. data/lib/sentry/rails/breadcrumb/monotonic_active_support_logger.rb +2 -0
  15. data/lib/sentry/rails/capture_exceptions.rb +25 -9
  16. data/lib/sentry/rails/configuration.rb +107 -18
  17. data/lib/sentry/rails/controller_methods.rb +2 -0
  18. data/lib/sentry/rails/controller_transaction.rb +9 -3
  19. data/lib/sentry/rails/engine.rb +2 -0
  20. data/lib/sentry/rails/error_subscriber.rb +9 -1
  21. data/lib/sentry/rails/instrument_payload_cleanup_helper.rb +2 -0
  22. data/lib/sentry/rails/overrides/streaming_reporter.rb +2 -0
  23. data/lib/sentry/rails/railtie.rb +17 -4
  24. data/lib/sentry/rails/rescued_exception_interceptor.rb +12 -1
  25. data/lib/sentry/rails/tracing/abstract_subscriber.rb +2 -1
  26. data/lib/sentry/rails/tracing/action_controller_subscriber.rb +6 -2
  27. data/lib/sentry/rails/tracing/action_view_subscriber.rb +11 -2
  28. data/lib/sentry/rails/tracing/active_record_subscriber.rb +89 -7
  29. data/lib/sentry/rails/tracing/active_storage_subscriber.rb +17 -4
  30. data/lib/sentry/rails/tracing/active_support_subscriber.rb +63 -0
  31. data/lib/sentry/rails/tracing.rb +2 -0
  32. data/lib/sentry/rails/version.rb +3 -1
  33. data/lib/sentry/rails.rb +2 -0
  34. data/lib/sentry-rails.rb +2 -0
  35. data/sentry-rails.gemspec +14 -6
  36. metadata +14 -14
  37. data/CODE_OF_CONDUCT.md +0 -74
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93209fe5a0b0e76d0260c3355410492f36d8d72ef4f4852b4345d80656eb9dc6
4
- data.tar.gz: e67e804834d28db4c5237757caffded4856d6a9594f337b2412e76c874a1cd58
3
+ metadata.gz: 8136167d60f6ee67885cdde939a5aa1e29f0e9f9bc1bd64b0ff4aecc29a477a9
4
+ data.tar.gz: d03846dced0b11fbe03df3b3386c01e2d4ff84d5cfc64dcaf33612f82f2de69a
5
5
  SHA512:
6
- metadata.gz: eadf2d1e97b5ab7a6abf9fdc03609bf1c362e1ebba96b369f895c04a8d8341acbb2d3f121c1d03420fbcb32083abc1a07489a1f1b12b02c99b8b4c8bebfdbd41
7
- data.tar.gz: 1c71775f931fd3b08a7dc1567a3baff47d6c56a128f34e5eb8ab42be9c1b89fae3e2201feba4e495e09e1fb8a343039ce4be3ccdc88bcea9ed63c6b024366a06
6
+ metadata.gz: '0900b1dd825499e01d9a5ce77c71bc7821581420abbb60ea8123fcdafed8d7fd9db53b8fb31bdcbbe445e95583c9b477b47f15bdeb58bc3988c54289f328d1bb'
7
+ data.tar.gz: 356eb3d7a526cb1bcd4ee56c3b32201fbbbfe58b0f6ea2cb8152fd7d732964522dea8bc83e08411271fc531c92a5cc9185b2a17dfd50d06153a1ca0f9a6ba87a
data/.gitignore CHANGED
@@ -5,7 +5,7 @@
5
5
  /doc/
6
6
  /pkg/
7
7
  /spec/reports/
8
- /spec/dummy/test_rails_app/db
8
+ /spec/dummy/test_rails_app/db*
9
9
  /tmp/
10
10
 
11
11
  # rspec failure tracking
data/Gemfile CHANGED
@@ -1,48 +1,62 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
  git_source(:github) { |name| "https://github.com/#{name}.git" }
3
5
 
6
+ eval_gemfile "../Gemfile"
7
+
4
8
  # Specify your gem's dependencies in sentry-ruby.gemspec
5
9
  gemspec
6
10
  gem "sentry-ruby", path: "../sentry-ruby"
7
11
 
12
+ platform :jruby do
13
+ gem "activerecord-jdbcmysql-adapter"
14
+ gem "jdbc-sqlite3"
15
+ end
16
+
17
+ ruby_version = Gem::Version.new(RUBY_VERSION)
18
+
8
19
  rails_version = ENV["RAILS_VERSION"]
9
- rails_version = "7.0.0" if rails_version.nil?
20
+ rails_version = "8.0.0" if rails_version.nil?
10
21
  rails_version = Gem::Version.new(rails_version)
11
22
 
12
- gem 'activerecord-jdbcmysql-adapter', platform: :jruby
13
- gem "jdbc-sqlite3", platform: :jruby
14
-
15
- if rails_version < Gem::Version.new("6.0.0")
16
- gem "sqlite3", "~> 1.3.0", platform: :ruby
23
+ gem "rails", "~> #{rails_version}"
24
+
25
+ if rails_version >= Gem::Version.new("8.0.0")
26
+ gem "rspec-rails"
27
+ gem "sqlite3", "~> 2.1.1", platform: :ruby
28
+ elsif rails_version >= Gem::Version.new("7.1.0")
29
+ gem "rspec-rails"
30
+ gem "sqlite3", "~> 1.7.3", platform: :ruby
31
+ elsif rails_version >= Gem::Version.new("6.1.0")
32
+ gem "rspec-rails", "~> 4.0"
33
+
34
+ if ruby_version >= Gem::Version.new("2.7.0")
35
+ gem "sqlite3", "~> 1.7.3", platform: :ruby
36
+ else
37
+ gem "sqlite3", "~> 1.6.9", platform: :ruby
38
+ end
17
39
  else
18
- gem "sqlite3", platform: :ruby
40
+ gem "rspec-rails", "~> 4.0"
41
+ gem "psych", "~> 3.0.0"
42
+
43
+ if rails_version >= Gem::Version.new("6.0.0")
44
+ gem "sqlite3", "~> 1.4.0", platform: :ruby
45
+ else
46
+ gem "sqlite3", "~> 1.3.0", platform: :ruby
47
+ end
19
48
  end
20
49
 
21
- if rails_version > Gem::Version.new("7.0.0")
22
- gem "rails", github: "rails/rails"
23
- else
24
- gem "rails", "~> #{rails_version}"
50
+ if ruby_version < Gem::Version.new("2.5.0")
51
+ # https://github.com/flavorjones/loofah/pull/267
52
+ # loofah changed the required ruby version in a patch so we need to explicitly pin it
53
+ gem "loofah", "2.20.0"
25
54
  end
26
55
 
27
56
  gem "mini_magick"
28
57
 
29
58
  gem "sprockets-rails"
30
59
 
31
- gem "sidekiq"
32
-
33
- gem "rspec", "~> 3.0"
34
- gem "rspec-retry"
35
- gem "rspec-rails", "~> 4.0"
36
- gem 'simplecov'
37
- gem "simplecov-cobertura", "~> 1.4"
38
- gem "rexml"
39
-
40
- gem "rake", "~> 12.0"
41
-
42
- gem "object_tracer"
43
- gem "debug", github: "ruby/debug", platform: :ruby if RUBY_VERSION.to_f >= 2.6
44
- gem "pry"
45
-
46
60
  gem "benchmark-ips"
47
61
  gem "benchmark_driver"
48
62
  gem "benchmark-ipsa"
data/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  [![Gem Version](https://img.shields.io/gem/v/sentry-rails.svg)](https://rubygems.org/gems/sentry-rails)
14
- ![Build Status](https://github.com/getsentry/sentry-ruby/workflows/sentry-rails%20Test/badge.svg)
14
+ ![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml/badge.svg)
15
15
  [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master)
16
16
  [![Gem](https://img.shields.io/gem/dt/sentry-rails.svg)](https://rubygems.org/gems/sentry-rails/)
17
17
  [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sentry-rails&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sentry-rails&package-manager=bundler&version-scheme=semver)
data/Rakefile CHANGED
@@ -1,14 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
4
+ require_relative "../lib/sentry/test/rake_tasks"
3
5
 
4
- RSpec::Core::RakeTask.new(:spec).tap do |task|
5
- task.rspec_opts = "--order rand"
6
- end
6
+ Sentry::Test::RakeTasks.define_spec_tasks(
7
+ spec_pattern: "spec/sentry/**/*_spec.rb",
8
+ spec_rspec_opts: "--order rand --format progress",
9
+ isolated_specs_pattern: "spec/isolated/**/*_spec.rb",
10
+ isolated_rspec_opts: "--format progress"
11
+ )
7
12
 
8
- task :isolated_specs do
9
- Dir["spec/isolated/*"].each do |file|
10
- sh "bundle exec ruby #{file}"
11
- end
12
- end
13
+ Sentry::Test::RakeTasks.define_versioned_specs_task(
14
+ rspec_opts: "--order rand --format progress"
15
+ )
13
16
 
14
- task :default => [:spec, :isolated_specs]
17
+ task default: [:spec, :"spec:versioned", :"spec:isolated"]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  if defined?(ActiveJob)
2
4
  module Sentry
3
5
  parent_job =
@@ -31,4 +33,3 @@ else
31
33
  class SendEventJob; end
32
34
  end
33
35
  end
34
-
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "sentry/ruby"
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ class SentryGenerator < ::Rails::Generators::Base
6
+ class_option :dsn, type: :string, desc: "Sentry DSN"
7
+
8
+ class_option :inject_meta, type: :boolean, default: true, desc: "Inject meta tag into layout"
9
+
10
+ def copy_initializer_file
11
+ dsn = options[:dsn] ? "'#{options[:dsn]}'" : "ENV['SENTRY_DSN']"
12
+
13
+ create_file "config/initializers/sentry.rb", <<~RUBY
14
+ # frozen_string_literal: true
15
+
16
+ Sentry.init do |config|
17
+ config.breadcrumbs_logger = [:active_support_logger]
18
+ config.dsn = #{dsn}
19
+ config.traces_sample_rate = 1.0
20
+ end
21
+ RUBY
22
+ end
23
+
24
+ def inject_code_into_layout
25
+ return unless options[:inject_meta]
26
+
27
+ inject_into_file "app/views/layouts/application.html.erb", before: "</head>\n" do
28
+ " <%= Sentry.get_trace_propagation_meta.html_safe %>\n "
29
+ end
30
+ end
31
+ end
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Rails
3
5
  module ActionCableExtensions
4
6
  class ErrorHandler
5
- OP_NAME = "websocket.server".freeze
7
+ OP_NAME = "websocket.server"
8
+ SPAN_ORIGIN = "auto.http.rails.actioncable"
6
9
 
7
10
  class << self
8
11
  def capture(connection, transaction_name:, extra_context: nil, &block)
@@ -33,11 +36,14 @@ module Sentry
33
36
  end
34
37
 
35
38
  def start_transaction(env, scope)
36
- sentry_trace = env["HTTP_SENTRY_TRACE"]
37
- baggage = env["HTTP_BAGGAGE"]
38
-
39
- options = { name: scope.transaction_name, source: scope.transaction_source, op: OP_NAME }
40
- transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, baggage: baggage, **options) if sentry_trace
39
+ options = {
40
+ name: scope.transaction_name,
41
+ source: scope.transaction_source,
42
+ op: OP_NAME,
43
+ origin: SPAN_ORIGIN
44
+ }
45
+
46
+ transaction = Sentry.continue_trace(env, **options)
41
47
  Sentry.start_transaction(transaction: transaction, **options)
42
48
  end
43
49
 
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
1
5
  module Sentry
2
6
  module Rails
3
7
  module ActiveJobExtensions
@@ -16,7 +20,12 @@ module Sentry
16
20
  end
17
21
 
18
22
  class SentryReporter
19
- OP_NAME = "queue.active_job".freeze
23
+ OP_NAME = "queue.active_job"
24
+ SPAN_ORIGIN = "auto.queue.active_job"
25
+
26
+ EVENT_HANDLERS = {
27
+ "enqueue_retry.active_job" => :retry_handler
28
+ }
20
29
 
21
30
  class << self
22
31
  def record(job, &block)
@@ -27,7 +36,12 @@ module Sentry
27
36
  if job.is_a?(::Sentry::SendEventJob)
28
37
  nil
29
38
  else
30
- Sentry.start_transaction(name: scope.transaction_name, source: scope.transaction_source, op: OP_NAME)
39
+ Sentry.start_transaction(
40
+ name: scope.transaction_name,
41
+ source: scope.transaction_source,
42
+ op: OP_NAME,
43
+ origin: SPAN_ORIGIN
44
+ )
31
45
  end
32
46
 
33
47
  scope.set_span(transaction) if transaction
@@ -38,19 +52,54 @@ module Sentry
38
52
  rescue Exception => e # rubocop:disable Lint/RescueException
39
53
  finish_sentry_transaction(transaction, 500)
40
54
 
41
- Sentry::Rails.capture_exception(
42
- e,
43
- extra: sentry_context(job),
44
- tags: {
45
- job_id: job.job_id,
46
- provider_job_id: job.provider_job_id
47
- }
48
- )
55
+ capture_exception(job, e)
56
+
49
57
  raise
50
58
  end
51
59
  end
52
60
  end
53
61
 
62
+ def capture_exception(job, e)
63
+ Sentry::Rails.capture_exception(
64
+ e,
65
+ extra: sentry_context(job),
66
+ tags: {
67
+ job_id: job.job_id,
68
+ provider_job_id: job.provider_job_id
69
+ }
70
+ )
71
+ end
72
+
73
+ def register_event_handlers
74
+ EVENT_HANDLERS.each do |name, handler|
75
+ subscribers << ActiveSupport::Notifications.subscribe(name) do |*args|
76
+ public_send(handler, *args)
77
+ end
78
+ end
79
+ end
80
+
81
+ def detach_event_handlers
82
+ subscribers.each do |subscriber|
83
+ ActiveSupport::Notifications.unsubscribe(subscriber)
84
+ end
85
+ subscribers.clear
86
+ end
87
+
88
+ # This handler does not capture error unless `active_job_report_on_retry_error` is true
89
+ def retry_handler(*args)
90
+ handle_error_event(*args) do |job, error|
91
+ return if !Sentry.initialized? || job.already_supported_by_sentry_integration?
92
+ return unless Sentry.configuration.rails.active_job_report_on_retry_error
93
+
94
+ capture_exception(job, error)
95
+ end
96
+ end
97
+
98
+ def handle_error_event(*args)
99
+ event = ActiveSupport::Notifications::Event.new(*args)
100
+ yield(event.payload[:job], event.payload[:error])
101
+ end
102
+
54
103
  def finish_sentry_transaction(transaction, status)
55
104
  return unless transaction
56
105
 
@@ -71,6 +120,12 @@ module Sentry
71
120
 
72
121
  def sentry_serialize_arguments(argument)
73
122
  case argument
123
+ when Range
124
+ if (argument.begin || argument.end).is_a?(ActiveSupport::TimeWithZone)
125
+ argument.to_s
126
+ else
127
+ argument.map { |v| sentry_serialize_arguments(v) }
128
+ end
74
129
  when Hash
75
130
  argument.transform_values { |v| sentry_serialize_arguments(v) }
76
131
  when Array, Enumerable
@@ -81,6 +136,12 @@ module Sentry
81
136
  argument
82
137
  end
83
138
  end
139
+
140
+ private
141
+
142
+ def subscribers
143
+ @__subscribers__ ||= Set.new
144
+ end
84
145
  end
85
146
  end
86
147
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class BackgroundWorker
3
5
  def _perform(&block)
@@ -1,21 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/backtrace_cleaner"
2
4
  require "active_support/core_ext/string/access"
3
5
 
4
6
  module Sentry
5
7
  module Rails
6
8
  class BacktraceCleaner < ActiveSupport::BacktraceCleaner
7
- APP_DIRS_PATTERN = /\A(?:\.\/)?(?:app|config|lib|test|\(\w*\))/.freeze
8
- RENDER_TEMPLATE_PATTERN = /:in `.*_\w+_{2,3}\d+_\d+'/.freeze
9
+ APP_DIRS_PATTERN = /\A(?:\.\/)?(?:app|config|lib|test|\(\w*\))/
10
+ RENDER_TEMPLATE_PATTERN = /:in (?:`|').*_\w+_{2,3}\d+_\d+'/
9
11
 
10
12
  def initialize
11
13
  super
12
- # we don't want any default silencers because they're too aggressive
14
+ # We don't want any default silencers because they're too aggressive
13
15
  remove_silencers!
16
+ # We don't want any default filters because Rails 7.2 starts shortening the paths. See #2472
17
+ remove_filters!
14
18
 
15
- @root = "#{Sentry.configuration.project_root}/"
16
- add_filter do |line|
17
- line.start_with?(@root) ? line.from(@root.size) : line
18
- end
19
19
  add_filter do |line|
20
20
  if line =~ RENDER_TEMPLATE_PATTERN
21
21
  line.sub(RENDER_TEMPLATE_PATTERN, "")
@@ -1,20 +1,16 @@
1
- require "sentry/rails/instrument_payload_cleanup_helper"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
4
  module Rails
5
5
  module Breadcrumb
6
6
  module ActiveSupportLogger
7
7
  class << self
8
- include InstrumentPayloadCleanupHelper
9
-
10
8
  def add(name, started, _finished, _unique_id, data)
11
9
  # skip Rails' internal events
12
10
  return if name.start_with?("!")
13
11
 
14
12
  if data.is_a?(Hash)
15
- # we should only mutate the copy of the data
16
- data = data.dup
17
- cleanup_data(data)
13
+ data = data.slice(*@allowed_keys[name])
18
14
  end
19
15
 
20
16
  crumb = Sentry::Breadcrumb.new(
@@ -25,7 +21,9 @@ module Sentry
25
21
  Sentry.add_breadcrumb(crumb)
26
22
  end
27
23
 
28
- def inject
24
+ def inject(allowed_keys)
25
+ @allowed_keys = allowed_keys
26
+
29
27
  @subscriber = ::ActiveSupport::Notifications.subscribe(/.*/) do |name, started, finished, unique_id, data|
30
28
  # we only record events that has a started timestamp
31
29
  if started.is_a?(Time)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "sentry/rails/instrument_payload_cleanup_helper"
2
4
 
3
5
  module Sentry
@@ -1,6 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Rails
3
5
  class CaptureExceptions < Sentry::Rack::CaptureExceptions
6
+ RAILS_7_1 = Gem::Version.new(::Rails.version) >= Gem::Version.new("7.1.0.alpha")
7
+ SPAN_ORIGIN = "auto.http.rails"
8
+
4
9
  def initialize(_)
5
10
  super
6
11
 
@@ -17,32 +22,43 @@ module Sentry
17
22
  end
18
23
 
19
24
  def transaction_op
20
- "http.server".freeze
25
+ "http.server"
21
26
  end
22
27
 
23
28
  def capture_exception(exception, env)
24
- request = ActionDispatch::Request.new(env)
25
-
26
29
  # the exception will be swallowed by ShowExceptions middleware
27
- return if request.show_exceptions? && !Sentry.configuration.rails.report_rescued_exceptions
30
+ return if show_exceptions?(exception, env) && !Sentry.configuration.rails.report_rescued_exceptions
31
+
28
32
  Sentry::Rails.capture_exception(exception).tap do |event|
29
33
  env[ERROR_EVENT_ID_KEY] = event.event_id if event
30
34
  end
31
35
  end
32
36
 
33
37
  def start_transaction(env, scope)
34
- sentry_trace = env["HTTP_SENTRY_TRACE"]
35
- baggage = env["HTTP_BAGGAGE"]
36
-
37
- options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
38
+ options = {
39
+ name: scope.transaction_name,
40
+ source: scope.transaction_source,
41
+ op: transaction_op,
42
+ origin: SPAN_ORIGIN
43
+ }
38
44
 
39
45
  if @assets_regexp && scope.transaction_name.match?(@assets_regexp)
40
46
  options.merge!(sampled: false)
41
47
  end
42
48
 
43
- transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, baggage: baggage, **options) if sentry_trace
49
+ transaction = Sentry.continue_trace(env, **options)
44
50
  Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
45
51
  end
52
+
53
+ def show_exceptions?(exception, env)
54
+ request = ActionDispatch::Request.new(env)
55
+
56
+ if RAILS_7_1
57
+ ActionDispatch::ExceptionWrapper.new(nil, exception).show?(request)
58
+ else
59
+ request.show_exceptions?
60
+ end
61
+ end
46
62
  end
47
63
  end
48
64
  end