sentry-rails 5.23.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/Rakefile +11 -28
- data/bin/test +389 -0
- data/lib/sentry/rails/active_job.rb +55 -8
- data/lib/sentry/rails/background_worker.rb +11 -7
- data/lib/sentry/rails/configuration.rb +35 -3
- 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 +12 -0
- data/lib/sentry/rails/structured_logging.rb +32 -0
- data/lib/sentry/rails/tracing/action_controller_subscriber.rb +1 -1
- data/lib/sentry/rails/tracing/active_storage_subscriber.rb +4 -1
- data/lib/sentry/rails/version.rb +1 -1
- data/lib/sentry/rails.rb +1 -0
- data/sentry-rails.gemspec +1 -1
- metadata +19 -11
@@ -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,8 +49,14 @@ 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
|
55
|
+
|
56
|
+
# Presence of ActiveJob is no longer a reliable cue
|
57
|
+
if defined?(Sentry::Rails::ActiveJobExtensions)
|
58
|
+
Sentry::Rails::ActiveJobExtensions::SentryReporter.register_event_handlers
|
59
|
+
end
|
54
60
|
end
|
55
61
|
|
56
62
|
runner do
|
@@ -133,6 +139,12 @@ module Sentry
|
|
133
139
|
end
|
134
140
|
end
|
135
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
|
+
|
136
148
|
def register_error_subscriber(app)
|
137
149
|
require "sentry/rails/error_subscriber"
|
138
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
|
@@ -14,7 +14,7 @@ module Sentry
|
|
14
14
|
SPAN_ORIGIN = "auto.view.rails"
|
15
15
|
|
16
16
|
def self.subscribe!
|
17
|
-
Sentry.
|
17
|
+
Sentry.sdk_logger.warn <<~MSG
|
18
18
|
DEPRECATION WARNING: sentry-rails has changed its approach on controller span recording and #{self.name} is now depreacted.
|
19
19
|
Please stop using or referencing #{self.name} as it will be removed in the next major release.
|
20
20
|
MSG
|
@@ -33,7 +33,10 @@ module Sentry
|
|
33
33
|
duration: duration
|
34
34
|
) do |span|
|
35
35
|
payload.each do |key, value|
|
36
|
-
|
36
|
+
next if key == START_TIMESTAMP_NAME
|
37
|
+
next if key == :key && !Sentry.configuration.send_default_pii
|
38
|
+
|
39
|
+
span.set_data(key, value)
|
37
40
|
end
|
38
41
|
end
|
39
42
|
end
|