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 +4 -4
- data/Gemfile +15 -4
- data/bin/test +389 -0
- data/lib/sentry/rails/background_worker.rb +11 -7
- data/lib/sentry/rails/configuration.rb +28 -0
- data/lib/sentry/rails/log_subscriber.rb +70 -0
- data/lib/sentry/rails/log_subscribers/action_controller_subscriber.rb +116 -0
- data/lib/sentry/rails/log_subscribers/action_mailer_subscriber.rb +88 -0
- data/lib/sentry/rails/log_subscribers/active_job_subscriber.rb +155 -0
- data/lib/sentry/rails/log_subscribers/active_record_subscriber.rb +134 -0
- data/lib/sentry/rails/log_subscribers/parameter_filter.rb +52 -0
- data/lib/sentry/rails/railtie.rb +7 -0
- data/lib/sentry/rails/structured_logging.rb +32 -0
- data/lib/sentry/rails/version.rb +1 -1
- data/lib/sentry/rails.rb +1 -0
- data/sentry-rails.gemspec +1 -1
- metadata +17 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15c5cac1232851c12adb474d437c228335f2c23b3f9f65e9000d769b6265d52d
|
4
|
+
data.tar.gz: fe44e7fe542de216cf8eb97bef21a72f058c62e862dec51edbbd244a98cd21aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
20
|
-
|
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
|
-
|
6
|
-
block
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
data/lib/sentry/rails/railtie.rb
CHANGED
@@ -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
|
data/lib/sentry/rails/version.rb
CHANGED
data/lib/sentry/rails.rb
CHANGED
data/sentry-rails.gemspec
CHANGED
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.
|
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.
|
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.
|
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.
|
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.
|
92
|
-
source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.
|
93
|
-
changelog_uri: https://github.com/getsentry/sentry-ruby/blob/5.
|
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.
|
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.
|
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: []
|