sentry-rails 5.1.0 → 5.16.1

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: f45143dd79cff19a0e7338912f235cec728d9ceb81a37e7a50df78a1722ce1b8
4
- data.tar.gz: 7b5a8dec4f9b6875f6f42794911d30e7a75e0a8662e7d5e83584726066eb3fd4
3
+ metadata.gz: 7ee07f40f442adc566067614916895a02d753d2a44c518a78d78528c6d967e1e
4
+ data.tar.gz: 71004c34b4c2798d8022a3d11dbda9daf19f9f9a39a9d4b9a8bdac0332a0b39f
5
5
  SHA512:
6
- metadata.gz: 0dbdd5331da560f4bc67a84390bb1f3579fbe7e03da95758cc2e3b103540f407bb2efb153202070f57dc4e2e1b5044dd9dca7e237272460560531d160890211c
7
- data.tar.gz: 1163c26220a308aca6fe3f624e4358446f82d1919eda4daf0c38729f1977eba08b53c4f8bb65543adfc1df7a129ca4098af35060acb3d921d8ddf58025176633
6
+ metadata.gz: 172c3ca6639f55e608db51ef7efb1837791b5bc8836273fed287b58f32a1b88377b50488e70e94134a4c3b9d230731fba03b7a2523cfde975dd6685c7a136d73
7
+ data.tar.gz: 3a8010027974f00f4c708e8b0356233c3ccddb7eb525df660b9d9aefbcc535870118dbf727b1658b1271bc5afddc9a93b13a4ce0c496fc22ba9c8db18c84d630
data/.gitignore CHANGED
@@ -5,7 +5,7 @@
5
5
  /doc/
6
6
  /pkg/
7
7
  /spec/reports/
8
- /spec/support/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,46 +1,54 @@
1
1
  source "https://rubygems.org"
2
+ git_source(:github) { |name| "https://github.com/#{name}.git" }
2
3
 
3
4
  # Specify your gem's dependencies in sentry-ruby.gemspec
4
5
  gemspec
5
6
  gem "sentry-ruby", path: "../sentry-ruby"
6
7
 
8
+ platform :jruby do
9
+ gem 'activerecord-jdbcmysql-adapter'
10
+ gem "jdbc-sqlite3"
11
+ end
12
+
7
13
  rails_version = ENV["RAILS_VERSION"]
8
- rails_version = "7.0.0" if rails_version.nil?
14
+ rails_version = "7.1.0" if rails_version.nil?
9
15
  rails_version = Gem::Version.new(rails_version)
10
16
 
11
- gem 'activerecord-jdbcmysql-adapter', platform: :jruby
12
- gem "jdbc-sqlite3", platform: :jruby
13
-
14
17
  if rails_version < Gem::Version.new("6.0.0")
15
18
  gem "sqlite3", "~> 1.3.0", platform: :ruby
16
19
  else
17
- gem "sqlite3", platform: :ruby
20
+ # 1.7.0 dropped support for ruby < 3.0, remove later after upgrading craft setup
21
+ gem "sqlite3", "1.6.9", platform: :ruby
18
22
  end
19
23
 
20
- if rails_version >= Gem::Version.new("7.0.0")
21
- gem "rails", github: "rails/rails", branch: "7-0-stable"
24
+ if rails_version >= Gem::Version.new("7.2.0.alpha")
25
+ gem "rails", github: "rails/rails"
26
+ elsif rails_version >= Gem::Version.new("7.1.0")
27
+ gem "rails", "~> #{rails_version}"
22
28
  else
23
29
  gem "rails", "~> #{rails_version}"
30
+ gem "psych", "~> 3.0.0"
24
31
  end
25
32
 
33
+ gem "mini_magick"
34
+
26
35
  gem "sprockets-rails"
27
36
 
28
37
  gem "sidekiq"
29
38
 
30
- gem "rspec", "~> 3.0"
31
- gem "rspec-retry"
32
39
  gem "rspec-rails", "~> 4.0"
33
- gem 'simplecov'
34
- gem "simplecov-cobertura", "~> 1.4"
35
- gem "rexml"
36
40
 
37
- gem "rake", "~> 12.0"
41
+ ruby_version = Gem::Version.new(RUBY_VERSION)
38
42
 
39
- gem "object_tracer"
40
- gem "debug", github: "ruby/debug", platform: :ruby if RUBY_VERSION.to_f >= 2.6
41
- gem "pry"
43
+ if ruby_version < Gem::Version.new("2.5.0")
44
+ # https://github.com/flavorjones/loofah/pull/267
45
+ # loofah changed the required ruby version in a patch so we need to explicitly pin it
46
+ gem "loofah", "2.20.0"
47
+ end
42
48
 
43
49
  gem "benchmark-ips"
44
50
  gem "benchmark_driver"
45
51
  gem "benchmark-ipsa"
46
52
  gem "benchmark-memory"
53
+
54
+ eval_gemfile File.expand_path("../Gemfile", __dir__)
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)
@@ -2,6 +2,8 @@ module Sentry
2
2
  module Rails
3
3
  module ActionCableExtensions
4
4
  class ErrorHandler
5
+ OP_NAME = "websocket.server".freeze
6
+
5
7
  class << self
6
8
  def capture(connection, transaction_name:, extra_context: nil, &block)
7
9
  return block.call unless Sentry.initialized?
@@ -13,13 +15,14 @@ module Sentry
13
15
  Sentry.with_scope do |scope|
14
16
  scope.set_rack_env(env)
15
17
  scope.set_context("action_cable", extra_context) if extra_context
16
- scope.set_transaction_name(transaction_name)
17
- transaction = start_transaction(env, scope.transaction_name)
18
+ scope.set_transaction_name(transaction_name, source: :view)
19
+ transaction = start_transaction(env, scope)
18
20
  scope.set_span(transaction) if transaction
19
21
 
20
22
  begin
21
- block.call
23
+ result = block.call
22
24
  finish_transaction(transaction, 200)
25
+ result
23
26
  rescue Exception => e # rubocop:disable Lint/RescueException
24
27
  Sentry::Rails.capture_exception(e)
25
28
  finish_transaction(transaction, 500)
@@ -29,10 +32,9 @@ module Sentry
29
32
  end
30
33
  end
31
34
 
32
- def start_transaction(env, transaction_name)
33
- sentry_trace = env["HTTP_SENTRY_TRACE"]
34
- options = { name: transaction_name, op: "rails.action_cable".freeze }
35
- transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
35
+ def start_transaction(env, scope)
36
+ options = { name: scope.transaction_name, source: scope.transaction_source, op: OP_NAME }
37
+ transaction = Sentry.continue_trace(env, **options)
36
38
  Sentry.start_transaction(transaction: transaction, **options)
37
39
  end
38
40
 
@@ -5,76 +5,82 @@ module Sentry
5
5
  if !Sentry.initialized? || already_supported_by_sentry_integration?
6
6
  super
7
7
  else
8
- Sentry.with_scope do |scope|
9
- capture_and_reraise_with_sentry(scope) do
10
- super
11
- end
8
+ SentryReporter.record(self) do
9
+ super
12
10
  end
13
11
  end
14
12
  end
15
13
 
16
- def capture_and_reraise_with_sentry(scope, &block)
17
- scope.set_transaction_name(self.class.name)
18
- transaction =
19
- if is_a?(::Sentry::SendEventJob)
20
- nil
21
- else
22
- Sentry.start_transaction(name: scope.transaction_name, op: "active_job")
23
- end
24
-
25
- scope.set_span(transaction) if transaction
14
+ def already_supported_by_sentry_integration?
15
+ Sentry.configuration.rails.skippable_job_adapters.include?(self.class.queue_adapter.class.to_s)
16
+ end
26
17
 
27
- return_value = block.call
18
+ class SentryReporter
19
+ OP_NAME = "queue.active_job".freeze
28
20
 
29
- finish_sentry_transaction(transaction, 200)
21
+ class << self
22
+ def record(job, &block)
23
+ Sentry.with_scope do |scope|
24
+ begin
25
+ scope.set_transaction_name(job.class.name, source: :task)
26
+ transaction =
27
+ if job.is_a?(::Sentry::SendEventJob)
28
+ nil
29
+ else
30
+ Sentry.start_transaction(name: scope.transaction_name, source: scope.transaction_source, op: OP_NAME)
31
+ end
30
32
 
31
- return_value
32
- rescue Exception => e # rubocop:disable Lint/RescueException
33
- finish_sentry_transaction(transaction, 500)
33
+ scope.set_span(transaction) if transaction
34
34
 
35
- Sentry::Rails.capture_exception(
36
- e,
37
- extra: sentry_context,
38
- tags: {
39
- job_id: job_id,
40
- provider_job_id: provider_job_id
41
- }
42
- )
43
- raise e
44
- end
35
+ yield.tap do
36
+ finish_sentry_transaction(transaction, 200)
37
+ end
38
+ rescue Exception => e # rubocop:disable Lint/RescueException
39
+ finish_sentry_transaction(transaction, 500)
45
40
 
46
- def finish_sentry_transaction(transaction, status)
47
- return unless transaction
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
+ )
49
+ raise
50
+ end
51
+ end
52
+ end
48
53
 
49
- transaction.set_http_status(status)
50
- transaction.finish
51
- end
54
+ def finish_sentry_transaction(transaction, status)
55
+ return unless transaction
52
56
 
53
- def already_supported_by_sentry_integration?
54
- Sentry.configuration.rails.skippable_job_adapters.include?(self.class.queue_adapter.class.to_s)
55
- end
57
+ transaction.set_http_status(status)
58
+ transaction.finish
59
+ end
56
60
 
57
- def sentry_context
58
- {
59
- active_job: self.class.name,
60
- arguments: sentry_serialize_arguments(arguments),
61
- scheduled_at: scheduled_at,
62
- job_id: job_id,
63
- provider_job_id: provider_job_id,
64
- locale: locale
65
- }
66
- end
61
+ def sentry_context(job)
62
+ {
63
+ active_job: job.class.name,
64
+ arguments: sentry_serialize_arguments(job.arguments),
65
+ scheduled_at: job.scheduled_at,
66
+ job_id: job.job_id,
67
+ provider_job_id: job.provider_job_id,
68
+ locale: job.locale
69
+ }
70
+ end
67
71
 
68
- def sentry_serialize_arguments(argument)
69
- case argument
70
- when Hash
71
- argument.transform_values { |v| sentry_serialize_arguments(v) }
72
- when Array, Enumerable
73
- argument.map { |v| sentry_serialize_arguments(v) }
74
- when ->(v) { v.respond_to?(:to_global_id) }
75
- argument.to_global_id.to_s rescue argument
76
- else
77
- argument
72
+ def sentry_serialize_arguments(argument)
73
+ case argument
74
+ when Hash
75
+ argument.transform_values { |v| sentry_serialize_arguments(v) }
76
+ when Array, Enumerable
77
+ argument.map { |v| sentry_serialize_arguments(v) }
78
+ when ->(v) { v.respond_to?(:to_global_id) }
79
+ argument.to_global_id.to_s rescue argument
80
+ else
81
+ argument
82
+ end
83
+ end
78
84
  end
79
85
  end
80
86
  end
@@ -1,9 +1,12 @@
1
1
  module Sentry
2
2
  class BackgroundWorker
3
3
  def _perform(&block)
4
- # make sure the background worker returns AR connection if it accidentally acquire one during serialization
5
- ActiveRecord::Base.connection_pool.with_connection do
6
- block.call
4
+ block.call
5
+ ensure
6
+ # some applications have partial or even no AR connection
7
+ if ActiveRecord::Base.connected?
8
+ # make sure the background worker returns AR connection if it accidentally acquire one during serialization
9
+ ActiveRecord::Base.connection_pool.release_connection
7
10
  end
8
11
  end
9
12
  end
@@ -1,20 +1,14 @@
1
- require "sentry/rails/instrument_payload_cleanup_helper"
2
-
3
1
  module Sentry
4
2
  module Rails
5
3
  module Breadcrumb
6
4
  module ActiveSupportLogger
7
5
  class << self
8
- include InstrumentPayloadCleanupHelper
9
-
10
6
  def add(name, started, _finished, _unique_id, data)
11
7
  # skip Rails' internal events
12
8
  return if name.start_with?("!")
13
9
 
14
10
  if data.is_a?(Hash)
15
- # we should only mutate the copy of the data
16
- data = data.dup
17
- cleanup_data(data)
11
+ data = data.slice(*@allowed_keys[name])
18
12
  end
19
13
 
20
14
  crumb = Sentry::Breadcrumb.new(
@@ -25,7 +19,9 @@ module Sentry
25
19
  Sentry.add_breadcrumb(crumb)
26
20
  end
27
21
 
28
- def inject
22
+ def inject(allowed_keys)
23
+ @allowed_keys = allowed_keys
24
+
29
25
  @subscriber = ::ActiveSupport::Notifications.subscribe(/.*/) do |name, started, finished, unique_id, data|
30
26
  # we only record events that has a started timestamp
31
27
  if started.is_a?(Time)
@@ -1,11 +1,13 @@
1
1
  module Sentry
2
2
  module Rails
3
3
  class CaptureExceptions < Sentry::Rack::CaptureExceptions
4
- def initialize(app)
4
+ RAILS_7_1 = Gem::Version.new(::Rails.version) >= Gem::Version.new("7.1.0.alpha")
5
+
6
+ def initialize(_)
5
7
  super
6
8
 
7
- if defined?(::Sprockets::Rails)
8
- @assets_regex = %r(\A/{0,2}#{::Rails.application.config.assets.prefix})
9
+ if Sentry.initialized?
10
+ @assets_regexp = Sentry.configuration.rails.assets_regexp
9
11
  end
10
12
  end
11
13
 
@@ -17,29 +19,37 @@ module Sentry
17
19
  end
18
20
 
19
21
  def transaction_op
20
- "rails.request".freeze
22
+ "http.server".freeze
21
23
  end
22
24
 
23
- def capture_exception(exception)
24
- current_scope = Sentry.get_current_scope
25
+ def capture_exception(exception, env)
26
+ # the exception will be swallowed by ShowExceptions middleware
27
+ return if show_exceptions?(exception, env) && !Sentry.configuration.rails.report_rescued_exceptions
25
28
 
26
- if original_transaction = current_scope.rack_env["sentry.original_transaction"]
27
- current_scope.set_transaction_name(original_transaction)
29
+ Sentry::Rails.capture_exception(exception).tap do |event|
30
+ env[ERROR_EVENT_ID_KEY] = event.event_id if event
28
31
  end
29
-
30
- Sentry::Rails.capture_exception(exception)
31
32
  end
32
33
 
33
34
  def start_transaction(env, scope)
34
- sentry_trace = env["HTTP_SENTRY_TRACE"]
35
- options = { name: scope.transaction_name, op: transaction_op }
35
+ options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
36
36
 
37
- if @assets_regex && scope.transaction_name.match?(@assets_regex)
37
+ if @assets_regexp && scope.transaction_name.match?(@assets_regexp)
38
38
  options.merge!(sampled: false)
39
39
  end
40
40
 
41
- transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
42
- Sentry.start_transaction(transaction: transaction, **options)
41
+ transaction = Sentry.continue_trace(env, **options)
42
+ Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
43
+ end
44
+
45
+ def show_exceptions?(exception, env)
46
+ request = ActionDispatch::Request.new(env)
47
+
48
+ if RAILS_7_1
49
+ ActionDispatch::ExceptionWrapper.new(nil, exception).show?(request)
50
+ else
51
+ request.show_exceptions?
52
+ end
43
53
  end
44
54
  end
45
55
  end
@@ -12,7 +12,12 @@ module Sentry
12
12
  @excluded_exceptions = @excluded_exceptions.concat(Sentry::Rails::IGNORE_DEFAULT)
13
13
 
14
14
  if ::Rails.logger
15
- @logger = ::Rails.logger
15
+ if defined?(::ActiveSupport::BroadcastLogger) && ::Rails.logger.is_a?(::ActiveSupport::BroadcastLogger)
16
+ dupped_broadcasts = ::Rails.logger.broadcasts.map(&:dup)
17
+ @logger = ::ActiveSupport::BroadcastLogger.new(*dupped_broadcasts)
18
+ else
19
+ @logger = ::Rails.logger.dup
20
+ end
16
21
  else
17
22
  @logger.warn(Sentry::LOGGER_PROGNAME) do
18
23
  <<~MSG
@@ -41,7 +46,74 @@ module Sentry
41
46
  'ActionDispatch::Http::Parameters::ParseError',
42
47
  'ActiveRecord::RecordNotFound'
43
48
  ].freeze
49
+
50
+ ACTIVE_SUPPORT_LOGGER_SUBSCRIPTION_ITEMS_DEFAULT = {
51
+ # action_controller
52
+ "write_fragment.action_controller" => %i[key],
53
+ "read_fragment.action_controller" => %i[key],
54
+ "exist_fragment?.action_controller" => %i[key],
55
+ "expire_fragment.action_controller" => %i[key],
56
+ "start_processing.action_controller" => %i[controller action params format method path],
57
+ "process_action.action_controller" => %i[controller action params format method path status view_runtime db_runtime],
58
+ "send_file.action_controller" => %i[path],
59
+ "redirect_to.action_controller" => %i[status location],
60
+ "halted_callback.action_controller" => %i[filter],
61
+ # action_dispatch
62
+ "process_middleware.action_dispatch" => %i[middleware],
63
+ # action_view
64
+ "render_template.action_view" => %i[identifier layout],
65
+ "render_partial.action_view" => %i[identifier],
66
+ "render_collection.action_view" => %i[identifier count cache_hits],
67
+ "render_layout.action_view" => %i[identifier],
68
+ # active_record
69
+ "sql.active_record" => %i[sql name statement_name cached],
70
+ "instantiation.active_record" => %i[record_count class_name],
71
+ # action_mailer
72
+ # not including to, from, or subject..etc. because of PII concern
73
+ "deliver.action_mailer" => %i[mailer date perform_deliveries],
74
+ "process.action_mailer" => %i[mailer action params],
75
+ # active_support
76
+ "cache_read.active_support" => %i[key store hit],
77
+ "cache_generate.active_support" => %i[key store],
78
+ "cache_fetch_hit.active_support" => %i[key store],
79
+ "cache_write.active_support" => %i[key store],
80
+ "cache_delete.active_support" => %i[key store],
81
+ "cache_exist?.active_support" => %i[key store],
82
+ # active_job
83
+ "enqueue_at.active_job" => %i[],
84
+ "enqueue.active_job" => %i[],
85
+ "enqueue_retry.active_job" => %i[],
86
+ "perform_start.active_job" => %i[],
87
+ "perform.active_job" => %i[],
88
+ "retry_stopped.active_job" => %i[],
89
+ "discard.active_job" => %i[],
90
+ # action_cable
91
+ "perform_action.action_cable" => %i[channel_class action],
92
+ "transmit.action_cable" => %i[channel_class],
93
+ "transmit_subscription_confirmation.action_cable" => %i[channel_class],
94
+ "transmit_subscription_rejection.action_cable" => %i[channel_class],
95
+ "broadcast.action_cable" => %i[broadcasting],
96
+ # active_storage
97
+ "service_upload.active_storage" => %i[service key checksum],
98
+ "service_streaming_download.active_storage" => %i[service key],
99
+ "service_download_chunk.active_storage" => %i[service key],
100
+ "service_download.active_storage" => %i[service key],
101
+ "service_delete.active_storage" => %i[service key],
102
+ "service_delete_prefixed.active_storage" => %i[service prefix],
103
+ "service_exist.active_storage" => %i[service key exist],
104
+ "service_url.active_storage" => %i[service key url],
105
+ "service_update_metadata.active_storage" => %i[service key],
106
+ "preview.active_storage" => %i[key],
107
+ "analyze.active_storage" => %i[analyzer],
108
+ }.freeze
109
+
44
110
  class Configuration
111
+ # Rails 7.0 introduced a new error reporter feature, which the SDK once opted-in by default.
112
+ # But after receiving multiple issue reports, the integration seemed to cause serious troubles to some users.
113
+ # So the integration is now controlled by this configuration, which is disabled (false) by default.
114
+ # More information can be found from: https://github.com/rails/rails/pull/43625#issuecomment-1072514175
115
+ attr_accessor :register_error_subscriber
116
+
45
117
  # Rails catches exceptions in the ActionDispatch::ShowExceptions or
46
118
  # ActionDispatch::DebugExceptions middlewares, depending on the environment.
47
119
  # When `rails_report_rescued_exceptions` is true (it is by default), Sentry
@@ -54,15 +126,38 @@ module Sentry
54
126
 
55
127
  attr_accessor :tracing_subscribers
56
128
 
129
+ # sentry-rails by default skips asset request' transactions by checking if the path matches
130
+ #
131
+ # ```rb
132
+ # %r(\A/{0,2}#{::Rails.application.config.assets.prefix})
133
+ # ```
134
+ #
135
+ # If you want to use a different pattern, you can configure the `assets_regexp` option like:
136
+ #
137
+ # ```rb
138
+ # Sentry.init do |config|
139
+ # config.rails.assets_regexp = /my_regexp/
140
+ # end
141
+ # ```
142
+ attr_accessor :assets_regexp
143
+
144
+ # Hash of subscription items that will be shown in breadcrumbs active support logger.
145
+ # @return [Hash<String, Array<Symbol>>]
146
+ attr_accessor :active_support_logger_subscription_items
147
+
57
148
  def initialize
149
+ @register_error_subscriber = false
58
150
  @report_rescued_exceptions = true
59
151
  @skippable_job_adapters = []
152
+ @assets_regexp = if defined?(::Sprockets::Rails)
153
+ %r(\A/{0,2}#{::Rails.application.config.assets.prefix})
154
+ end
60
155
  @tracing_subscribers = Set.new([
61
- Sentry::Rails::Tracing::ActionControllerSubscriber,
62
156
  Sentry::Rails::Tracing::ActionViewSubscriber,
63
157
  Sentry::Rails::Tracing::ActiveRecordSubscriber,
64
158
  Sentry::Rails::Tracing::ActiveStorageSubscriber
65
159
  ])
160
+ @active_support_logger_subscription_items = Sentry::Rails::ACTIVE_SUPPORT_LOGGER_SUBSCRIPTION_ITEMS_DEFAULT.dup
66
161
  end
67
162
  end
68
163
  end
@@ -2,8 +2,34 @@ module Sentry
2
2
  module Rails
3
3
  module ControllerTransaction
4
4
  def self.included(base)
5
- base.prepend_before_action do |controller|
6
- Sentry.get_current_scope.set_transaction_name("#{controller.class}##{controller.action_name}")
5
+ base.prepend_around_action(:sentry_around_action)
6
+ end
7
+
8
+ private
9
+
10
+ def sentry_around_action
11
+ if Sentry.initialized?
12
+ transaction_name = "#{self.class}##{action_name}"
13
+ Sentry.get_current_scope.set_transaction_name(transaction_name, source: :view)
14
+ Sentry.with_child_span(op: "view.process_action.action_controller", description: transaction_name) do |child_span|
15
+ if child_span
16
+ begin
17
+ result = yield
18
+ ensure
19
+ child_span.set_http_status(response.status)
20
+ child_span.set_data(:format, request.format)
21
+ child_span.set_data(:method, request.method)
22
+ child_span.set_data(:path, request.path)
23
+ child_span.set_data(:params, request.params)
24
+ end
25
+
26
+ result
27
+ else
28
+ yield
29
+ end
30
+ end
31
+ else
32
+ yield
7
33
  end
8
34
  end
9
35
  end
@@ -1,19 +1,25 @@
1
1
  module Sentry
2
2
  module Rails
3
3
  # This is not a user-facing class. You should use it with Rails 7.0's error reporter feature and its interfaces.
4
- # See https://github.com/rails/rails/blob/main/activesupport/lib/active_support/error_reporter.rb for more information.
4
+ # See https://github.com/rails/rails/blob/main/activesupport/lib/active_support/error_reporter.rb to learn more about reporting APIs.
5
+ # If you want Sentry to subscribe to the error reporter, please set `config.rails.register_error_subscriber` to `true`.
5
6
  class ErrorSubscriber
6
- def report(error, handled:, severity:, context:)
7
- # a component may already have an integration to capture exceptions while its operation is also wrapped inside an `app.executor.wrap` (e.g. ActionCable)
8
- # in such condition, the exception would be captured repeatedly. it usually happens in this order:
9
- #
10
- # 1. exception captured and reported by the component integration and re-raised
11
- # 2. exception captured by the executor, which then reports it with executor.error_reporter
12
- #
13
- # and because there's no direct communication between the 2 callbacks, we need a way to identify if an exception has been captured before
14
- # using a Sentry-specific intance variable should be the last impactful way
15
- return if error.instance_variable_get(:@__sentry_captured)
16
- Sentry::Rails.capture_exception(error, level: severity, contexts: { "rails.error" => context }, tags: { handled: handled })
7
+ SKIP_SOURCES = Regexp.union([/.*_cache_store.active_support/])
8
+
9
+ def report(error, handled:, severity:, context:, source: nil)
10
+ tags = { handled: handled }
11
+
12
+ if source
13
+ return if SKIP_SOURCES.match?(source)
14
+ tags[:source] = source
15
+ end
16
+
17
+ if context[:tags].is_a?(Hash)
18
+ context = context.dup
19
+ tags.merge!(context.delete(:tags))
20
+ end
21
+
22
+ Sentry::Rails.capture_exception(error, level: severity, contexts: { "rails.error" => context }, tags: tags)
17
23
  end
18
24
  end
19
25
  end
@@ -40,6 +40,7 @@ module Sentry
40
40
 
41
41
  configure_project_root
42
42
  configure_trusted_proxies
43
+ configure_cron_timezone
43
44
  extend_controller_methods if defined?(ActionController)
44
45
  patch_background_worker if defined?(ActiveRecord)
45
46
  override_streaming_reporter if defined?(ActionView)
@@ -47,12 +48,19 @@ module Sentry
47
48
  inject_breadcrumbs_logger
48
49
  activate_tracing
49
50
 
50
- register_error_subscriber(app) if ::Rails.version.to_f >= 7.0
51
+ register_error_subscriber(app) if ::Rails.version.to_f >= 7.0 && Sentry.configuration.rails.register_error_subscriber
51
52
  end
52
53
 
53
54
  runner do
54
55
  next unless Sentry.initialized?
55
56
  Sentry.configuration.background_worker_threads = 0
57
+
58
+ at_exit do
59
+ # TODO: Add a condition for Rails 7.1 to avoid confliction with https://github.com/rails/rails/pull/44999
60
+ if $ERROR_INFO && !($ERROR_INFO.is_a?(SystemExit) && $ERROR_INFO.success?)
61
+ Sentry::Rails.capture_exception($ERROR_INFO, tags: { source: "runner" })
62
+ end
63
+ end
56
64
  end
57
65
 
58
66
  def configure_project_root
@@ -63,6 +71,11 @@ module Sentry
63
71
  Sentry.configuration.trusted_proxies += Array(::Rails.application.config.action_dispatch.trusted_proxies)
64
72
  end
65
73
 
74
+ def configure_cron_timezone
75
+ tz_info = ::ActiveSupport::TimeZone.find_tzinfo(::Rails.application.config.time_zone)
76
+ Sentry.configuration.cron.default_timezone = tz_info.name
77
+ end
78
+
66
79
  def extend_controller_methods
67
80
  require "sentry/rails/controller_methods"
68
81
  require "sentry/rails/controller_transaction"
@@ -82,7 +95,7 @@ module Sentry
82
95
  def inject_breadcrumbs_logger
83
96
  if Sentry.configuration.breadcrumbs_logger.include?(:active_support_logger)
84
97
  require 'sentry/rails/breadcrumb/active_support_logger'
85
- Sentry::Rails::Breadcrumb::ActiveSupportLogger.inject
98
+ Sentry::Rails::Breadcrumb::ActiveSupportLogger.inject(Sentry.configuration.rails.active_support_logger_subscription_items)
86
99
  end
87
100
 
88
101
  if Sentry.configuration.breadcrumbs_logger.include?(:monotonic_active_support_logger)
@@ -110,7 +123,7 @@ module Sentry
110
123
  end
111
124
 
112
125
  def activate_tracing
113
- if Sentry.configuration.tracing_enabled?
126
+ if Sentry.configuration.tracing_enabled? && Sentry.configuration.instrumenter == :sentry
114
127
  subscribers = Sentry.configuration.rails.tracing_subscribers
115
128
  Sentry::Rails::Tracing.register_subscribers(subscribers)
116
129
  Sentry::Rails::Tracing.subscribe_tracing_events
@@ -40,15 +40,12 @@ module Sentry
40
40
  def record_on_current_span(duration:, **options)
41
41
  return unless options[:start_timestamp]
42
42
 
43
- scope = Sentry.get_current_scope
44
- transaction = scope.get_transaction
45
- return unless transaction && transaction.sampled
46
-
47
- span = transaction.start_child(**options)
48
- # duration in ActiveSupport is computed in millisecond
49
- # so we need to covert it as second before calculating the timestamp
50
- span.set_timestamp(span.start_timestamp + duration / 1000)
51
- yield(span) if block_given?
43
+ Sentry.with_child_span(**options) do |child_span|
44
+ # duration in ActiveSupport is computed in millisecond
45
+ # so we need to covert it as second before calculating the timestamp
46
+ child_span.set_timestamp(child_span.start_timestamp + duration / 1000)
47
+ yield(child_span) if block_given?
48
+ end
52
49
  end
53
50
  end
54
51
  end
@@ -8,14 +8,20 @@ module Sentry
8
8
  extend InstrumentPayloadCleanupHelper
9
9
 
10
10
  EVENT_NAMES = ["process_action.action_controller"].freeze
11
+ OP_NAME = "view.process_action.action_controller".freeze
11
12
 
12
13
  def self.subscribe!
14
+ Sentry.logger.warn <<~MSG
15
+ DEPRECATION WARNING: sentry-rails has changed its approach on controller span recording and #{self.name} is now depreacted.
16
+ Please stop using or referencing #{self.name} as it will be removed in the next major release.
17
+ MSG
18
+
13
19
  subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload|
14
20
  controller = payload[:controller]
15
21
  action = payload[:action]
16
22
 
17
23
  record_on_current_span(
18
- op: event_name,
24
+ op: OP_NAME,
19
25
  start_timestamp: payload[START_TIMESTAMP_NAME],
20
26
  description: "#{controller}##{action}",
21
27
  duration: duration
@@ -13,7 +13,33 @@ module Sentry
13
13
  next if EXCLUDED_EVENTS.include? payload[:name]
14
14
 
15
15
  record_on_current_span(op: SPAN_PREFIX + event_name, start_timestamp: payload[START_TIMESTAMP_NAME], description: payload[:sql], duration: duration) do |span|
16
- span.set_data(:connection_id, payload[:connection_id])
16
+ span.set_tag(:cached, true) if payload.fetch(:cached, false) # cached key is only set for hits in the QueryCache, from Rails 5.1
17
+
18
+ connection = payload[:connection]
19
+
20
+ if payload[:connection_id]
21
+ span.set_data(:connection_id, payload[:connection_id])
22
+
23
+ # we fallback to the base connection on rails < 6.0.0 since the payload doesn't have it
24
+ base_connection = ActiveRecord::Base.connection
25
+ connection ||= base_connection if payload[:connection_id] == base_connection.object_id
26
+ end
27
+
28
+ next unless connection
29
+
30
+ db_config = if connection.pool.respond_to?(:db_config)
31
+ connection.pool.db_config.configuration_hash
32
+ elsif connection.pool.respond_to?(:spec)
33
+ connection.pool.spec.config
34
+ end
35
+
36
+ next unless db_config
37
+
38
+ span.set_data(Span::DataConventions::DB_SYSTEM, db_config[:adapter]) if db_config[:adapter]
39
+ span.set_data(Span::DataConventions::DB_NAME, db_config[:database]) if db_config[:database]
40
+ span.set_data(Span::DataConventions::SERVER_ADDRESS, db_config[:host]) if db_config[:host]
41
+ span.set_data(Span::DataConventions::SERVER_PORT, db_config[:port]) if db_config[:port]
42
+ span.set_data(Span::DataConventions::SERVER_SOCKET_ADDRESS, db_config[:socket]) if db_config[:socket]
17
43
  end
18
44
  end
19
45
  end
@@ -21,7 +21,7 @@ module Sentry
21
21
 
22
22
  def self.subscribe!
23
23
  subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload|
24
- record_on_current_span(op: event_name, start_timestamp: payload[START_TIMESTAMP_NAME], description: payload[:service], duration: duration) do |span|
24
+ record_on_current_span(op: "file.#{event_name}".freeze, start_timestamp: payload[START_TIMESTAMP_NAME], description: payload[:service], duration: duration) do |span|
25
25
  payload.each do |key, value|
26
26
  span.set_data(key, value) unless key == START_TIMESTAMP_NAME
27
27
  end
@@ -67,7 +67,7 @@ module Sentry
67
67
  end
68
68
 
69
69
  def self.get_current_transaction
70
- Sentry.get_current_scope.get_transaction
70
+ Sentry.get_current_scope.get_transaction if Sentry.initialized?
71
71
  end
72
72
 
73
73
  # it's just a container for the extended method
@@ -1,5 +1,5 @@
1
1
  module Sentry
2
2
  module Rails
3
- VERSION = "5.1.0"
3
+ VERSION = "5.16.1"
4
4
  end
5
5
  end
data/sentry-rails.gemspec CHANGED
@@ -23,5 +23,5 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ["lib"]
24
24
 
25
25
  spec.add_dependency "railties", ">= 5.0"
26
- spec.add_dependency "sentry-ruby-core", "~> 5.1.0"
26
+ spec.add_dependency "sentry-ruby", "~> 5.16.1"
27
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0
4
+ version: 5.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-10 00:00:00.000000000 Z
11
+ date: 2024-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: sentry-ruby-core
28
+ name: sentry-ruby
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 5.1.0
33
+ version: 5.16.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 5.1.0
40
+ version: 5.16.1
41
41
  description: A gem that provides Rails integration for the Sentry error logger
42
42
  email: accounts@sentry.io
43
43
  executables: []
@@ -49,7 +49,6 @@ files:
49
49
  - ".gitignore"
50
50
  - ".rspec"
51
51
  - CHANGELOG.md
52
- - CODE_OF_CONDUCT.md
53
52
  - Gemfile
54
53
  - LICENSE.txt
55
54
  - Makefile
data/CODE_OF_CONDUCT.md DELETED
@@ -1,74 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at stan001212@gmail.com. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [https://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: https://contributor-covenant.org
74
- [version]: https://contributor-covenant.org/version/1/4/