sentry-rails 5.18.2 → 5.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +43 -26
- data/Rakefile +13 -10
- data/app/jobs/sentry/send_event_job.rb +2 -0
- data/bin/console +1 -0
- data/bin/test +389 -0
- data/lib/generators/sentry_generator.rb +13 -1
- data/lib/sentry/rails/action_cable.rb +3 -1
- data/lib/sentry/rails/active_job.rb +65 -10
- data/lib/sentry/rails/background_worker.rb +13 -7
- data/lib/sentry/rails/backtrace_cleaner.rb +7 -7
- data/lib/sentry/rails/breadcrumb/active_support_logger.rb +2 -0
- data/lib/sentry/rails/breadcrumb/monotonic_active_support_logger.rb +2 -0
- data/lib/sentry/rails/capture_exceptions.rb +4 -2
- data/lib/sentry/rails/configuration.rb +64 -18
- data/lib/sentry/rails/controller_methods.rb +2 -0
- data/lib/sentry/rails/controller_transaction.rb +7 -3
- data/lib/sentry/rails/engine.rb +2 -0
- data/lib/sentry/rails/error_subscriber.rb +2 -0
- data/lib/sentry/rails/instrument_payload_cleanup_helper.rb +2 -0
- data/lib/sentry/rails/log_subscriber.rb +73 -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/overrides/streaming_reporter.rb +2 -0
- data/lib/sentry/rails/railtie.rb +16 -2
- data/lib/sentry/rails/rescued_exception_interceptor.rb +12 -1
- data/lib/sentry/rails/structured_logging.rb +32 -0
- data/lib/sentry/rails/tracing/abstract_subscriber.rb +2 -0
- data/lib/sentry/rails/tracing/action_controller_subscriber.rb +5 -3
- data/lib/sentry/rails/tracing/action_view_subscriber.rb +4 -2
- data/lib/sentry/rails/tracing/active_record_subscriber.rb +1 -0
- data/lib/sentry/rails/tracing/active_storage_subscriber.rb +8 -3
- data/lib/sentry/rails/tracing/active_support_subscriber.rb +63 -0
- data/lib/sentry/rails/tracing.rb +2 -0
- data/lib/sentry/rails/version.rb +3 -1
- data/lib/sentry/rails.rb +3 -0
- data/lib/sentry-rails.rb +2 -0
- data/sentry-rails.gemspec +4 -2
- metadata +20 -14
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sentry
|
|
2
4
|
class BackgroundWorker
|
|
3
|
-
|
|
4
|
-
block
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
10
14
|
end
|
|
11
15
|
end
|
|
12
16
|
end
|
|
13
17
|
end
|
|
18
|
+
|
|
19
|
+
Sentry::BackgroundWorker.prepend(Sentry::BackgroundWorker::ActiveRecordConnectionPatch)
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "active_support/backtrace_cleaner"
|
|
2
4
|
require "active_support/core_ext/string/access"
|
|
3
5
|
|
|
4
6
|
module Sentry
|
|
5
7
|
module Rails
|
|
6
8
|
class BacktraceCleaner < ActiveSupport::BacktraceCleaner
|
|
7
|
-
APP_DIRS_PATTERN = /\A(?:\.\/)?(?:app|config|lib|test|\(\w*\))
|
|
8
|
-
RENDER_TEMPLATE_PATTERN = /:in
|
|
9
|
+
APP_DIRS_PATTERN = /\A(?:\.\/)?(?:app|config|lib|test|\(\w*\))/
|
|
10
|
+
RENDER_TEMPLATE_PATTERN = /:in (?:`|').*_\w+_{2,3}\d+_\d+'/
|
|
9
11
|
|
|
10
12
|
def initialize
|
|
11
13
|
super
|
|
12
|
-
#
|
|
14
|
+
# We don't want any default silencers because they're too aggressive
|
|
13
15
|
remove_silencers!
|
|
16
|
+
# We don't want any default filters because Rails 7.2 starts shortening the paths. See #2472
|
|
17
|
+
remove_filters!
|
|
14
18
|
|
|
15
|
-
@root = "#{Sentry.configuration.project_root}/"
|
|
16
|
-
add_filter do |line|
|
|
17
|
-
line.start_with?(@root) ? line.from(@root.size) : line
|
|
18
|
-
end
|
|
19
19
|
add_filter do |line|
|
|
20
20
|
if line =~ RENDER_TEMPLATE_PATTERN
|
|
21
21
|
line.sub(RENDER_TEMPLATE_PATTERN, "")
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sentry
|
|
2
4
|
module Rails
|
|
3
5
|
class CaptureExceptions < Sentry::Rack::CaptureExceptions
|
|
4
6
|
RAILS_7_1 = Gem::Version.new(::Rails.version) >= Gem::Version.new("7.1.0.alpha")
|
|
5
|
-
SPAN_ORIGIN =
|
|
7
|
+
SPAN_ORIGIN = "auto.http.rails"
|
|
6
8
|
|
|
7
9
|
def initialize(_)
|
|
8
10
|
super
|
|
@@ -20,7 +22,7 @@ module Sentry
|
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def transaction_op
|
|
23
|
-
"http.server"
|
|
25
|
+
"http.server"
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def capture_exception(exception, env)
|
|
@@ -1,25 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "sentry/rails/tracing/action_controller_subscriber"
|
|
2
4
|
require "sentry/rails/tracing/action_view_subscriber"
|
|
3
5
|
require "sentry/rails/tracing/active_record_subscriber"
|
|
4
6
|
require "sentry/rails/tracing/active_storage_subscriber"
|
|
7
|
+
require "sentry/rails/tracing/active_support_subscriber"
|
|
8
|
+
|
|
9
|
+
require "sentry/rails/log_subscribers/active_record_subscriber"
|
|
10
|
+
require "sentry/rails/log_subscribers/action_controller_subscriber"
|
|
5
11
|
|
|
6
12
|
module Sentry
|
|
7
13
|
class Configuration
|
|
8
14
|
attr_reader :rails
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
after(:initialize) do
|
|
11
17
|
@rails = Sentry::Rails::Configuration.new
|
|
12
18
|
@excluded_exceptions = @excluded_exceptions.concat(Sentry::Rails::IGNORE_DEFAULT)
|
|
13
19
|
|
|
14
20
|
if ::Rails.logger
|
|
15
21
|
if defined?(::ActiveSupport::BroadcastLogger) && ::Rails.logger.is_a?(::ActiveSupport::BroadcastLogger)
|
|
16
22
|
dupped_broadcasts = ::Rails.logger.broadcasts.map(&:dup)
|
|
17
|
-
|
|
23
|
+
self.sdk_logger = ::ActiveSupport::BroadcastLogger.new(*dupped_broadcasts)
|
|
18
24
|
else
|
|
19
|
-
|
|
25
|
+
self.sdk_logger = ::Rails.logger.dup
|
|
20
26
|
end
|
|
21
27
|
else
|
|
22
|
-
|
|
28
|
+
sdk_logger.warn(Sentry::LOGGER_PROGNAME) do
|
|
23
29
|
<<~MSG
|
|
24
30
|
sentry-rails can't detect Rails.logger. it may be caused by misplacement of the SDK initialization code
|
|
25
31
|
please make sure you place the Sentry.init block under the `config/initializers` folder, e.g. `config/initializers/sentry.rb`
|
|
@@ -27,24 +33,28 @@ module Sentry
|
|
|
27
33
|
end
|
|
28
34
|
end
|
|
29
35
|
end
|
|
36
|
+
|
|
37
|
+
after(:configured) do
|
|
38
|
+
rails.structured_logging.enabled = enable_logs if rails.structured_logging.enabled.nil?
|
|
39
|
+
end
|
|
30
40
|
end
|
|
31
41
|
|
|
32
42
|
module Rails
|
|
33
43
|
IGNORE_DEFAULT = [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
"AbstractController::ActionNotFound",
|
|
45
|
+
"ActionController::BadRequest",
|
|
46
|
+
"ActionController::InvalidAuthenticityToken",
|
|
47
|
+
"ActionController::InvalidCrossOriginRequest",
|
|
48
|
+
"ActionController::MethodNotAllowed",
|
|
49
|
+
"ActionController::NotImplemented",
|
|
50
|
+
"ActionController::ParameterMissing",
|
|
51
|
+
"ActionController::RoutingError",
|
|
52
|
+
"ActionController::UnknownAction",
|
|
53
|
+
"ActionController::UnknownFormat",
|
|
54
|
+
"ActionDispatch::Http::MimeNegotiation::InvalidType",
|
|
55
|
+
"ActionController::UnknownHttpMethod",
|
|
56
|
+
"ActionDispatch::Http::Parameters::ParseError",
|
|
57
|
+
"ActiveRecord::RecordNotFound"
|
|
48
58
|
].freeze
|
|
49
59
|
|
|
50
60
|
ACTIVE_SUPPORT_LOGGER_SUBSCRIPTION_ITEMS_DEFAULT = {
|
|
@@ -153,6 +163,13 @@ module Sentry
|
|
|
153
163
|
# @return [Hash<String, Array<Symbol>>]
|
|
154
164
|
attr_accessor :active_support_logger_subscription_items
|
|
155
165
|
|
|
166
|
+
# Set this option to true if you want Sentry to capture each retry failure
|
|
167
|
+
attr_accessor :active_job_report_on_retry_error
|
|
168
|
+
|
|
169
|
+
# Configuration for structured logging feature
|
|
170
|
+
# @return [StructuredLoggingConfiguration]
|
|
171
|
+
attr_reader :structured_logging
|
|
172
|
+
|
|
156
173
|
def initialize
|
|
157
174
|
@register_error_subscriber = false
|
|
158
175
|
@report_rescued_exceptions = true
|
|
@@ -162,12 +179,41 @@ module Sentry
|
|
|
162
179
|
end
|
|
163
180
|
@tracing_subscribers = Set.new([
|
|
164
181
|
Sentry::Rails::Tracing::ActionViewSubscriber,
|
|
182
|
+
Sentry::Rails::Tracing::ActiveSupportSubscriber,
|
|
165
183
|
Sentry::Rails::Tracing::ActiveRecordSubscriber,
|
|
166
184
|
Sentry::Rails::Tracing::ActiveStorageSubscriber
|
|
167
185
|
])
|
|
168
186
|
@enable_db_query_source = true
|
|
169
187
|
@db_query_source_threshold_ms = 100
|
|
170
188
|
@active_support_logger_subscription_items = Sentry::Rails::ACTIVE_SUPPORT_LOGGER_SUBSCRIPTION_ITEMS_DEFAULT.dup
|
|
189
|
+
@active_job_report_on_retry_error = false
|
|
190
|
+
@structured_logging = StructuredLoggingConfiguration.new
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
class StructuredLoggingConfiguration
|
|
195
|
+
# Enable or disable structured logging
|
|
196
|
+
# @return [Boolean]
|
|
197
|
+
attr_accessor :enabled
|
|
198
|
+
|
|
199
|
+
# Hash of components to subscriber classes for structured logging
|
|
200
|
+
# @return [Hash<Symbol, Class>]
|
|
201
|
+
attr_accessor :subscribers
|
|
202
|
+
|
|
203
|
+
DEFAULT_SUBSCRIBERS = {
|
|
204
|
+
active_record: Sentry::Rails::LogSubscribers::ActiveRecordSubscriber,
|
|
205
|
+
action_controller: Sentry::Rails::LogSubscribers::ActionControllerSubscriber
|
|
206
|
+
}.freeze
|
|
207
|
+
|
|
208
|
+
def initialize
|
|
209
|
+
@enabled = nil
|
|
210
|
+
@subscribers = DEFAULT_SUBSCRIBERS.dup
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Returns true if structured logging should be enabled.
|
|
214
|
+
# @return [Boolean]
|
|
215
|
+
def enabled?
|
|
216
|
+
enabled
|
|
171
217
|
end
|
|
172
218
|
end
|
|
173
219
|
end
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sentry
|
|
2
4
|
module Rails
|
|
3
5
|
module ControllerTransaction
|
|
4
|
-
SPAN_ORIGIN =
|
|
6
|
+
SPAN_ORIGIN = "auto.view.rails"
|
|
5
7
|
|
|
6
8
|
def self.included(base)
|
|
7
9
|
base.prepend_around_action(:sentry_around_action)
|
|
@@ -21,8 +23,10 @@ module Sentry
|
|
|
21
23
|
child_span.set_http_status(response.status)
|
|
22
24
|
child_span.set_data(:format, request.format)
|
|
23
25
|
child_span.set_data(:method, request.method)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
|
|
27
|
+
pii = Sentry.configuration.send_default_pii
|
|
28
|
+
child_span.set_data(:path, pii ? request.fullpath : request.filtered_path)
|
|
29
|
+
child_span.set_data(:params, pii ? request.params : request.filtered_parameters)
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
result
|
data/lib/sentry/rails/engine.rb
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
ORIGIN = "auto.log.rails.log_subscriber"
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
if ::Rails.version.to_f < 6.0
|
|
34
|
+
# Rails 5.x does not provide detach_from
|
|
35
|
+
def detach_from(namespace, notifications = ActiveSupport::Notifications)
|
|
36
|
+
listeners = public_instance_methods(false)
|
|
37
|
+
.flat_map { |key|
|
|
38
|
+
notifications.notifier.listeners_for("#{key}.#{namespace}")
|
|
39
|
+
}
|
|
40
|
+
.select { |listener| listener.instance_variable_get(:@delegate).is_a?(self) }
|
|
41
|
+
|
|
42
|
+
listeners.map do |listener|
|
|
43
|
+
notifications.notifier.unsubscribe(listener)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
protected
|
|
50
|
+
|
|
51
|
+
# Log a structured event using Sentry's structured logger
|
|
52
|
+
#
|
|
53
|
+
# @param message [String] The log message
|
|
54
|
+
# @param level [Symbol] The log level (:trace, :debug, :info, :warn, :error, :fatal)
|
|
55
|
+
# @param attributes [Hash] Additional structured attributes to include
|
|
56
|
+
# @param origin [String] The origin of the log event
|
|
57
|
+
def log_structured_event(message:, level: :info, attributes: {}, origin: ORIGIN)
|
|
58
|
+
Sentry.logger.public_send(level, message, **attributes, origin: origin)
|
|
59
|
+
rescue => e
|
|
60
|
+
# Silently handle any errors in logging to avoid breaking the application
|
|
61
|
+
Sentry.configuration.sdk_logger.debug("Failed to log structured event: #{e.message}")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Calculate duration in milliseconds from an event
|
|
65
|
+
#
|
|
66
|
+
# @param event [ActiveSupport::Notifications::Event] The event
|
|
67
|
+
# @return [Float] Duration in milliseconds
|
|
68
|
+
def duration_ms(event)
|
|
69
|
+
event.duration.round(2)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
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
|