sentry-rails 5.26.0 → 5.27.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8136167d60f6ee67885cdde939a5aa1e29f0e9f9bc1bd64b0ff4aecc29a477a9
4
- data.tar.gz: d03846dced0b11fbe03df3b3386c01e2d4ff84d5cfc64dcaf33612f82f2de69a
3
+ metadata.gz: 15c5cac1232851c12adb474d437c228335f2c23b3f9f65e9000d769b6265d52d
4
+ data.tar.gz: fe44e7fe542de216cf8eb97bef21a72f058c62e862dec51edbbd244a98cd21aa
5
5
  SHA512:
6
- metadata.gz: '0900b1dd825499e01d9a5ce77c71bc7821581420abbb60ea8123fcdafed8d7fd9db53b8fb31bdcbbe445e95583c9b477b47f15bdeb58bc3988c54289f328d1bb'
7
- data.tar.gz: 356eb3d7a526cb1bcd4ee56c3b32201fbbbfe58b0f6ea2cb8152fd7d732964522dea8bc83e08411271fc531c92a5cc9185b2a17dfd50d06153a1ca0f9a6ba87a
6
+ metadata.gz: ed7cc92a5d90ce2d251f1afaeaa1a08cc5e4af878f4b254b3a7bcf369e1353af3acba97228cec5c86d20f63e354b44860bc4a8120f1577dd66c01fb2d75600d7
7
+ data.tar.gz: 30b11a5ed39d0351a91348938d4aa6a2a7625c28e55293c591087747e71e2488b086967f6e811a5281292a332b1f66b3e94e18ed7e7783eab50f9d1408740a3b
data/Gemfile CHANGED
@@ -3,7 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
  git_source(:github) { |name| "https://github.com/#{name}.git" }
5
5
 
6
- eval_gemfile "../Gemfile"
6
+ eval_gemfile "../Gemfile.dev"
7
7
 
8
8
  # Specify your gem's dependencies in sentry-ruby.gemspec
9
9
  gemspec
@@ -16,8 +16,18 @@ end
16
16
 
17
17
  ruby_version = Gem::Version.new(RUBY_VERSION)
18
18
 
19
- rails_version = ENV["RAILS_VERSION"]
20
- rails_version = "8.0.0" if rails_version.nil?
19
+ rails_version = ENV.fetch("RAILS_VERSION") do
20
+ if ruby_version >= Gem::Version.new("3.2")
21
+ "8.0"
22
+ elsif ruby_version >= Gem::Version.new("3.1")
23
+ "7.2"
24
+ elsif ruby_version >= Gem::Version.new("2.7")
25
+ "7.1"
26
+ elsif ruby_version >= Gem::Version.new("2.4")
27
+ "5.2"
28
+ end
29
+ end
30
+
21
31
  rails_version = Gem::Version.new(rails_version)
22
32
 
23
33
  gem "rails", "~> #{rails_version}"
@@ -26,6 +36,7 @@ if rails_version >= Gem::Version.new("8.0.0")
26
36
  gem "rspec-rails"
27
37
  gem "sqlite3", "~> 2.1.1", platform: :ruby
28
38
  elsif rails_version >= Gem::Version.new("7.1.0")
39
+ gem "psych", "~> 4.0.0"
29
40
  gem "rspec-rails"
30
41
  gem "sqlite3", "~> 1.7.3", platform: :ruby
31
42
  elsif rails_version >= Gem::Version.new("6.1.0")
@@ -37,8 +48,8 @@ elsif rails_version >= Gem::Version.new("6.1.0")
37
48
  gem "sqlite3", "~> 1.6.9", platform: :ruby
38
49
  end
39
50
  else
40
- gem "rspec-rails", "~> 4.0"
41
51
  gem "psych", "~> 3.0.0"
52
+ gem "rspec-rails", "~> 4.0"
42
53
 
43
54
  if rails_version >= Gem::Version.new("6.0.0")
44
55
  gem "sqlite3", "~> 1.4.0", platform: :ruby
data/bin/test ADDED
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ # Standalone CLI script to test sentry-rails against multiple Rails versions
6
+ #
7
+ # FEATURES:
8
+ # - Dedicated lock files for each Ruby/Rails version combination
9
+ # - Prevents dependency conflicts between different Rails versions
10
+ # - Automatic lock file management and restoration
11
+ # - Clean up functionality for old lock files
12
+ #
13
+ # LOCK FILE STRATEGY:
14
+ # Each Ruby/Rails combination gets its own lock file:
15
+ # - Ruby 3.4.5 + Rails 6.1 → Gemfile-ruby-3.4.5-rails-6.1.lock
16
+ # - Ruby 3.4.5 + Rails 7.0 → Gemfile-ruby-3.4.5-rails-7.0.lock
17
+ #
18
+ # Usage:
19
+ # ./bin/test --version 5.0
20
+ # ./bin/test --all
21
+ # ./bin/test --help
22
+
23
+ require 'optparse'
24
+ require 'fileutils'
25
+
26
+ class RailsVersionTester
27
+ SUPPORTED_VERSIONS = %w[5.0 5.1 5.2 6.0 6.1 7.0 7.1 7.2 8.0].freeze
28
+
29
+ def initialize
30
+ @options = {}
31
+ @failed_versions = []
32
+ @ruby_version = RUBY_VERSION
33
+ @spec_paths = []
34
+ end
35
+
36
+ def run(args)
37
+ parse_options(args)
38
+
39
+ case
40
+ when @options[:help]
41
+ show_help
42
+ when @options[:list]
43
+ list_versions
44
+ when @options[:clean]
45
+ clean_lock_files
46
+ when @options[:all]
47
+ test_all_versions
48
+ when @options[:version]
49
+ test_single_version(@options[:version])
50
+ else
51
+ puts "Error: No action specified. Use --help for usage information."
52
+ exit(1)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def parse_options(args)
59
+ OptionParser.new do |opts|
60
+ opts.banner = "Usage: #{$0} [options] [spec_paths...]"
61
+
62
+ opts.on("-v", "--version VERSION", "Test specific Rails version") do |version|
63
+ unless SUPPORTED_VERSIONS.include?(version)
64
+ puts "Error: Unsupported Rails version '#{version}'"
65
+ puts "Supported versions: #{SUPPORTED_VERSIONS.join(', ')}"
66
+ exit(1)
67
+ end
68
+ @options[:version] = version
69
+ end
70
+
71
+ opts.on("-a", "--all", "Test all supported Rails versions") do
72
+ @options[:all] = true
73
+ end
74
+
75
+ opts.on("-l", "--list", "List supported Rails versions and lock file status") do
76
+ @options[:list] = true
77
+ end
78
+
79
+ opts.on("-c", "--clean", "Clean up old lock files for current Ruby version") do
80
+ @options[:clean] = true
81
+ end
82
+
83
+ opts.on("-h", "--help", "Show this help message") do
84
+ @options[:help] = true
85
+ end
86
+ end.parse!(args)
87
+
88
+ # Remaining arguments are spec paths
89
+ @spec_paths = args
90
+ end
91
+
92
+ def show_help
93
+ puts <<~HELP
94
+ Rails Version Tester for sentry-rails
95
+
96
+ This script tests sentry-rails against different Rails versions by:
97
+ 1. Setting the RAILS_VERSION environment variable
98
+ 2. Managing bundle dependencies with dedicated lock files per Ruby/Rails combination
99
+ 3. Running the test suite in isolated processes
100
+ 4. Providing proper exit codes for CI/CD integration
101
+
102
+ Each Ruby/Rails version combination gets its own Gemfile.lock to prevent conflicts:
103
+ - Ruby #{@ruby_version} + Rails 6.1 → Gemfile-ruby-#{@ruby_version}-rails-6.1.lock
104
+ - Ruby #{@ruby_version} + Rails 7.0 → Gemfile-ruby-#{@ruby_version}-rails-7.0.lock
105
+
106
+ Usage:
107
+ #{$0} --version 6.1 # Test specific Rails version (all specs)
108
+ #{$0} --version 7.0 spec/sentry/rails/log_subscribers # Test specific Rails version with specific specs
109
+ #{$0} --all # Test all supported versions
110
+ #{$0} --list # List supported versions and lock file status
111
+ #{$0} --clean # Clean up old lock files for current Ruby version
112
+ #{$0} --help # Show this help
113
+
114
+ Supported Rails versions: #{SUPPORTED_VERSIONS.join(', ')}
115
+
116
+ Examples:
117
+ #{$0} -v 7.1 # Test Rails 7.1 (all specs)
118
+ #{$0} -v 7.0 spec/sentry/rails/log_subscribers # Test Rails 7.0 log subscriber specs only
119
+ #{$0} -v 7.0 spec/sentry/rails/tracing # Test Rails 7.0 tracing specs only
120
+ #{$0} -a # Test all versions
121
+ #{$0} -c # Clean up old lock files
122
+ HELP
123
+ end
124
+
125
+ def list_versions
126
+ puts "Supported Rails versions:"
127
+ SUPPORTED_VERSIONS.each do |version|
128
+ lock_file = generate_lock_file_name(version)
129
+ status = File.exist?(lock_file) ? "(has lock file)" : "(no lock file)"
130
+ puts " - #{version} #{status}"
131
+ end
132
+ puts
133
+ puts "Current Ruby version: #{@ruby_version}"
134
+ puts "Lock files are stored as: Gemfile-ruby-X.X.X-rails-Y.Y.lock"
135
+ end
136
+
137
+ def test_all_versions
138
+ puts "Testing sentry-rails against all supported Rails versions: #{SUPPORTED_VERSIONS.join(', ')}"
139
+ puts
140
+
141
+ SUPPORTED_VERSIONS.each do |version|
142
+ puts "=" * 60
143
+ puts "Testing Rails #{version}"
144
+ puts "=" * 60
145
+
146
+ exit_code = test_rails_version(version)
147
+
148
+ if exit_code == 0
149
+ puts "✓ Rails #{version} - PASSED"
150
+ else
151
+ puts "✗ Rails #{version} - FAILED (exit code: #{exit_code})"
152
+ @failed_versions << version
153
+ end
154
+ puts
155
+ end
156
+
157
+ print_summary
158
+ end
159
+
160
+ def test_single_version(version)
161
+ puts "Testing sentry-rails against Rails #{version}..."
162
+ exit_code = test_rails_version(version)
163
+ exit(exit_code) unless exit_code == 0
164
+ end
165
+
166
+ def test_rails_version(version)
167
+ puts "Setting up environment for Rails #{version}..."
168
+
169
+ # Generate dedicated lock file name for this Ruby/Rails combination
170
+ dedicated_lock_file = generate_lock_file_name(version)
171
+ current_lock_file = "Gemfile.lock"
172
+
173
+ # Set up environment variables
174
+ env = {
175
+ "RAILS_VERSION" => version,
176
+ "BUNDLE_GEMFILE" => File.expand_path("Gemfile", Dir.pwd)
177
+ }
178
+
179
+ puts "Using dedicated lock file: #{dedicated_lock_file}"
180
+
181
+ # Manage lock file switching
182
+ setup_lock_file(dedicated_lock_file, current_lock_file)
183
+
184
+ begin
185
+ # Check if bundle update is needed
186
+ if bundle_update_needed?(env, dedicated_lock_file)
187
+ puts "Dependencies need to be updated for Rails #{version}..."
188
+ unless update_bundle(env, dedicated_lock_file)
189
+ puts "✗ Failed to update bundle for Rails #{version}"
190
+ return 1
191
+ end
192
+ end
193
+
194
+ # Run the tests in a separate process
195
+ puts "Running test suite..."
196
+ run_tests(env, @spec_paths)
197
+ ensure
198
+ # Save the current lock file back to the dedicated location
199
+ save_lock_file(dedicated_lock_file, current_lock_file)
200
+ end
201
+ end
202
+
203
+ def generate_lock_file_name(rails_version)
204
+ # Create a unique lock file name for this Ruby/Rails combination
205
+ ruby_version_clean = @ruby_version.gsub(/[^\d\.]/, '')
206
+ rails_version_clean = rails_version.gsub(/[^\d\.]/, '')
207
+ "Gemfile-ruby-#{ruby_version_clean}-rails-#{rails_version_clean}.lock"
208
+ end
209
+
210
+ def setup_lock_file(dedicated_lock_file, current_lock_file)
211
+ # If we have a dedicated lock file, copy it to the current location
212
+ if File.exist?(dedicated_lock_file)
213
+ puts "Restoring lock file from #{dedicated_lock_file}"
214
+ FileUtils.cp(dedicated_lock_file, current_lock_file)
215
+ elsif File.exist?(current_lock_file)
216
+ # If no dedicated lock file exists but current one does, remove it
217
+ # so we get a fresh resolution
218
+ puts "Removing existing lock file for fresh dependency resolution"
219
+ File.delete(current_lock_file)
220
+ end
221
+ end
222
+
223
+ def save_lock_file(dedicated_lock_file, current_lock_file)
224
+ # Save the current lock file to the dedicated location
225
+ if File.exist?(current_lock_file)
226
+ puts "Saving lock file to #{dedicated_lock_file}"
227
+ FileUtils.cp(current_lock_file, dedicated_lock_file)
228
+ end
229
+ end
230
+
231
+ def bundle_update_needed?(env, dedicated_lock_file)
232
+ # Check if current Gemfile.lock exists
233
+ current_lock_file = "Gemfile.lock"
234
+ gemfile_path = env["BUNDLE_GEMFILE"] || "Gemfile"
235
+
236
+ return true unless File.exist?(current_lock_file)
237
+
238
+ # Check if Gemfile is newer than the current lock file
239
+ return true if File.mtime(gemfile_path) > File.mtime(current_lock_file)
240
+
241
+ # For Rails version changes, check if lockfile has incompatible Rails version
242
+ if env["RAILS_VERSION"] && lockfile_has_incompatible_rails_version?(current_lock_file, env["RAILS_VERSION"])
243
+ return true
244
+ end
245
+
246
+ # Check if bundle check passes
247
+ system(env, "bundle check > /dev/null 2>&1") == false
248
+ end
249
+
250
+ def lockfile_has_incompatible_rails_version?(lockfile_path, target_rails_version)
251
+ return false unless File.exist?(lockfile_path)
252
+
253
+ lockfile_content = File.read(lockfile_path)
254
+
255
+ # Extract Rails version from lockfile
256
+ if lockfile_content =~ /^\s*rails \(([^)]+)\)/
257
+ locked_rails_version = $1
258
+ target_major_minor = target_rails_version.split('.')[0..1].join('.')
259
+ locked_major_minor = locked_rails_version.split('.')[0..1].join('.')
260
+
261
+ # If major.minor versions don't match, we need to update
262
+ return target_major_minor != locked_major_minor
263
+ end
264
+
265
+ # If we can't determine the Rails version, assume update is needed
266
+ true
267
+ end
268
+
269
+ def update_bundle(env, dedicated_lock_file)
270
+ puts "Updating bundle for Rails #{env['RAILS_VERSION']}..."
271
+
272
+ current_lock_file = "Gemfile.lock"
273
+
274
+ # Try bundle update first
275
+ if system(env, "bundle update --quiet")
276
+ puts "Bundle updated successfully"
277
+ return true
278
+ end
279
+
280
+ puts "Bundle update failed, trying clean install..."
281
+
282
+ # Remove the current lockfile and try fresh install
283
+ File.delete(current_lock_file) if File.exist?(current_lock_file)
284
+
285
+ if system(env, "bundle install --quiet")
286
+ puts "Bundle installed successfully"
287
+ return true
288
+ end
289
+
290
+ puts "Bundle install failed"
291
+ false
292
+ end
293
+
294
+ def run_tests(env, spec_paths = [])
295
+ # Determine the command to run
296
+ if spec_paths.empty?
297
+ # Run all tests via rake
298
+ command = "bundle exec rake"
299
+ else
300
+ # Run specific specs via rspec
301
+ command = "bundle exec rspec #{spec_paths.join(' ')}"
302
+ end
303
+
304
+ puts "Executing: #{command}"
305
+
306
+ # Run the tests in a separate process with proper signal handling
307
+ pid = spawn(env, command,
308
+ out: $stdout,
309
+ err: $stderr,
310
+ pgroup: true)
311
+
312
+ begin
313
+ _, status = Process.wait2(pid)
314
+ status.exitstatus
315
+ rescue Interrupt
316
+ puts "\nInterrupted! Terminating test process..."
317
+ terminate_process_group(pid)
318
+ 130 # Standard exit code for SIGINT
319
+ end
320
+ end
321
+
322
+ def terminate_process_group(pid)
323
+ begin
324
+ Process.kill("TERM", -pid) # Kill the process group
325
+ sleep(2)
326
+ Process.kill("KILL", -pid) if process_running?(pid)
327
+ rescue Errno::ESRCH
328
+ # Process already terminated
329
+ end
330
+ end
331
+
332
+ def process_running?(pid)
333
+ Process.getpgid(pid)
334
+ true
335
+ rescue Errno::ESRCH
336
+ false
337
+ end
338
+
339
+ def clean_lock_files
340
+ puts "Cleaning up lock files for Ruby #{@ruby_version}..."
341
+
342
+ # Find all lock files matching our pattern
343
+ pattern = "Gemfile-ruby-#{@ruby_version.gsub(/[^\d\.]/, '')}-rails-*.lock"
344
+ lock_files = Dir.glob(pattern)
345
+
346
+ if lock_files.empty?
347
+ puts "No lock files found matching pattern: #{pattern}"
348
+ return
349
+ end
350
+
351
+ puts "Found #{lock_files.length} lock file(s):"
352
+ lock_files.each { |file| puts " - #{file}" }
353
+
354
+ print "Delete these files? [y/N]: "
355
+ response = $stdin.gets.chomp.downcase
356
+
357
+ if response == 'y' || response == 'yes'
358
+ lock_files.each do |file|
359
+ File.delete(file)
360
+ puts "Deleted: #{file}"
361
+ end
362
+ puts "Cleanup complete!"
363
+ else
364
+ puts "Cleanup cancelled."
365
+ end
366
+ end
367
+
368
+ def print_summary
369
+ puts "=" * 60
370
+ puts "SUMMARY"
371
+ puts "=" * 60
372
+
373
+ if @failed_versions.empty?
374
+ puts "✓ All Rails versions passed!"
375
+ exit(0)
376
+ else
377
+ puts "✗ Failed versions: #{@failed_versions.join(', ')}"
378
+ puts
379
+ puts "Some Rails versions failed. See output above for details."
380
+ exit(1)
381
+ end
382
+ end
383
+ end
384
+
385
+ # Run the script if called directly
386
+ if __FILE__ == $0
387
+ tester = RailsVersionTester.new
388
+ tester.run(ARGV)
389
+ end
@@ -2,14 +2,18 @@
2
2
 
3
3
  module Sentry
4
4
  class BackgroundWorker
5
- def _perform(&block)
6
- block.call
7
- ensure
8
- # some applications have partial or even no AR connection
9
- if ActiveRecord::Base.connected?
10
- # make sure the background worker returns AR connection if it accidentally acquire one during serialization
11
- ActiveRecord::Base.connection_pool.release_connection
5
+ module ActiveRecordConnectionPatch
6
+ def _perform(&block)
7
+ super(&block)
8
+ ensure
9
+ # some applications have partial or even no AR connection
10
+ if ActiveRecord::Base.connected?
11
+ # make sure the background worker returns AR connection if it accidentally acquire one during serialization
12
+ ActiveRecord::Base.connection_pool.release_connection
13
+ end
12
14
  end
13
15
  end
14
16
  end
15
17
  end
18
+
19
+ Sentry::BackgroundWorker.prepend(Sentry::BackgroundWorker::ActiveRecordConnectionPatch)
@@ -6,6 +6,9 @@ require "sentry/rails/tracing/active_record_subscriber"
6
6
  require "sentry/rails/tracing/active_storage_subscriber"
7
7
  require "sentry/rails/tracing/active_support_subscriber"
8
8
 
9
+ require "sentry/rails/log_subscribers/active_record_subscriber"
10
+ require "sentry/rails/log_subscribers/action_controller_subscriber"
11
+
9
12
  module Sentry
10
13
  class Configuration
11
14
  attr_reader :rails
@@ -159,6 +162,10 @@ module Sentry
159
162
  # Set this option to true if you want Sentry to capture each retry failure
160
163
  attr_accessor :active_job_report_on_retry_error
161
164
 
165
+ # Configuration for structured logging feature
166
+ # @return [StructuredLoggingConfiguration]
167
+ attr_reader :structured_logging
168
+
162
169
  def initialize
163
170
  @register_error_subscriber = false
164
171
  @report_rescued_exceptions = true
@@ -176,6 +183,27 @@ module Sentry
176
183
  @db_query_source_threshold_ms = 100
177
184
  @active_support_logger_subscription_items = Sentry::Rails::ACTIVE_SUPPORT_LOGGER_SUBSCRIPTION_ITEMS_DEFAULT.dup
178
185
  @active_job_report_on_retry_error = false
186
+ @structured_logging = StructuredLoggingConfiguration.new
187
+ end
188
+ end
189
+
190
+ class StructuredLoggingConfiguration
191
+ # Enable or disable structured logging
192
+ # @return [Boolean]
193
+ attr_accessor :enabled
194
+
195
+ # Hash of components to subscriber classes for structured logging
196
+ # @return [Hash<Symbol, Class>]
197
+ attr_accessor :subscribers
198
+
199
+ DEFAULT_SUBSCRIBERS = {
200
+ active_record: Sentry::Rails::LogSubscribers::ActiveRecordSubscriber,
201
+ action_controller: Sentry::Rails::LogSubscribers::ActionControllerSubscriber
202
+ }.freeze
203
+
204
+ def initialize
205
+ @enabled = false
206
+ @subscribers = DEFAULT_SUBSCRIBERS.dup
179
207
  end
180
208
  end
181
209
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/log_subscriber"
4
+
5
+ module Sentry
6
+ module Rails
7
+ # Base class for Sentry log subscribers that extends ActiveSupport::LogSubscriber
8
+ # to provide structured logging capabilities for Rails components.
9
+ #
10
+ # This class follows Rails' LogSubscriber pattern and provides common functionality
11
+ # for capturing Rails instrumentation events and logging them through Sentry's
12
+ # structured logging system.
13
+ #
14
+ # @example Creating a custom log subscriber
15
+ # class MySubscriber < Sentry::Rails::LogSubscriber
16
+ # attach_to :my_component
17
+ #
18
+ # def my_event(event)
19
+ # log_structured_event(
20
+ # message: "My event occurred",
21
+ # level: :info,
22
+ # attributes: {
23
+ # duration_ms: event.duration,
24
+ # custom_data: event.payload[:custom_data]
25
+ # }
26
+ # )
27
+ # end
28
+ # end
29
+ class LogSubscriber < ActiveSupport::LogSubscriber
30
+ class << self
31
+ if ::Rails.version.to_f < 6.0
32
+ # Rails 5.x does not provide detach_from
33
+ def detach_from(namespace, notifications = ActiveSupport::Notifications)
34
+ listeners = public_instance_methods(false)
35
+ .flat_map { |key|
36
+ notifications.notifier.listeners_for("#{key}.#{namespace}")
37
+ }
38
+ .select { |listener| listener.instance_variable_get(:@delegate).is_a?(self) }
39
+
40
+ listeners.map do |listener|
41
+ notifications.notifier.unsubscribe(listener)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ protected
48
+
49
+ # Log a structured event using Sentry's structured logger
50
+ #
51
+ # @param message [String] The log message
52
+ # @param level [Symbol] The log level (:trace, :debug, :info, :warn, :error, :fatal)
53
+ # @param attributes [Hash] Additional structured attributes to include
54
+ def log_structured_event(message:, level: :info, attributes: {})
55
+ Sentry.logger.public_send(level, message, **attributes)
56
+ rescue => e
57
+ # Silently handle any errors in logging to avoid breaking the application
58
+ Sentry.configuration.sdk_logger.debug("Failed to log structured event: #{e.message}")
59
+ end
60
+
61
+ # Calculate duration in milliseconds from an event
62
+ #
63
+ # @param event [ActiveSupport::Notifications::Event] The event
64
+ # @return [Float] Duration in milliseconds
65
+ def duration_ms(event)
66
+ event.duration.round(2)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/rails/log_subscriber"
4
+ require "sentry/rails/log_subscribers/parameter_filter"
5
+
6
+ module Sentry
7
+ module Rails
8
+ module LogSubscribers
9
+ # LogSubscriber for ActionController events that captures HTTP request processing
10
+ # and logs them using Sentry's structured logging system.
11
+ #
12
+ # This subscriber captures process_action.action_controller events and formats them
13
+ # with relevant request information including controller, action, HTTP status,
14
+ # request parameters, and performance metrics.
15
+ #
16
+ # @example Usage
17
+ # # Enable structured logging for ActionController
18
+ # Sentry.init do |config|
19
+ # config.enable_logs = true
20
+ # config.rails.structured_logging = true
21
+ # config.rails.structured_logging.subscribers = { action_controller: Sentry::Rails::LogSubscribers::ActionControllerSubscriber }
22
+ # end
23
+ class ActionControllerSubscriber < Sentry::Rails::LogSubscriber
24
+ include ParameterFilter
25
+
26
+ # Handle process_action.action_controller events
27
+ #
28
+ # @param event [ActiveSupport::Notifications::Event] The controller action event
29
+ def process_action(event)
30
+ payload = event.payload
31
+
32
+ controller = payload[:controller]
33
+ action = payload[:action]
34
+
35
+ status = extract_status(payload)
36
+
37
+ attributes = {
38
+ controller: controller,
39
+ action: action,
40
+ duration_ms: duration_ms(event),
41
+ method: payload[:method],
42
+ path: payload[:path],
43
+ format: payload[:format]
44
+ }
45
+
46
+ attributes[:status] = status if status
47
+
48
+ if payload[:view_runtime]
49
+ attributes[:view_runtime_ms] = payload[:view_runtime].round(2)
50
+ end
51
+
52
+ if payload[:db_runtime]
53
+ attributes[:db_runtime_ms] = payload[:db_runtime].round(2)
54
+ end
55
+
56
+ if Sentry.configuration.send_default_pii && payload[:params]
57
+ filtered_params = filter_sensitive_params(payload[:params])
58
+ attributes[:params] = filtered_params unless filtered_params.empty?
59
+ end
60
+
61
+ level = level_for_request(payload)
62
+ message = "#{controller}##{action}"
63
+
64
+ log_structured_event(
65
+ message: message,
66
+ level: level,
67
+ attributes: attributes
68
+ )
69
+ end
70
+
71
+ private
72
+
73
+ def extract_status(payload)
74
+ if payload[:status]
75
+ payload[:status]
76
+ elsif payload[:exception]
77
+ case payload[:exception].first
78
+ when "ActionController::RoutingError"
79
+ 404
80
+ when "ActionController::BadRequest"
81
+ 400
82
+ else
83
+ 500
84
+ end
85
+ end
86
+ end
87
+
88
+ def level_for_request(payload)
89
+ status = payload[:status]
90
+
91
+ # In Rails < 6.0 status is not set when an action raised an exception
92
+ if status.nil? && payload[:exception]
93
+ case payload[:exception].first
94
+ when "ActionController::RoutingError"
95
+ :warn
96
+ when "ActionController::BadRequest"
97
+ :warn
98
+ else
99
+ :error
100
+ end
101
+ elsif status.nil?
102
+ :info
103
+ elsif status >= 200 && status < 400
104
+ :info
105
+ elsif status >= 400 && status < 500
106
+ :warn
107
+ elsif status >= 500
108
+ :error
109
+ else
110
+ :info
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/rails/log_subscriber"
4
+ require "sentry/rails/log_subscribers/parameter_filter"
5
+
6
+ module Sentry
7
+ module Rails
8
+ module LogSubscribers
9
+ # LogSubscriber for ActionMailer events that captures email delivery
10
+ # and processing events using Sentry's structured logging system.
11
+ #
12
+ # This subscriber captures deliver.action_mailer and process.action_mailer events
13
+ # and formats them with relevant email information while respecting PII settings.
14
+ #
15
+ # @example Usage
16
+ # # Enable structured logging for ActionMailer
17
+ # Sentry.init do |config|
18
+ # config.enable_logs = true
19
+ # config.rails.structured_logging = true
20
+ # config.rails.structured_logging.subscribers = { action_mailer: Sentry::Rails::LogSubscribers::ActionMailerSubscriber }
21
+ # end
22
+ class ActionMailerSubscriber < Sentry::Rails::LogSubscriber
23
+ include ParameterFilter
24
+
25
+ # Handle deliver.action_mailer events
26
+ #
27
+ # @param event [ActiveSupport::Notifications::Event] The email delivery event
28
+ def deliver(event)
29
+ payload = event.payload
30
+
31
+ mailer = payload[:mailer]
32
+
33
+ attributes = {
34
+ mailer: mailer,
35
+ duration_ms: duration_ms(event),
36
+ perform_deliveries: payload[:perform_deliveries]
37
+ }
38
+
39
+ attributes[:delivery_method] = payload[:delivery_method] if payload[:delivery_method]
40
+ attributes[:date] = payload[:date].to_s if payload[:date]
41
+
42
+ if Sentry.configuration.send_default_pii
43
+ attributes[:message_id] = payload[:message_id] if payload[:message_id]
44
+ end
45
+
46
+ message = "Email delivered via #{mailer}"
47
+
48
+ # Log the structured event
49
+ log_structured_event(
50
+ message: message,
51
+ level: :info,
52
+ attributes: attributes
53
+ )
54
+ end
55
+
56
+ # Handle process.action_mailer events
57
+ #
58
+ # @param event [ActiveSupport::Notifications::Event] The email processing event
59
+ def process(event)
60
+ payload = event.payload
61
+
62
+ mailer = payload[:mailer]
63
+ action = payload[:action]
64
+ duration = duration_ms(event)
65
+
66
+ attributes = {
67
+ mailer: mailer,
68
+ action: action,
69
+ duration_ms: duration
70
+ }
71
+
72
+ if Sentry.configuration.send_default_pii && payload[:params]
73
+ filtered_params = filter_sensitive_params(payload[:params])
74
+ attributes[:params] = filtered_params unless filtered_params.empty?
75
+ end
76
+
77
+ message = "#{mailer}##{action}"
78
+
79
+ log_structured_event(
80
+ message: message,
81
+ level: :info,
82
+ attributes: attributes
83
+ )
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/rails/log_subscriber"
4
+ require "sentry/rails/log_subscribers/parameter_filter"
5
+
6
+ module Sentry
7
+ module Rails
8
+ module LogSubscribers
9
+ # LogSubscriber for ActiveJob events that captures background job execution
10
+ # and logs them using Sentry's structured logging system.
11
+ #
12
+ # This subscriber captures various ActiveJob events including job execution,
13
+ # enqueueing, retries, and failures with relevant job information.
14
+ #
15
+ # @example Usage
16
+ # # Enable structured logging for ActiveJob
17
+ # Sentry.init do |config|
18
+ # config.enable_logs = true
19
+ # config.rails.structured_logging = true
20
+ # config.rails.structured_logging.subscribers = { active_job: Sentry::Rails::LogSubscribers::ActiveJobSubscriber }
21
+ # end
22
+ class ActiveJobSubscriber < Sentry::Rails::LogSubscriber
23
+ include ParameterFilter
24
+
25
+ # Handle perform.active_job events
26
+ #
27
+ # @param event [ActiveSupport::Notifications::Event] The job performance event
28
+ def perform(event)
29
+ job = event.payload[:job]
30
+ duration = duration_ms(event)
31
+
32
+ attributes = {
33
+ job_class: job.class.name,
34
+ job_id: job.job_id,
35
+ queue_name: job.queue_name,
36
+ duration_ms: duration,
37
+ executions: job.executions,
38
+ priority: job.priority
39
+ }
40
+
41
+ attributes[:adapter] = job.class.queue_adapter.class.name
42
+
43
+ if job.scheduled_at
44
+ attributes[:scheduled_at] = job.scheduled_at.iso8601
45
+ attributes[:delay_ms] = ((Time.current - job.scheduled_at) * 1000).round(2)
46
+ end
47
+
48
+ if Sentry.configuration.send_default_pii && job.arguments.present?
49
+ filtered_args = filter_sensitive_arguments(job.arguments)
50
+ attributes[:arguments] = filtered_args unless filtered_args.empty?
51
+ end
52
+
53
+ message = "Job performed: #{job.class.name}"
54
+
55
+ log_structured_event(
56
+ message: message,
57
+ level: :info,
58
+ attributes: attributes
59
+ )
60
+ end
61
+
62
+ # Handle enqueue.active_job events
63
+ #
64
+ # @param event [ActiveSupport::Notifications::Event] The job enqueue event
65
+ def enqueue(event)
66
+ job = event.payload[:job]
67
+
68
+ attributes = {
69
+ job_class: job.class.name,
70
+ job_id: job.job_id,
71
+ queue_name: job.queue_name,
72
+ priority: job.priority
73
+ }
74
+
75
+ attributes[:adapter] = job.class.queue_adapter.class.name if job.class.respond_to?(:queue_adapter)
76
+
77
+ if job.scheduled_at
78
+ attributes[:scheduled_at] = job.scheduled_at.iso8601
79
+ attributes[:delay_seconds] = (job.scheduled_at - Time.current).round(2)
80
+ end
81
+
82
+ message = "Job enqueued: #{job.class.name}"
83
+
84
+ log_structured_event(
85
+ message: message,
86
+ level: :info,
87
+ attributes: attributes
88
+ )
89
+ end
90
+
91
+ def retry_stopped(event)
92
+ job = event.payload[:job]
93
+ error = event.payload[:error]
94
+
95
+ attributes = {
96
+ job_class: job.class.name,
97
+ job_id: job.job_id,
98
+ queue_name: job.queue_name,
99
+ executions: job.executions,
100
+ error_class: error.class.name,
101
+ error_message: error.message
102
+ }
103
+
104
+ message = "Job retry stopped: #{job.class.name}"
105
+
106
+ log_structured_event(
107
+ message: message,
108
+ level: :error,
109
+ attributes: attributes
110
+ )
111
+ end
112
+
113
+ def discard(event)
114
+ job = event.payload[:job]
115
+ error = event.payload[:error]
116
+
117
+ attributes = {
118
+ job_class: job.class.name,
119
+ job_id: job.job_id,
120
+ queue_name: job.queue_name,
121
+ executions: job.executions
122
+ }
123
+
124
+ attributes[:error_class] = error.class.name if error
125
+ attributes[:error_message] = error.message if error
126
+
127
+ message = "Job discarded: #{job.class.name}"
128
+
129
+ log_structured_event(
130
+ message: message,
131
+ level: :warn,
132
+ attributes: attributes
133
+ )
134
+ end
135
+
136
+ private
137
+
138
+ def filter_sensitive_arguments(arguments)
139
+ return [] unless arguments.is_a?(Array)
140
+
141
+ arguments.map do |arg|
142
+ case arg
143
+ when Hash
144
+ filter_sensitive_params(arg)
145
+ when String
146
+ arg.length > 100 ? "[FILTERED: #{arg.length} chars]" : arg
147
+ else
148
+ arg
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/rails/log_subscriber"
4
+ require "sentry/rails/log_subscribers/parameter_filter"
5
+
6
+ module Sentry
7
+ module Rails
8
+ module LogSubscribers
9
+ # LogSubscriber for ActiveRecord events that captures database queries
10
+ # and logs them using Sentry's structured logging system.
11
+ #
12
+ # This subscriber captures sql.active_record events and formats them
13
+ # with relevant database information including SQL queries, duration,
14
+ # database configuration, and caching information.
15
+ #
16
+ # @example Usage
17
+ # # Automatically attached when structured logging is enabled for :active_record
18
+ # Sentry.init do |config|
19
+ # config.enable_logs = true
20
+ # config.rails.structured_logging = true
21
+ # config.rails.structured_logging.subscribers = { active_record: Sentry::Rails::LogSubscribers::ActiveRecordSubscriber }
22
+ # end
23
+ class ActiveRecordSubscriber < Sentry::Rails::LogSubscriber
24
+ include ParameterFilter
25
+
26
+ EXCLUDED_NAMES = ["SCHEMA", "TRANSACTION"].freeze
27
+
28
+ # Handle sql.active_record events
29
+ #
30
+ # @param event [ActiveSupport::Notifications::Event] The SQL event
31
+ def sql(event)
32
+ return if EXCLUDED_NAMES.include?(event.payload[:name])
33
+
34
+ sql = event.payload[:sql]
35
+ statement_name = event.payload[:name]
36
+
37
+ # Rails 5.0.0 doesn't include :cached in the payload, it was added in Rails 5.1
38
+ cached = event.payload.fetch(:cached, false)
39
+ connection_id = event.payload[:connection_id]
40
+
41
+ db_config = extract_db_config(event.payload)
42
+
43
+ attributes = {
44
+ sql: sql,
45
+ duration_ms: duration_ms(event),
46
+ cached: cached
47
+ }
48
+
49
+ attributes[:statement_name] = statement_name if statement_name && statement_name != "SQL"
50
+ attributes[:connection_id] = connection_id if connection_id
51
+
52
+ add_db_config_attributes(attributes, db_config)
53
+
54
+ message = build_log_message(statement_name)
55
+
56
+ log_structured_event(
57
+ message: message,
58
+ level: :info,
59
+ attributes: attributes
60
+ )
61
+ end
62
+
63
+ private
64
+
65
+ def build_log_message(statement_name)
66
+ if statement_name && statement_name != "SQL"
67
+ "Database query: #{statement_name}"
68
+ else
69
+ "Database query"
70
+ end
71
+ end
72
+
73
+ def extract_db_config(payload)
74
+ connection = payload[:connection]
75
+
76
+ return unless connection
77
+
78
+ extract_db_config_from_connection(connection)
79
+ end
80
+
81
+ def add_db_config_attributes(attributes, db_config)
82
+ return unless db_config
83
+
84
+ attributes[:db_system] = db_config[:adapter] if db_config[:adapter]
85
+
86
+ if db_config[:database]
87
+ db_name = db_config[:database]
88
+
89
+ if db_config[:adapter] == "sqlite3" && db_name.include?("/")
90
+ db_name = File.basename(db_name)
91
+ end
92
+
93
+ attributes[:db_name] = db_name
94
+ end
95
+
96
+ attributes[:server_address] = db_config[:host] if db_config[:host]
97
+ attributes[:server_port] = db_config[:port] if db_config[:port]
98
+ attributes[:server_socket_address] = db_config[:socket] if db_config[:socket]
99
+ end
100
+
101
+ if ::Rails.version.to_f >= 6.1
102
+ def extract_db_config_from_connection(connection)
103
+ if connection.pool.respond_to?(:db_config)
104
+ db_config = connection.pool.db_config
105
+ if db_config.respond_to?(:configuration_hash)
106
+ return db_config.configuration_hash
107
+ elsif db_config.respond_to?(:config)
108
+ return db_config.config
109
+ end
110
+ end
111
+
112
+ extract_db_config_fallback(connection)
113
+ end
114
+ else
115
+ # Rails 6.0 and earlier use spec API
116
+ def extract_db_config_from_connection(connection)
117
+ if connection.pool.respond_to?(:spec)
118
+ spec = connection.pool.spec
119
+ if spec.respond_to?(:config)
120
+ return spec.config
121
+ end
122
+ end
123
+
124
+ extract_db_config_fallback(connection)
125
+ end
126
+ end
127
+
128
+ def extract_db_config_fallback(connection)
129
+ connection.config if connection.respond_to?(:config)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Rails
5
+ module LogSubscribers
6
+ # Shared utility module for filtering sensitive parameters in log subscribers.
7
+ #
8
+ # This module provides consistent parameter filtering across all Sentry Rails
9
+ # log subscribers, leveraging Rails' built-in parameter filtering when available.
10
+ # It automatically detects the correct Rails parameter filtering API based on
11
+ # the Rails version and includes the appropriate implementation module.
12
+ #
13
+ # @example Usage in a log subscriber
14
+ # class MySubscriber < Sentry::Rails::LogSubscriber
15
+ # include Sentry::Rails::LogSubscribers::ParameterFilter
16
+ #
17
+ # def my_event(event)
18
+ # if Sentry.configuration.send_default_pii && event.payload[:params]
19
+ # filtered_params = filter_sensitive_params(event.payload[:params])
20
+ # attributes[:params] = filtered_params unless filtered_params.empty?
21
+ # end
22
+ # end
23
+ # end
24
+ module ParameterFilter
25
+ EMPTY_HASH = {}.freeze
26
+
27
+ if ::Rails.version.to_f >= 6.0
28
+ def self.backend
29
+ ActiveSupport::ParameterFilter
30
+ end
31
+ else
32
+ def self.backend
33
+ ActionDispatch::Http::ParameterFilter
34
+ end
35
+ end
36
+
37
+ # Filter sensitive parameters from a hash, respecting Rails configuration.
38
+ #
39
+ # @param params [Hash] The parameters to filter
40
+ # @return [Hash] Filtered parameters with sensitive data removed
41
+ def filter_sensitive_params(params)
42
+ return EMPTY_HASH unless params.is_a?(Hash)
43
+
44
+ filter_parameters = ::Rails.application.config.filter_parameters
45
+ parameter_filter = ParameterFilter.backend.new(filter_parameters)
46
+
47
+ parameter_filter.filter(params)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -49,6 +49,7 @@ module Sentry
49
49
  setup_backtrace_cleanup_callback
50
50
  inject_breadcrumbs_logger
51
51
  activate_tracing
52
+ activate_structured_logging
52
53
 
53
54
  register_error_subscriber(app) if ::Rails.version.to_f >= 7.0 && Sentry.configuration.rails.register_error_subscriber
54
55
 
@@ -138,6 +139,12 @@ module Sentry
138
139
  end
139
140
  end
140
141
 
142
+ def activate_structured_logging
143
+ if Sentry.configuration.rails.structured_logging.enabled && Sentry.configuration.enable_logs
144
+ Sentry::Rails::StructuredLogging.attach(Sentry.configuration.rails.structured_logging)
145
+ end
146
+ end
147
+
141
148
  def register_error_subscriber(app)
142
149
  require "sentry/rails/error_subscriber"
143
150
  app.executor.error_reporter.subscribe(Sentry::Rails::ErrorSubscriber.new)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/rails/log_subscriber"
4
+ require "sentry/rails/log_subscribers/action_controller_subscriber"
5
+ require "sentry/rails/log_subscribers/active_record_subscriber"
6
+ require "sentry/rails/log_subscribers/active_job_subscriber"
7
+ require "sentry/rails/log_subscribers/action_mailer_subscriber"
8
+
9
+ module Sentry
10
+ module Rails
11
+ module StructuredLogging
12
+ class << self
13
+ def attach(config)
14
+ config.subscribers.each do |component, subscriber_class|
15
+ subscriber_class.attach_to component
16
+ end
17
+ rescue => e
18
+ Sentry.configuration.sdk_logger.error("Failed to attach structured loggers: #{e.message}")
19
+ Sentry.configuration.sdk_logger.error(e.backtrace.join("\n"))
20
+ end
21
+
22
+ def detach(config)
23
+ config.subscribers.each do |component, subscriber_class|
24
+ subscriber_class.detach_from component
25
+ end
26
+ rescue => e
27
+ Sentry.configuration.sdk_logger.debug("Error during detaching loggers: #{e.message}")
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sentry
4
4
  module Rails
5
- VERSION = "5.26.0"
5
+ VERSION = "5.27.0"
6
6
  end
7
7
  end
data/lib/sentry/rails.rb CHANGED
@@ -5,6 +5,7 @@ require "sentry-ruby"
5
5
  require "sentry/integrable"
6
6
  require "sentry/rails/tracing"
7
7
  require "sentry/rails/configuration"
8
+ require "sentry/rails/structured_logging"
8
9
  require "sentry/rails/engine"
9
10
  require "sentry/rails/railtie"
10
11
 
data/sentry-rails.gemspec CHANGED
@@ -31,5 +31,5 @@ Gem::Specification.new do |spec|
31
31
  spec.require_paths = ["lib"]
32
32
 
33
33
  spec.add_dependency "railties", ">= 5.0"
34
- spec.add_dependency "sentry-ruby", "~> 5.26.0"
34
+ spec.add_dependency "sentry-ruby", "~> 5.27.0"
35
35
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.26.0
4
+ version: 5.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 5.26.0
32
+ version: 5.27.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 5.26.0
39
+ version: 5.27.0
40
40
  description: A gem that provides Rails integration for the Sentry error logger
41
41
  email: accounts@sentry.io
42
42
  executables: []
@@ -56,6 +56,7 @@ files:
56
56
  - app/jobs/sentry/send_event_job.rb
57
57
  - bin/console
58
58
  - bin/setup
59
+ - bin/test
59
60
  - lib/generators/sentry_generator.rb
60
61
  - lib/sentry-rails.rb
61
62
  - lib/sentry/rails.rb
@@ -72,9 +73,16 @@ files:
72
73
  - lib/sentry/rails/engine.rb
73
74
  - lib/sentry/rails/error_subscriber.rb
74
75
  - lib/sentry/rails/instrument_payload_cleanup_helper.rb
76
+ - lib/sentry/rails/log_subscriber.rb
77
+ - lib/sentry/rails/log_subscribers/action_controller_subscriber.rb
78
+ - lib/sentry/rails/log_subscribers/action_mailer_subscriber.rb
79
+ - lib/sentry/rails/log_subscribers/active_job_subscriber.rb
80
+ - lib/sentry/rails/log_subscribers/active_record_subscriber.rb
81
+ - lib/sentry/rails/log_subscribers/parameter_filter.rb
75
82
  - lib/sentry/rails/overrides/streaming_reporter.rb
76
83
  - lib/sentry/rails/railtie.rb
77
84
  - lib/sentry/rails/rescued_exception_interceptor.rb
85
+ - lib/sentry/rails/structured_logging.rb
78
86
  - lib/sentry/rails/tracing.rb
79
87
  - lib/sentry/rails/tracing/abstract_subscriber.rb
80
88
  - lib/sentry/rails/tracing/action_controller_subscriber.rb
@@ -84,15 +92,15 @@ files:
84
92
  - lib/sentry/rails/tracing/active_support_subscriber.rb
85
93
  - lib/sentry/rails/version.rb
86
94
  - sentry-rails.gemspec
87
- homepage: https://github.com/getsentry/sentry-ruby/tree/5.26.0/sentry-rails
95
+ homepage: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-rails
88
96
  licenses:
89
97
  - MIT
90
98
  metadata:
91
- homepage_uri: https://github.com/getsentry/sentry-ruby/tree/5.26.0/sentry-rails
92
- source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.26.0/sentry-rails
93
- changelog_uri: https://github.com/getsentry/sentry-ruby/blob/5.26.0/CHANGELOG.md
99
+ homepage_uri: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-rails
100
+ source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-rails
101
+ changelog_uri: https://github.com/getsentry/sentry-ruby/blob/5.27.0/CHANGELOG.md
94
102
  bug_tracker_uri: https://github.com/getsentry/sentry-ruby/issues
95
- documentation_uri: http://www.rubydoc.info/gems/sentry-rails/5.26.0
103
+ documentation_uri: http://www.rubydoc.info/gems/sentry-rails/5.27.0
96
104
  rdoc_options: []
97
105
  require_paths:
98
106
  - lib
@@ -107,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
115
  - !ruby/object:Gem::Version
108
116
  version: '0'
109
117
  requirements: []
110
- rubygems_version: 3.6.7
118
+ rubygems_version: 3.6.9
111
119
  specification_version: 4
112
120
  summary: A gem that provides Rails integration for the Sentry error logger
113
121
  test_files: []