sentry-rails 5.17.3 → 5.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d004cdd6b80d76424d8684494a6a0565b5f5440ada51a6878280baa0a0c174e7
4
- data.tar.gz: 747ed1691526ff296e90d697ab61bf6f6abfe5c63687904f7a1a12afa9a833d6
3
+ metadata.gz: f37e46eaedbb7739f9def9819546a7f4b1457c7fefd076bb394104fdf18910c1
4
+ data.tar.gz: f7360c8d456797deef74837c24ee44ae7a5fd875cc602b83382ac345d6498c44
5
5
  SHA512:
6
- metadata.gz: dec86deec8fd26222ddc97c7568d471bdea6c75caaae49b4793f37e5235d81537b026e336136ad3b734101b15f16ebeeeb546d99c6255a4a93ed305ef758be74
7
- data.tar.gz: b0ff142977ad7ece0e73a40cd3f434a2bb967efb6cc2040e5feb9448cf3963acfa535efb14d095efaf2f235923c3780f2f1a532cb9287344774e33b955618263
6
+ metadata.gz: c5ec2e4609761381f9414e88c58559086dae1fb177d8f7b9a91e52bce88b3ea82f9f201d4aabd498e39d52f91637a7a1d5778f726d91afa1138f429136a96294
7
+ data.tar.gz: e58d7d66bb42a9dc0ff8679d7667c9c9bcb1fe268313c1fced2a6bd8713bf04b2dd95d407547b95052a8f57a9e93a393ace8e03108c9d9491bbc727414983944
data/Gemfile CHANGED
@@ -17,8 +17,11 @@ rails_version = Gem::Version.new(rails_version)
17
17
  if rails_version < Gem::Version.new("6.0.0")
18
18
  gem "sqlite3", "~> 1.3.0", platform: :ruby
19
19
  else
20
- # 1.7.0 dropped support for ruby < 3.0, remove later after upgrading craft setup
21
- gem "sqlite3", "1.6.9", platform: :ruby
20
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
21
+ gem "sqlite3", "~> 1.7.3", platform: :ruby
22
+ else
23
+ gem "sqlite3", "~> 1.6.9", platform: :ruby
24
+ end
22
25
  end
23
26
 
24
27
  if rails_version >= Gem::Version.new("7.2.0.alpha")
@@ -0,0 +1,19 @@
1
+ require "rails/generators/base"
2
+
3
+ class SentryGenerator < ::Rails::Generators::Base
4
+ class_option :dsn, type: :string, desc: "Sentry DSN"
5
+
6
+ def copy_initializer_file
7
+ dsn = options[:dsn] ? "'#{options[:dsn]}'" : "ENV['SENTRY_DSN']"
8
+
9
+ create_file "config/initializers/sentry.rb", <<~RUBY
10
+ # frozen_string_literal: true
11
+
12
+ Sentry.init do |config|
13
+ config.breadcrumbs_logger = [:active_support_logger]
14
+ config.dsn = #{dsn}
15
+ config.enable_tracing = true
16
+ end
17
+ RUBY
18
+ end
19
+ end
@@ -3,6 +3,7 @@ module Sentry
3
3
  module ActionCableExtensions
4
4
  class ErrorHandler
5
5
  OP_NAME = "websocket.server".freeze
6
+ SPAN_ORIGIN = "auto.http.rails.actioncable"
6
7
 
7
8
  class << self
8
9
  def capture(connection, transaction_name:, extra_context: nil, &block)
@@ -33,7 +34,13 @@ module Sentry
33
34
  end
34
35
 
35
36
  def start_transaction(env, scope)
36
- options = { name: scope.transaction_name, source: scope.transaction_source, op: OP_NAME }
37
+ options = {
38
+ name: scope.transaction_name,
39
+ source: scope.transaction_source,
40
+ op: OP_NAME,
41
+ origin: SPAN_ORIGIN
42
+ }
43
+
37
44
  transaction = Sentry.continue_trace(env, **options)
38
45
  Sentry.start_transaction(transaction: transaction, **options)
39
46
  end
@@ -17,6 +17,7 @@ module Sentry
17
17
 
18
18
  class SentryReporter
19
19
  OP_NAME = "queue.active_job".freeze
20
+ SPAN_ORIGIN = "auto.queue.active_job".freeze
20
21
 
21
22
  class << self
22
23
  def record(job, &block)
@@ -27,7 +28,12 @@ module Sentry
27
28
  if job.is_a?(::Sentry::SendEventJob)
28
29
  nil
29
30
  else
30
- Sentry.start_transaction(name: scope.transaction_name, source: scope.transaction_source, op: OP_NAME)
31
+ Sentry.start_transaction(
32
+ name: scope.transaction_name,
33
+ source: scope.transaction_source,
34
+ op: OP_NAME,
35
+ origin: SPAN_ORIGIN
36
+ )
31
37
  end
32
38
 
33
39
  scope.set_span(transaction) if transaction
@@ -2,6 +2,7 @@ module Sentry
2
2
  module Rails
3
3
  class CaptureExceptions < Sentry::Rack::CaptureExceptions
4
4
  RAILS_7_1 = Gem::Version.new(::Rails.version) >= Gem::Version.new("7.1.0.alpha")
5
+ SPAN_ORIGIN = 'auto.http.rails'.freeze
5
6
 
6
7
  def initialize(_)
7
8
  super
@@ -32,7 +33,12 @@ module Sentry
32
33
  end
33
34
 
34
35
  def start_transaction(env, scope)
35
- options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
36
+ options = {
37
+ name: scope.transaction_name,
38
+ source: scope.transaction_source,
39
+ op: transaction_op,
40
+ origin: SPAN_ORIGIN
41
+ }
36
42
 
37
43
  if @assets_regexp && scope.transaction_name.match?(@assets_regexp)
38
44
  options.merge!(sampled: false)
@@ -126,6 +126,14 @@ module Sentry
126
126
 
127
127
  attr_accessor :tracing_subscribers
128
128
 
129
+ # When the ActiveRecordSubscriber is enabled, capture the source location of the query in the span data.
130
+ # This is enabled by default, but can be disabled by setting this to false.
131
+ attr_accessor :enable_db_query_source
132
+
133
+ # The threshold in milliseconds for the ActiveRecordSubscriber to capture the source location of the query
134
+ # in the span data. Default is 100ms.
135
+ attr_accessor :db_query_source_threshold_ms
136
+
129
137
  # sentry-rails by default skips asset request' transactions by checking if the path matches
130
138
  #
131
139
  # ```rb
@@ -157,6 +165,8 @@ module Sentry
157
165
  Sentry::Rails::Tracing::ActiveRecordSubscriber,
158
166
  Sentry::Rails::Tracing::ActiveStorageSubscriber
159
167
  ])
168
+ @enable_db_query_source = true
169
+ @db_query_source_threshold_ms = 100
160
170
  @active_support_logger_subscription_items = Sentry::Rails::ACTIVE_SUPPORT_LOGGER_SUBSCRIPTION_ITEMS_DEFAULT.dup
161
171
  end
162
172
  end
@@ -1,6 +1,8 @@
1
1
  module Sentry
2
2
  module Rails
3
3
  module ControllerTransaction
4
+ SPAN_ORIGIN = 'auto.view.rails'.freeze
5
+
4
6
  def self.included(base)
5
7
  base.prepend_around_action(:sentry_around_action)
6
8
  end
@@ -11,7 +13,7 @@ module Sentry
11
13
  if Sentry.initialized?
12
14
  transaction_name = "#{self.class}##{action_name}"
13
15
  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|
16
+ Sentry.with_child_span(op: "view.process_action.action_controller", description: transaction_name, origin: SPAN_ORIGIN) do |child_span|
15
17
  if child_span
16
18
  begin
17
19
  result = yield
@@ -9,6 +9,7 @@ module Sentry
9
9
 
10
10
  EVENT_NAMES = ["process_action.action_controller"].freeze
11
11
  OP_NAME = "view.process_action.action_controller".freeze
12
+ SPAN_ORIGIN = "auto.view.rails".freeze
12
13
 
13
14
  def self.subscribe!
14
15
  Sentry.logger.warn <<~MSG
@@ -22,6 +23,7 @@ module Sentry
22
23
 
23
24
  record_on_current_span(
24
25
  op: OP_NAME,
26
+ origin: SPAN_ORIGIN,
25
27
  start_timestamp: payload[START_TIMESTAMP_NAME],
26
28
  description: "#{controller}##{action}",
27
29
  duration: duration
@@ -6,10 +6,17 @@ module Sentry
6
6
  class ActionViewSubscriber < AbstractSubscriber
7
7
  EVENT_NAMES = ["render_template.action_view"].freeze
8
8
  SPAN_PREFIX = "template.".freeze
9
+ SPAN_ORIGIN = "auto.template.rails".freeze
9
10
 
10
11
  def self.subscribe!
11
12
  subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload|
12
- record_on_current_span(op: SPAN_PREFIX + event_name, start_timestamp: payload[START_TIMESTAMP_NAME], description: payload[:identifier], duration: duration)
13
+ record_on_current_span(
14
+ op: SPAN_PREFIX + event_name,
15
+ origin: SPAN_ORIGIN,
16
+ start_timestamp: payload[START_TIMESTAMP_NAME],
17
+ description: payload[:identifier],
18
+ duration: duration
19
+ )
13
20
  end
14
21
  end
15
22
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "sentry/rails/tracing/abstract_subscriber"
2
4
 
3
5
  module Sentry
@@ -5,41 +7,95 @@ module Sentry
5
7
  module Tracing
6
8
  class ActiveRecordSubscriber < AbstractSubscriber
7
9
  EVENT_NAMES = ["sql.active_record"].freeze
8
- SPAN_PREFIX = "db.".freeze
10
+ SPAN_PREFIX = "db."
11
+ SPAN_ORIGIN = "auto.db.rails"
9
12
  EXCLUDED_EVENTS = ["SCHEMA", "TRANSACTION"].freeze
10
13
 
11
- def self.subscribe!
12
- subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload|
13
- next if EXCLUDED_EVENTS.include? payload[:name]
14
+ SUPPORT_SOURCE_LOCATION = ActiveSupport::BacktraceCleaner.method_defined?(:clean_frame)
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_tag(:cached, true) if payload.fetch(:cached, false) # cached key is only set for hits in the QueryCache, from Rails 5.1
16
+ if SUPPORT_SOURCE_LOCATION
17
+ class_attribute :backtrace_cleaner, default: (ActiveSupport::BacktraceCleaner.new.tap do |cleaner|
18
+ cleaner.add_silencer { |line| line.include?("sentry-ruby/lib") || line.include?("sentry-rails/lib") }
19
+ end)
20
+ end
17
21
 
18
- connection = payload[:connection]
22
+ class << self
23
+ def subscribe!
24
+ record_query_source = SUPPORT_SOURCE_LOCATION && Sentry.configuration.rails.enable_db_query_source
25
+ query_source_threshold = Sentry.configuration.rails.db_query_source_threshold_ms
19
26
 
20
- if payload[:connection_id]
21
- span.set_data(:connection_id, payload[:connection_id])
27
+ subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload|
28
+ next if EXCLUDED_EVENTS.include? payload[:name]
22
29
 
23
- # we fallback to the base connection on rails < 6.0.0 since the payload doesn't have it
24
- connection ||= ActiveRecord::Base.connection_pool.connections.find { |conn| conn.object_id == payload[:connection_id] }
25
- end
30
+ record_on_current_span(
31
+ op: SPAN_PREFIX + event_name,
32
+ origin: SPAN_ORIGIN,
33
+ start_timestamp: payload[START_TIMESTAMP_NAME],
34
+ description: payload[:sql],
35
+ duration: duration
36
+ ) do |span|
37
+ span.set_tag(:cached, true) if payload.fetch(:cached, false) # cached key is only set for hits in the QueryCache, from Rails 5.1
26
38
 
27
- next unless connection
39
+ connection = payload[:connection]
28
40
 
29
- db_config =
30
- 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
41
+ if payload[:connection_id]
42
+ span.set_data(:connection_id, payload[:connection_id])
43
+
44
+ # we fallback to the base connection on rails < 6.0.0 since the payload doesn't have it
45
+ connection ||= ActiveRecord::Base.connection_pool.connections.find { |conn| conn.object_id == payload[:connection_id] }
34
46
  end
35
47
 
36
- next unless db_config
48
+ next unless connection
49
+
50
+ db_config =
51
+ if connection.pool.respond_to?(:db_config)
52
+ connection.pool.db_config.configuration_hash
53
+ elsif connection.pool.respond_to?(:spec)
54
+ connection.pool.spec.config
55
+ end
56
+
57
+ next unless db_config
58
+
59
+ span.set_data(Span::DataConventions::DB_SYSTEM, db_config[:adapter]) if db_config[:adapter]
60
+ span.set_data(Span::DataConventions::DB_NAME, db_config[:database]) if db_config[:database]
61
+ span.set_data(Span::DataConventions::SERVER_ADDRESS, db_config[:host]) if db_config[:host]
62
+ span.set_data(Span::DataConventions::SERVER_PORT, db_config[:port]) if db_config[:port]
63
+ span.set_data(Span::DataConventions::SERVER_SOCKET_ADDRESS, db_config[:socket]) if db_config[:socket]
64
+
65
+ next unless record_query_source
66
+
67
+ # both duration and query_source_threshold are in ms
68
+ next unless duration >= query_source_threshold
37
69
 
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]
70
+ source_location = query_source_location
71
+
72
+ if source_location
73
+ backtrace_line = Sentry::Backtrace::Line.parse(source_location)
74
+ span.set_data(Span::DataConventions::FILEPATH, backtrace_line.file) if backtrace_line.file
75
+ span.set_data(Span::DataConventions::LINENO, backtrace_line.number) if backtrace_line.number
76
+ span.set_data(Span::DataConventions::FUNCTION, backtrace_line.method) if backtrace_line.method
77
+ # Only JRuby has namespace in the backtrace
78
+ span.set_data(Span::DataConventions::NAMESPACE, backtrace_line.module_name) if backtrace_line.module_name
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # Thread.each_caller_location is an API added in Ruby 3.2 that doesn't always collect the entire stack like
85
+ # Kernel#caller or #caller_locations do. See https://github.com/rails/rails/pull/49095 for more context.
86
+ if SUPPORT_SOURCE_LOCATION && Thread.respond_to?(:each_caller_location)
87
+ def query_source_location
88
+ Thread.each_caller_location do |location|
89
+ frame = backtrace_cleaner.clean_frame(location)
90
+ return frame if frame
91
+ end
92
+ nil
93
+ end
94
+ else
95
+ # Since Sentry is mostly used in production, we don't want to fallback to the slower implementation
96
+ # and adds potentially big overhead to the application.
97
+ def query_source_location
98
+ nil
43
99
  end
44
100
  end
45
101
  end
@@ -19,9 +19,17 @@ module Sentry
19
19
  analyze.active_storage
20
20
  ].freeze
21
21
 
22
+ SPAN_ORIGIN = "auto.file.rails".freeze
23
+
22
24
  def self.subscribe!
23
25
  subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload|
24
- record_on_current_span(op: "file.#{event_name}".freeze, start_timestamp: payload[START_TIMESTAMP_NAME], description: payload[:service], duration: duration) do |span|
26
+ record_on_current_span(
27
+ op: "file.#{event_name}".freeze,
28
+ origin: SPAN_ORIGIN,
29
+ start_timestamp: payload[START_TIMESTAMP_NAME],
30
+ description: payload[:service],
31
+ duration: duration
32
+ ) do |span|
25
33
  payload.each do |key, value|
26
34
  span.set_data(key, value) unless key == START_TIMESTAMP_NAME
27
35
  end
@@ -1,5 +1,5 @@
1
1
  module Sentry
2
2
  module Rails
3
- VERSION = "5.17.3"
3
+ VERSION = "5.18.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", "~> 5.17.3"
26
+ spec.add_dependency "sentry-ruby", "~> 5.18.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.17.3
4
+ version: 5.18.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: 2024-04-11 00:00:00.000000000 Z
11
+ date: 2024-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 5.17.3
33
+ version: 5.18.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.17.3
40
+ version: 5.18.1
41
41
  description: A gem that provides Rails integration for the Sentry error logger
42
42
  email: accounts@sentry.io
43
43
  executables: []
@@ -57,6 +57,7 @@ files:
57
57
  - app/jobs/sentry/send_event_job.rb
58
58
  - bin/console
59
59
  - bin/setup
60
+ - lib/generators/sentry_generator.rb
60
61
  - lib/sentry-rails.rb
61
62
  - lib/sentry/rails.rb
62
63
  - lib/sentry/rails/action_cable.rb
@@ -105,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
106
  - !ruby/object:Gem::Version
106
107
  version: '0'
107
108
  requirements: []
108
- rubygems_version: 3.1.6
109
+ rubygems_version: 3.5.11
109
110
  signing_key:
110
111
  specification_version: 4
111
112
  summary: A gem that provides Rails integration for the Sentry error logger