sentry-rails 6.6.2 → 7.0.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/.gitignore +1 -1
- data/Gemfile +50 -0
- data/README.md +1 -1
- data/bin/test +12 -386
- data/bin/test_old +389 -0
- data/gemfiles/ruby-2.7_rails-5.2.0.gemfile.lock +266 -0
- data/gemfiles/ruby-2.7_rails-6.0.0.gemfile.lock +282 -0
- data/gemfiles/ruby-2.7_rails-6.1.0.gemfile.lock +294 -0
- data/gemfiles/ruby-2.7_rails-7.0.0.gemfile.lock +311 -0
- data/gemfiles/ruby-2.7_rails-7.1.0.gemfile.lock +348 -0
- data/gemfiles/ruby-3.0_rails-6.1.0.gemfile.lock +362 -0
- data/gemfiles/ruby-3.0_rails-7.0.0.gemfile.lock +379 -0
- data/gemfiles/ruby-3.0_rails-7.1.0.gemfile.lock +405 -0
- data/gemfiles/ruby-3.1_rails-6.1.0.gemfile.lock +493 -0
- data/gemfiles/ruby-3.1_rails-7.0.0.gemfile.lock +513 -0
- data/gemfiles/ruby-3.1_rails-7.1.0.gemfile.lock +547 -0
- data/gemfiles/ruby-3.1_rails-7.2.0.gemfile.lock +541 -0
- data/gemfiles/ruby-3.2_rails-6.1.0.gemfile.lock +493 -0
- data/gemfiles/ruby-3.2_rails-7.0.0.gemfile.lock +511 -0
- data/gemfiles/ruby-3.2_rails-7.1.0.gemfile.lock +553 -0
- data/gemfiles/ruby-3.2_rails-7.2.0.gemfile.lock +545 -0
- data/gemfiles/ruby-3.2_rails-8.0.0.gemfile.lock +544 -0
- data/gemfiles/ruby-3.3_rails-6.1.0.gemfile.lock +493 -0
- data/gemfiles/ruby-3.3_rails-7.0.0.gemfile.lock +511 -0
- data/gemfiles/ruby-3.3_rails-7.1.0.gemfile.lock +553 -0
- data/gemfiles/ruby-3.3_rails-7.2.0.gemfile.lock +545 -0
- data/gemfiles/ruby-3.3_rails-8.0.0.gemfile.lock +544 -0
- data/gemfiles/ruby-3.4_rails-7.1.0.gemfile.lock +534 -0
- data/gemfiles/ruby-3.4_rails-7.2.0.gemfile.lock +528 -0
- data/gemfiles/ruby-3.4_rails-8.0.0.gemfile.lock +523 -0
- data/gemfiles/ruby-3.4_rails-8.1.3.gemfile.lock +527 -0
- data/gemfiles/ruby-4.0_rails-6.1.0.gemfile.lock +476 -0
- data/gemfiles/ruby-4.0_rails-7.0.0.gemfile.lock +492 -0
- data/gemfiles/ruby-4.0_rails-7.1.0.gemfile.lock +534 -0
- data/gemfiles/ruby-4.0_rails-8.0.0.gemfile.lock +523 -0
- data/gemfiles/ruby-4.0_rails-8.1.3.gemfile.lock +527 -0
- data/gemfiles/ruby-jruby-9.4.14.0_rails-6.1.0.gemfile.lock +369 -0
- data/gemfiles/ruby-jruby-9.4.14.0_rails-7.0.0.gemfile.lock +391 -0
- data/gemfiles/ruby-jruby-9.4.14.0_rails-7.1.0.gemfile.lock +425 -0
- data/lib/sentry/rails/active_job.rb +188 -29
- data/lib/sentry/rails/breadcrumb/active_support_logger.rb +30 -0
- data/lib/sentry/rails/capture_exceptions.rb +8 -1
- data/lib/sentry/rails/configuration.rb +14 -2
- data/lib/sentry/rails/controller_transaction.rb +24 -3
- data/lib/sentry/rails/error_reporter_context.rb +24 -0
- data/lib/sentry/rails/log_subscribers/action_controller_subscriber.rb +3 -7
- data/lib/sentry/rails/log_subscribers/action_mailer_subscriber.rb +5 -9
- data/lib/sentry/rails/log_subscribers/active_job_subscriber.rb +5 -7
- data/lib/sentry/rails/log_subscribers/active_record_subscriber.rb +1 -5
- data/lib/sentry/rails/railtie.rb +8 -1
- data/lib/sentry/rails/serializer.rb +35 -0
- data/lib/sentry/rails/tracing/active_storage_subscriber.rb +4 -1
- data/lib/sentry/rails/version.rb +1 -1
- data/sentry-rails.gemspec +2 -2
- metadata +45 -9
- data/lib/sentry/rails/log_subscribers/parameter_filter.rb +0 -52
|
@@ -1,17 +1,66 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "set"
|
|
4
|
+
require "sentry/rails/serializer"
|
|
5
|
+
require "sentry/rails/error_reporter_context"
|
|
4
6
|
|
|
5
7
|
module Sentry
|
|
6
8
|
module Rails
|
|
7
9
|
module ActiveJobExtensions
|
|
10
|
+
SENTRY_PAYLOAD_KEY = "_sentry"
|
|
11
|
+
|
|
12
|
+
USER_FIELDS_ALLOWLIST = %w[id email username].freeze
|
|
13
|
+
|
|
14
|
+
def self.prepended(base)
|
|
15
|
+
base.attr_accessor :_sentry
|
|
16
|
+
end
|
|
17
|
+
|
|
8
18
|
def perform_now
|
|
9
19
|
if !Sentry.initialized? || already_supported_by_sentry_integration?
|
|
10
20
|
super
|
|
11
21
|
else
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
data = _sentry || {}
|
|
23
|
+
SentryReporter.record(
|
|
24
|
+
self,
|
|
25
|
+
trace_headers: data["trace_propagation_headers"],
|
|
26
|
+
user: data["user"]
|
|
27
|
+
) { super }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def serialize
|
|
32
|
+
payload = super
|
|
33
|
+
return payload if !Sentry.initialized? || already_supported_by_sentry_integration?
|
|
34
|
+
|
|
35
|
+
begin
|
|
36
|
+
sentry_data = {}
|
|
37
|
+
if Sentry.configuration.rails.active_job_propagate_traces
|
|
38
|
+
headers = Sentry.get_trace_propagation_headers
|
|
39
|
+
sentry_data["trace_propagation_headers"] = headers if headers && !headers.empty?
|
|
14
40
|
end
|
|
41
|
+
|
|
42
|
+
if Sentry.configuration.data_collection.user_info
|
|
43
|
+
user = Sentry.get_current_scope.user
|
|
44
|
+
allowed = user.transform_keys(&:to_s).slice(*USER_FIELDS_ALLOWLIST)
|
|
45
|
+
sentry_data["user"] = allowed unless allowed.empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
payload[SENTRY_PAYLOAD_KEY] = sentry_data unless sentry_data.empty?
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
Sentry.sdk_logger&.error("sentry-rails: failed to inject _sentry payload: #{e.class}: #{e.message}\n #{Array(e.backtrace).first(5).join("\n ")}")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
payload
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def deserialize(job_data)
|
|
57
|
+
super
|
|
58
|
+
return if !Sentry.initialized? || already_supported_by_sentry_integration?
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
self._sentry = job_data[SENTRY_PAYLOAD_KEY]
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
Sentry.sdk_logger&.error("sentry-rails: failed to extract _sentry payload: #{e.class}: #{e.message}\n #{Array(e.backtrace).first(5).join("\n ")}")
|
|
15
64
|
end
|
|
16
65
|
end
|
|
17
66
|
|
|
@@ -20,27 +69,104 @@ module Sentry
|
|
|
20
69
|
end
|
|
21
70
|
|
|
22
71
|
class SentryReporter
|
|
23
|
-
OP_NAME = "queue.
|
|
72
|
+
OP_NAME = "queue.process"
|
|
24
73
|
SPAN_ORIGIN = "auto.queue.active_job"
|
|
25
74
|
|
|
75
|
+
# Emitted as messaging.system when the configured queue adapter's
|
|
76
|
+
# identity cannot be resolved from the job. ActiveJob is adapter-agnostic
|
|
77
|
+
# and supports arbitrary third-party backends, so the concrete value is
|
|
78
|
+
# normally derived from the adapter itself; this is only the last resort.
|
|
79
|
+
MESSAGING_SYSTEM_FALLBACK = "activejob"
|
|
80
|
+
|
|
26
81
|
EVENT_HANDLERS = {
|
|
27
82
|
"enqueue_retry.active_job" => :retry_handler
|
|
28
83
|
}
|
|
29
84
|
|
|
30
85
|
class << self
|
|
31
|
-
|
|
86
|
+
include ErrorReporterContext
|
|
87
|
+
|
|
88
|
+
def producer_callback_registered?
|
|
89
|
+
@producer_callback_registered ||= false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def producer_callback_registered!
|
|
93
|
+
@producer_callback_registered = true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def record_producer_span(job, &enqueue)
|
|
97
|
+
return yield if !Sentry.initialized? || job.already_supported_by_sentry_integration?
|
|
98
|
+
|
|
99
|
+
enqueued = false
|
|
100
|
+
|
|
101
|
+
run_enqueue = lambda do
|
|
102
|
+
enqueued = true
|
|
103
|
+
enqueue.call
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
begin
|
|
107
|
+
Sentry.with_child_span(op: "queue.publish", description: job.class.name) do |span|
|
|
108
|
+
if span
|
|
109
|
+
span.set_origin(SPAN_ORIGIN)
|
|
110
|
+
span.set_data(Sentry::Span::DataConventions::MESSAGING_SYSTEM, messaging_system(job))
|
|
111
|
+
span.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, job.job_id)
|
|
112
|
+
span.set_data(Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, job.queue_name)
|
|
113
|
+
|
|
114
|
+
if (count = retry_count(job))
|
|
115
|
+
span.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_RETRY_COUNT, count)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
run_enqueue.call
|
|
120
|
+
end
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
raise if enqueued
|
|
123
|
+
|
|
124
|
+
log_producer_span_error(e)
|
|
125
|
+
|
|
126
|
+
run_enqueue.call
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def log_producer_span_error(e)
|
|
131
|
+
Sentry.sdk_logger&.error("sentry-rails: failed to record producer span: #{e.class}: #{e.message}\n #{Array(e.backtrace).first(5).join("\n ")}")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def record(job, trace_headers: nil, user: nil, &block)
|
|
135
|
+
original_hub = Sentry.get_current_hub_internal
|
|
136
|
+
Sentry.clone_hub_to_current_thread
|
|
137
|
+
|
|
32
138
|
Sentry.with_scope do |scope|
|
|
33
139
|
begin
|
|
140
|
+
scope.set_user(user.transform_keys(&:to_sym)) if user && !user.empty?
|
|
141
|
+
|
|
34
142
|
scope.set_transaction_name(job.class.name, source: :task)
|
|
143
|
+
scope.set_tags(queue: job.queue_name)
|
|
35
144
|
|
|
36
|
-
|
|
145
|
+
scope.set_contexts(active_job: {
|
|
146
|
+
job_class: job.class.name,
|
|
147
|
+
job_id: job.job_id,
|
|
148
|
+
queue: job.queue_name,
|
|
149
|
+
provider_job_id: job.provider_job_id
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
transaction_options = {
|
|
37
153
|
name: scope.transaction_name,
|
|
38
154
|
source: scope.transaction_source,
|
|
39
155
|
op: OP_NAME,
|
|
40
156
|
origin: SPAN_ORIGIN
|
|
41
|
-
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
transaction = if trace_headers && !trace_headers.empty?
|
|
160
|
+
continued = Sentry.continue_trace(trace_headers, **transaction_options)
|
|
161
|
+
Sentry.start_transaction(transaction: continued, **transaction_options)
|
|
162
|
+
else
|
|
163
|
+
Sentry.start_transaction(**transaction_options)
|
|
164
|
+
end
|
|
42
165
|
|
|
43
|
-
|
|
166
|
+
if transaction
|
|
167
|
+
set_messaging_data(transaction, job)
|
|
168
|
+
scope.set_span(transaction)
|
|
169
|
+
end
|
|
44
170
|
|
|
45
171
|
yield.tap do
|
|
46
172
|
finish_sentry_transaction(transaction, 200)
|
|
@@ -53,17 +179,60 @@ module Sentry
|
|
|
53
179
|
raise
|
|
54
180
|
end
|
|
55
181
|
end
|
|
182
|
+
ensure
|
|
183
|
+
Sentry.set_current_hub_internal(original_hub)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def set_messaging_data(transaction, job)
|
|
187
|
+
transaction.set_data(Sentry::Span::DataConventions::MESSAGING_SYSTEM, messaging_system(job))
|
|
188
|
+
transaction.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, job.job_id)
|
|
189
|
+
transaction.set_data(Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, job.queue_name)
|
|
190
|
+
if (count = retry_count(job))
|
|
191
|
+
transaction.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_RETRY_COUNT, count)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
if (latency = compute_latency(job))
|
|
195
|
+
transaction.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_RECEIVE_LATENCY, latency)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def messaging_system(job)
|
|
200
|
+
name = job.class.queue_adapter_name if job.class.respond_to?(:queue_adapter_name)
|
|
201
|
+
name = name.to_s
|
|
202
|
+
name.empty? ? MESSAGING_SYSTEM_FALLBACK : name
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Number of retries the job has already gone through, as observed at
|
|
206
|
+
# the moment the span is opened. On the consumer the span opens before
|
|
207
|
+
# ActiveJob increments +executions+, so a job's first attempt reads 0,
|
|
208
|
+
# its first retry reads 1, and so on. On the producer the retry enqueue
|
|
209
|
+
# runs after the failed attempt bumped +executions+, so it observes the
|
|
210
|
+
# same progression.
|
|
211
|
+
def retry_count(job)
|
|
212
|
+
job.executions.to_i if job.respond_to?(:executions)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def compute_latency(job)
|
|
216
|
+
return unless job.respond_to?(:enqueued_at) && job.enqueued_at
|
|
217
|
+
|
|
218
|
+
enqueued_time = job.enqueued_at.is_a?(String) ? Time.parse(job.enqueued_at) : job.enqueued_at
|
|
219
|
+
(Time.now.to_f - enqueued_time.to_f) * 1000.0
|
|
56
220
|
end
|
|
57
221
|
|
|
58
222
|
def capture_exception(job, e)
|
|
59
|
-
|
|
60
|
-
e,
|
|
223
|
+
options = {
|
|
61
224
|
extra: sentry_context(job),
|
|
62
225
|
tags: {
|
|
63
226
|
job_id: job.job_id,
|
|
64
227
|
provider_job_id: job.provider_job_id
|
|
65
|
-
}
|
|
66
|
-
|
|
228
|
+
},
|
|
229
|
+
# Send synchronously: a worker process may exit before the async
|
|
230
|
+
# background worker flushes its queue, which would drop the event.
|
|
231
|
+
hint: { background: false }
|
|
232
|
+
}
|
|
233
|
+
options[:contexts] = execution_context if Sentry.configuration.data_collection.queues
|
|
234
|
+
|
|
235
|
+
Sentry::Rails.capture_exception(e, **options)
|
|
67
236
|
end
|
|
68
237
|
|
|
69
238
|
def register_event_handlers
|
|
@@ -104,33 +273,23 @@ module Sentry
|
|
|
104
273
|
end
|
|
105
274
|
|
|
106
275
|
def sentry_context(job)
|
|
107
|
-
{
|
|
276
|
+
context = {
|
|
108
277
|
active_job: job.class.name,
|
|
109
|
-
arguments: sentry_serialize_arguments(job.arguments),
|
|
110
278
|
scheduled_at: job.scheduled_at,
|
|
111
279
|
job_id: job.job_id,
|
|
112
280
|
provider_job_id: job.provider_job_id,
|
|
113
281
|
locale: job.locale
|
|
114
282
|
}
|
|
283
|
+
|
|
284
|
+
if Sentry.configuration.data_collection.queues
|
|
285
|
+
context[:arguments] = sentry_serialize_arguments(job.arguments)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
context
|
|
115
289
|
end
|
|
116
290
|
|
|
117
291
|
def sentry_serialize_arguments(argument)
|
|
118
|
-
|
|
119
|
-
when Range
|
|
120
|
-
if (argument.begin || argument.end).is_a?(ActiveSupport::TimeWithZone)
|
|
121
|
-
argument.to_s
|
|
122
|
-
else
|
|
123
|
-
argument.map { |v| sentry_serialize_arguments(v) }
|
|
124
|
-
end
|
|
125
|
-
when Hash
|
|
126
|
-
argument.transform_values { |v| sentry_serialize_arguments(v) }
|
|
127
|
-
when Array, Enumerable
|
|
128
|
-
argument.map { |v| sentry_serialize_arguments(v) }
|
|
129
|
-
when ->(v) { v.respond_to?(:to_global_id) }
|
|
130
|
-
argument.to_global_id.to_s rescue argument
|
|
131
|
-
else
|
|
132
|
-
argument
|
|
133
|
-
end
|
|
292
|
+
Sentry::Rails::Serializer.serialize(argument)
|
|
134
293
|
end
|
|
135
294
|
|
|
136
295
|
private
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "sentry/utils/http_tracing"
|
|
4
|
+
|
|
3
5
|
module Sentry
|
|
4
6
|
module Rails
|
|
5
7
|
module Breadcrumb
|
|
6
8
|
module ActiveSupportLogger
|
|
7
9
|
class << self
|
|
10
|
+
include Sentry::Utils::HttpTracing
|
|
8
11
|
def add(name, started, _finished, _unique_id, data)
|
|
12
|
+
return unless Sentry.initialized?
|
|
13
|
+
|
|
9
14
|
# skip Rails' internal events
|
|
10
15
|
return if name.start_with?("!")
|
|
11
16
|
|
|
12
17
|
if data.is_a?(Hash)
|
|
13
18
|
data = data.slice(*@allowed_keys[name])
|
|
19
|
+
filter_data_collection!(data)
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
crumb = Sentry::Breadcrumb.new(
|
|
@@ -35,6 +41,30 @@ module Sentry
|
|
|
35
41
|
def detach
|
|
36
42
|
::ActiveSupport::Notifications.unsubscribe(@subscriber)
|
|
37
43
|
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def filter_data_collection!(data)
|
|
48
|
+
filter_params!(data)
|
|
49
|
+
filter_path!(data)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def filter_params!(data)
|
|
53
|
+
return unless data.key?(:params) && data[:params].is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
data[:params] = Sentry.configuration.data_collection.url_query_params.filter(data[:params])
|
|
56
|
+
data.delete(:params) if data[:params].empty?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def filter_path!(data)
|
|
60
|
+
return unless data.key?(:path) && data[:path].is_a?(String)
|
|
61
|
+
|
|
62
|
+
path, query = data[:path].split("?", 2)
|
|
63
|
+
return unless query
|
|
64
|
+
|
|
65
|
+
filtered_query = filter_query_params(query)
|
|
66
|
+
data[:path] = filtered_query ? "#{path}?#{filtered_query}" : path
|
|
67
|
+
end
|
|
38
68
|
end
|
|
39
69
|
end
|
|
40
70
|
end
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "sentry/rails/error_reporter_context"
|
|
4
|
+
|
|
3
5
|
module Sentry
|
|
4
6
|
module Rails
|
|
5
7
|
class CaptureExceptions < Sentry::Rack::CaptureExceptions
|
|
8
|
+
include ErrorReporterContext
|
|
9
|
+
|
|
6
10
|
RAILS_7_1 = Gem::Version.new(::Rails.version) >= Gem::Version.new("7.1.0.alpha")
|
|
7
11
|
SPAN_ORIGIN = "auto.http.rails"
|
|
8
12
|
|
|
@@ -30,7 +34,10 @@ module Sentry
|
|
|
30
34
|
return unless Sentry.initialized?
|
|
31
35
|
return if show_exceptions?(exception, env) && !Sentry.configuration.rails.report_rescued_exceptions
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
options = {}
|
|
38
|
+
options[:contexts] = execution_context if Sentry.configuration.data_collection.user_info
|
|
39
|
+
|
|
40
|
+
Sentry::Rails.capture_exception(exception, **options).tap do |event|
|
|
34
41
|
env[ERROR_EVENT_ID_KEY] = event.event_id if event
|
|
35
42
|
end
|
|
36
43
|
end
|
|
@@ -35,7 +35,13 @@ module Sentry
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
after(:configured) do
|
|
38
|
-
|
|
38
|
+
collection = data_collection.url_query_params
|
|
39
|
+
if collection.mode == :deny_list
|
|
40
|
+
filter_parameters = ::Rails.application.config.filter_parameters.select do |filter|
|
|
41
|
+
filter.is_a?(String) || filter.is_a?(Symbol) || filter.is_a?(Regexp)
|
|
42
|
+
end
|
|
43
|
+
collection.terms = (Array(collection.terms) + filter_parameters).uniq
|
|
44
|
+
end
|
|
39
45
|
end
|
|
40
46
|
end
|
|
41
47
|
|
|
@@ -172,6 +178,11 @@ module Sentry
|
|
|
172
178
|
# Set this option to true if you want Sentry to capture each retry failure
|
|
173
179
|
attr_accessor :active_job_report_on_retry_error
|
|
174
180
|
|
|
181
|
+
# Whether we should inject trace propagation headers into the serialized job
|
|
182
|
+
# payload in order to have a connected trace between producer and consumer.
|
|
183
|
+
# Defaults to true. Set to false to opt out.
|
|
184
|
+
attr_accessor :active_job_propagate_traces
|
|
185
|
+
|
|
175
186
|
# Configuration for structured logging feature
|
|
176
187
|
# @return [StructuredLoggingConfiguration]
|
|
177
188
|
attr_reader :structured_logging
|
|
@@ -193,6 +204,7 @@ module Sentry
|
|
|
193
204
|
@db_query_source_threshold_ms = 100
|
|
194
205
|
@active_support_logger_subscription_items = Sentry::Rails::ACTIVE_SUPPORT_LOGGER_SUBSCRIPTION_ITEMS_DEFAULT.dup
|
|
195
206
|
@active_job_report_on_retry_error = false
|
|
207
|
+
@active_job_propagate_traces = true
|
|
196
208
|
@structured_logging = StructuredLoggingConfiguration.new
|
|
197
209
|
end
|
|
198
210
|
end
|
|
@@ -212,7 +224,7 @@ module Sentry
|
|
|
212
224
|
}.freeze
|
|
213
225
|
|
|
214
226
|
def initialize
|
|
215
|
-
@enabled =
|
|
227
|
+
@enabled = true
|
|
216
228
|
@subscribers = DEFAULT_SUBSCRIBERS.dup
|
|
217
229
|
end
|
|
218
230
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "sentry/utils/http_tracing"
|
|
4
|
+
|
|
3
5
|
module Sentry
|
|
4
6
|
module Rails
|
|
5
7
|
module ControllerTransaction
|
|
@@ -24,9 +26,28 @@ module Sentry
|
|
|
24
26
|
child_span.set_data(:format, request.format)
|
|
25
27
|
child_span.set_data(:method, request.method)
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
data_collection = Sentry.configuration.data_collection
|
|
30
|
+
|
|
31
|
+
path = request.path
|
|
32
|
+
query_parameters = request.query_parameters
|
|
33
|
+
|
|
34
|
+
if query_parameters.is_a?(Hash)
|
|
35
|
+
filtered_query_parameters = data_collection.url_query_params.filter(query_parameters)
|
|
36
|
+
|
|
37
|
+
unless filtered_query_parameters.empty?
|
|
38
|
+
formatted_query = Sentry::Utils::HttpTracing.format_query(filtered_query_parameters)
|
|
39
|
+
path = "#{path}?#{formatted_query}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
child_span.set_data(:path, path)
|
|
44
|
+
|
|
45
|
+
# all params request + body
|
|
46
|
+
params = request.params
|
|
47
|
+
if params.is_a?(Hash)
|
|
48
|
+
filtered_params = data_collection.url_query_params.filter(params)
|
|
49
|
+
child_span.set_data(:params, filtered_params)
|
|
50
|
+
end
|
|
30
51
|
end
|
|
31
52
|
|
|
32
53
|
result
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sentry/rails/serializer"
|
|
4
|
+
|
|
5
|
+
module Sentry
|
|
6
|
+
module Rails
|
|
7
|
+
module ErrorReporterContext
|
|
8
|
+
SUPPORTS_EXECUTION_CONTEXT = Gem::Version.new(::Rails.version) >= Gem::Version.new("7.0.0")
|
|
9
|
+
|
|
10
|
+
if SUPPORTS_EXECUTION_CONTEXT
|
|
11
|
+
def execution_context
|
|
12
|
+
context = ::ActiveSupport::ExecutionContext.to_h
|
|
13
|
+
return {} if context.empty?
|
|
14
|
+
|
|
15
|
+
{ "rails.error" => Sentry::Rails::Serializer.serialize(context) }
|
|
16
|
+
end
|
|
17
|
+
else
|
|
18
|
+
def execution_context
|
|
19
|
+
{}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "sentry/rails/log_subscriber"
|
|
4
|
-
require "sentry/rails/log_subscribers/parameter_filter"
|
|
5
4
|
|
|
6
5
|
module Sentry
|
|
7
6
|
module Rails
|
|
@@ -16,13 +15,10 @@ module Sentry
|
|
|
16
15
|
# @example Usage
|
|
17
16
|
# # Enable structured logging for ActionController
|
|
18
17
|
# Sentry.init do |config|
|
|
19
|
-
# config.enable_logs = true
|
|
20
18
|
# config.rails.structured_logging = true
|
|
21
19
|
# config.rails.structured_logging.subscribers = { action_controller: Sentry::Rails::LogSubscribers::ActionControllerSubscriber }
|
|
22
20
|
# end
|
|
23
21
|
class ActionControllerSubscriber < Sentry::Rails::LogSubscriber
|
|
24
|
-
include ParameterFilter
|
|
25
|
-
|
|
26
22
|
# Handle process_action.action_controller events
|
|
27
23
|
#
|
|
28
24
|
# @param event [ActiveSupport::Notifications::Event] The controller action event
|
|
@@ -55,9 +51,9 @@ module Sentry
|
|
|
55
51
|
attributes[:db_runtime_ms] = payload[:db_runtime].round(2)
|
|
56
52
|
end
|
|
57
53
|
|
|
58
|
-
if
|
|
59
|
-
|
|
60
|
-
attributes[:params] =
|
|
54
|
+
if payload[:params].is_a?(Hash)
|
|
55
|
+
params = Sentry.configuration.data_collection.url_query_params.filter(payload[:params])
|
|
56
|
+
attributes[:params] = params unless params.empty?
|
|
61
57
|
end
|
|
62
58
|
|
|
63
59
|
level = level_for_request(payload)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "sentry/rails/log_subscriber"
|
|
4
|
-
require "sentry/rails/log_subscribers/parameter_filter"
|
|
5
4
|
|
|
6
5
|
module Sentry
|
|
7
6
|
module Rails
|
|
@@ -15,13 +14,10 @@ module Sentry
|
|
|
15
14
|
# @example Usage
|
|
16
15
|
# # Enable structured logging for ActionMailer
|
|
17
16
|
# Sentry.init do |config|
|
|
18
|
-
# config.enable_logs = true
|
|
19
17
|
# config.rails.structured_logging = true
|
|
20
18
|
# config.rails.structured_logging.subscribers = { action_mailer: Sentry::Rails::LogSubscribers::ActionMailerSubscriber }
|
|
21
19
|
# end
|
|
22
20
|
class ActionMailerSubscriber < Sentry::Rails::LogSubscriber
|
|
23
|
-
include ParameterFilter
|
|
24
|
-
|
|
25
21
|
# Handle deliver.action_mailer events
|
|
26
22
|
#
|
|
27
23
|
# @param event [ActiveSupport::Notifications::Event] The email delivery event
|
|
@@ -41,8 +37,8 @@ module Sentry
|
|
|
41
37
|
attributes[:delivery_method] = payload[:delivery_method] if payload[:delivery_method]
|
|
42
38
|
attributes[:date] = payload[:date].to_s if payload[:date]
|
|
43
39
|
|
|
44
|
-
if Sentry.configuration.
|
|
45
|
-
attributes[:message_id] = payload[:message_id]
|
|
40
|
+
if Sentry.configuration.data_collection.user_info && payload[:message_id]
|
|
41
|
+
attributes[:message_id] = payload[:message_id]
|
|
46
42
|
end
|
|
47
43
|
|
|
48
44
|
message = "Email delivered via #{mailer}"
|
|
@@ -73,9 +69,9 @@ module Sentry
|
|
|
73
69
|
duration_ms: duration
|
|
74
70
|
}
|
|
75
71
|
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
attributes[:params] =
|
|
72
|
+
if payload[:params].is_a?(Hash)
|
|
73
|
+
params = Sentry.configuration.data_collection.url_query_params.filter(payload[:params])
|
|
74
|
+
attributes[:params] = params unless params.empty?
|
|
79
75
|
end
|
|
80
76
|
|
|
81
77
|
message = "#{mailer}##{action}"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "sentry/rails/log_subscriber"
|
|
4
|
-
require "sentry/rails/log_subscribers/parameter_filter"
|
|
5
4
|
|
|
6
5
|
module Sentry
|
|
7
6
|
module Rails
|
|
@@ -15,13 +14,10 @@ module Sentry
|
|
|
15
14
|
# @example Usage
|
|
16
15
|
# # Enable structured logging for ActiveJob
|
|
17
16
|
# Sentry.init do |config|
|
|
18
|
-
# config.enable_logs = true
|
|
19
17
|
# config.rails.structured_logging = true
|
|
20
18
|
# config.rails.structured_logging.subscribers = { active_job: Sentry::Rails::LogSubscribers::ActiveJobSubscriber }
|
|
21
19
|
# end
|
|
22
20
|
class ActiveJobSubscriber < Sentry::Rails::LogSubscriber
|
|
23
|
-
include ParameterFilter
|
|
24
|
-
|
|
25
21
|
# Handle perform.active_job events
|
|
26
22
|
#
|
|
27
23
|
# @param event [ActiveSupport::Notifications::Event] The job performance event
|
|
@@ -47,7 +43,7 @@ module Sentry
|
|
|
47
43
|
attributes[:delay_ms] = ((Time.current - job.scheduled_at) * 1000).round(2)
|
|
48
44
|
end
|
|
49
45
|
|
|
50
|
-
if Sentry.configuration.
|
|
46
|
+
if Sentry.configuration.data_collection.queues && job.arguments.present?
|
|
51
47
|
filtered_args = filter_sensitive_arguments(job.arguments)
|
|
52
48
|
attributes[:arguments] = filtered_args unless filtered_args.empty?
|
|
53
49
|
end
|
|
@@ -149,9 +145,11 @@ module Sentry
|
|
|
149
145
|
arguments.map do |arg|
|
|
150
146
|
case arg
|
|
151
147
|
when Hash
|
|
152
|
-
|
|
148
|
+
# we're using url_query_params here since rails filter_parameters end up there
|
|
149
|
+
# and we don't have a dedicated queue params config yet
|
|
150
|
+
Sentry.configuration.data_collection.url_query_params.filter(arg, cookie: false)
|
|
153
151
|
when String
|
|
154
|
-
arg.length > 100 ? "[
|
|
152
|
+
arg.length > 100 ? "[Filtered: #{arg.length} chars]" : arg
|
|
155
153
|
else
|
|
156
154
|
arg
|
|
157
155
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "sentry/rails/log_subscriber"
|
|
4
|
-
require "sentry/rails/log_subscribers/parameter_filter"
|
|
5
4
|
|
|
6
5
|
module Sentry
|
|
7
6
|
module Rails
|
|
@@ -16,13 +15,10 @@ module Sentry
|
|
|
16
15
|
# @example Usage
|
|
17
16
|
# # Automatically attached when structured logging is enabled for :active_record
|
|
18
17
|
# Sentry.init do |config|
|
|
19
|
-
# config.enable_logs = true
|
|
20
18
|
# config.rails.structured_logging = true
|
|
21
19
|
# config.rails.structured_logging.subscribers = { active_record: Sentry::Rails::LogSubscribers::ActiveRecordSubscriber }
|
|
22
20
|
# end
|
|
23
21
|
class ActiveRecordSubscriber < Sentry::Rails::LogSubscriber
|
|
24
|
-
include ParameterFilter
|
|
25
|
-
|
|
26
22
|
EXCLUDED_NAMES = ["SCHEMA", "TRANSACTION"].freeze
|
|
27
23
|
EMPTY_ARRAY = [].freeze
|
|
28
24
|
|
|
@@ -49,7 +45,7 @@ module Sentry
|
|
|
49
45
|
|
|
50
46
|
binds = event.payload[:binds]
|
|
51
47
|
|
|
52
|
-
if Sentry.configuration.
|
|
48
|
+
if Sentry.configuration.data_collection.database_query_data && (binds && !binds.empty?)
|
|
53
49
|
type_casted_binds = type_casted_binds(event)
|
|
54
50
|
|
|
55
51
|
type_casted_binds.each_with_index do |value, index|
|
data/lib/sentry/rails/railtie.rb
CHANGED
|
@@ -21,6 +21,13 @@ module Sentry
|
|
|
21
21
|
ActiveSupport.on_load(:active_job) do
|
|
22
22
|
require "sentry/rails/active_job"
|
|
23
23
|
prepend Sentry::Rails::ActiveJobExtensions
|
|
24
|
+
|
|
25
|
+
unless Sentry::Rails::ActiveJobExtensions::SentryReporter.producer_callback_registered?
|
|
26
|
+
around_enqueue do |job, block|
|
|
27
|
+
Sentry::Rails::ActiveJobExtensions::SentryReporter.record_producer_span(job, &block)
|
|
28
|
+
end
|
|
29
|
+
Sentry::Rails::ActiveJobExtensions::SentryReporter.producer_callback_registered!
|
|
30
|
+
end
|
|
24
31
|
end
|
|
25
32
|
end
|
|
26
33
|
|
|
@@ -135,7 +142,7 @@ module Sentry
|
|
|
135
142
|
end
|
|
136
143
|
|
|
137
144
|
def activate_structured_logging
|
|
138
|
-
if Sentry.configuration.rails.structured_logging.enabled?
|
|
145
|
+
if Sentry.configuration.rails.structured_logging.enabled?
|
|
139
146
|
Sentry::Rails::StructuredLogging.attach(Sentry.configuration.rails.structured_logging)
|
|
140
147
|
end
|
|
141
148
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Rails
|
|
5
|
+
module Serializer
|
|
6
|
+
def self.serialize(value)
|
|
7
|
+
case value
|
|
8
|
+
when Range
|
|
9
|
+
serialize_range(value)
|
|
10
|
+
when Hash
|
|
11
|
+
value.transform_values { |item| serialize(item) }
|
|
12
|
+
when Array, Enumerable
|
|
13
|
+
value.map { |item| serialize(item) }
|
|
14
|
+
when ->(item) { item.respond_to?(:to_global_id) }
|
|
15
|
+
serialize_global_id(value)
|
|
16
|
+
else
|
|
17
|
+
value
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.serialize_global_id(value)
|
|
22
|
+
value.to_global_id.to_s
|
|
23
|
+
rescue StandardError
|
|
24
|
+
value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.serialize_range(range)
|
|
28
|
+
return range.to_s if range.begin.nil? || range.end.nil?
|
|
29
|
+
return range.to_s if range.begin.is_a?(::ActiveSupport::TimeWithZone)
|
|
30
|
+
|
|
31
|
+
range.map { |item| serialize(item) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|