Class: Sentry::Rails::ActiveJobExtensions::SentryReporter
- Inherits:
-
Object
- Object
- Sentry::Rails::ActiveJobExtensions::SentryReporter
- Extended by:
- ErrorReporterContext
- Defined in:
- lib/sentry/rails/active_job.rb
Constant Summary collapse
- OP_NAME =
"queue.process"- SPAN_ORIGIN =
"auto.queue.active_job"- MESSAGING_SYSTEM_FALLBACK =
Emitted as messaging.system when the configured queue adapter's identity cannot be resolved from the job. ActiveJob is adapter-agnostic and supports arbitrary third-party backends, so the concrete value is normally derived from the adapter itself; this is only the last resort.
"activejob"- EVENT_HANDLERS =
{ "enqueue_retry.active_job" => :retry_handler }
Constants included from ErrorReporterContext
ErrorReporterContext::SUPPORTS_EXECUTION_CONTEXT
Class Method Summary collapse
- .capture_exception(job, e) ⇒ Object
- .compute_latency(job) ⇒ Object
- .detach_event_handlers ⇒ Object
- .finish_sentry_transaction(transaction, status) ⇒ Object
- .handle_error_event(*args) {|event.payload[:job], event.payload[:error]| ... } ⇒ Object
- .log_producer_span_error(e) ⇒ Object
- .messaging_system(job) ⇒ Object
- .producer_callback_registered! ⇒ Object
- .producer_callback_registered? ⇒ Boolean
- .record(job, trace_headers: nil, user: nil, &block) ⇒ Object
- .record_producer_span(job, &enqueue) ⇒ Object
- .register_event_handlers ⇒ Object
-
.retry_count(job) ⇒ Object
Number of retries the job has already gone through, as observed at the moment the span is opened.
-
.retry_handler(*args) ⇒ Object
This handler does not capture error unless
active_job_report_on_retry_erroris true. - .sentry_context(job) ⇒ Object
- .sentry_serialize_arguments(argument) ⇒ Object
- .set_messaging_data(transaction, job) ⇒ Object
Methods included from ErrorReporterContext
Class Method Details
.capture_exception(job, e) ⇒ Object
222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/sentry/rails/active_job.rb', line 222 def capture_exception(job, e) Sentry::Rails.capture_exception( e, extra: sentry_context(job), tags: { job_id: job.job_id, provider_job_id: job.provider_job_id }, contexts: execution_context, # Send synchronously: a worker process may exit before the async # background worker flushes its queue, which would drop the event. hint: { background: false } ) end |
.compute_latency(job) ⇒ Object
215 216 217 218 219 220 |
# File 'lib/sentry/rails/active_job.rb', line 215 def compute_latency(job) return unless job.respond_to?(:enqueued_at) && job.enqueued_at enqueued_time = job.enqueued_at.is_a?(String) ? Time.parse(job.enqueued_at) : job.enqueued_at (Time.now.to_f - enqueued_time.to_f) * 1000.0 end |
.detach_event_handlers ⇒ Object
245 246 247 248 249 250 |
# File 'lib/sentry/rails/active_job.rb', line 245 def detach_event_handlers subscribers.each do |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) end subscribers.clear end |
.finish_sentry_transaction(transaction, status) ⇒ Object
267 268 269 270 271 272 |
# File 'lib/sentry/rails/active_job.rb', line 267 def finish_sentry_transaction(transaction, status) return unless transaction transaction.set_http_status(status) transaction.finish end |
.handle_error_event(*args) {|event.payload[:job], event.payload[:error]| ... } ⇒ Object
262 263 264 265 |
# File 'lib/sentry/rails/active_job.rb', line 262 def handle_error_event(*args) event = ActiveSupport::Notifications::Event.new(*args) yield(event.payload[:job], event.payload[:error]) end |
.log_producer_span_error(e) ⇒ Object
130 131 132 |
# File 'lib/sentry/rails/active_job.rb', line 130 def log_producer_span_error(e) Sentry.sdk_logger&.error("sentry-rails: failed to record producer span: #{e.class}: #{e.}\n #{Array(e.backtrace).first(5).join("\n ")}") end |
.messaging_system(job) ⇒ Object
199 200 201 202 203 |
# File 'lib/sentry/rails/active_job.rb', line 199 def messaging_system(job) name = job.class.queue_adapter_name if job.class.respond_to?(:queue_adapter_name) name = name.to_s name.empty? ? MESSAGING_SYSTEM_FALLBACK : name end |
.producer_callback_registered! ⇒ Object
92 93 94 |
# File 'lib/sentry/rails/active_job.rb', line 92 def producer_callback_registered! @producer_callback_registered = true end |
.producer_callback_registered? ⇒ Boolean
88 89 90 |
# File 'lib/sentry/rails/active_job.rb', line 88 def producer_callback_registered? @producer_callback_registered ||= false end |
.record(job, trace_headers: nil, user: nil, &block) ⇒ Object
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/sentry/rails/active_job.rb', line 134 def record(job, trace_headers: nil, user: nil, &block) original_hub = Sentry.get_current_hub_internal Sentry.clone_hub_to_current_thread Sentry.with_scope do |scope| begin scope.set_user(user.transform_keys(&:to_sym)) if user && !user.empty? scope.set_transaction_name(job.class.name, source: :task) scope.(queue: job.queue_name) scope.set_contexts(active_job: { job_class: job.class.name, job_id: job.job_id, queue: job.queue_name, provider_job_id: job.provider_job_id }) = { name: scope.transaction_name, source: scope.transaction_source, op: OP_NAME, origin: SPAN_ORIGIN } transaction = if trace_headers && !trace_headers.empty? continued = Sentry.continue_trace(trace_headers, **) Sentry.start_transaction(transaction: continued, **) else Sentry.start_transaction(**) end if transaction set_messaging_data(transaction, job) scope.set_span(transaction) end yield.tap do finish_sentry_transaction(transaction, 200) end rescue Exception => e # rubocop:disable Lint/RescueException finish_sentry_transaction(transaction, 500) capture_exception(job, e) raise end end ensure Sentry.set_current_hub_internal(original_hub) end |
.record_producer_span(job, &enqueue) ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/sentry/rails/active_job.rb', line 96 def record_producer_span(job, &enqueue) return yield if !Sentry.initialized? || job.already_supported_by_sentry_integration? enqueued = false run_enqueue = lambda do enqueued = true enqueue.call end begin Sentry.with_child_span(op: "queue.publish", description: job.class.name) do |span| if span span.set_origin(SPAN_ORIGIN) span.set_data(Sentry::Span::DataConventions::MESSAGING_SYSTEM, messaging_system(job)) span.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, job.job_id) span.set_data(Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, job.queue_name) if (count = retry_count(job)) span.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_RETRY_COUNT, count) end end run_enqueue.call end rescue StandardError => e raise if enqueued log_producer_span_error(e) run_enqueue.call end end |
.register_event_handlers ⇒ Object
237 238 239 240 241 242 243 |
# File 'lib/sentry/rails/active_job.rb', line 237 def register_event_handlers EVENT_HANDLERS.each do |name, handler| subscribers << ActiveSupport::Notifications.subscribe(name) do |*args| public_send(handler, *args) end end end |
.retry_count(job) ⇒ Object
Number of retries the job has already gone through, as observed at
the moment the span is opened. On the consumer the span opens before
ActiveJob increments executions, so a job's first attempt reads 0,
its first retry reads 1, and so on. On the producer the retry enqueue
runs after the failed attempt bumped executions, so it observes the
same progression.
211 212 213 |
# File 'lib/sentry/rails/active_job.rb', line 211 def retry_count(job) job.executions.to_i if job.respond_to?(:executions) end |
.retry_handler(*args) ⇒ Object
This handler does not capture error unless active_job_report_on_retry_error is true
253 254 255 256 257 258 259 260 |
# File 'lib/sentry/rails/active_job.rb', line 253 def retry_handler(*args) handle_error_event(*args) do |job, error| return if !Sentry.initialized? || job.already_supported_by_sentry_integration? return unless Sentry.configuration.rails.active_job_report_on_retry_error capture_exception(job, error) end end |
.sentry_context(job) ⇒ Object
274 275 276 277 278 279 280 281 282 283 |
# File 'lib/sentry/rails/active_job.rb', line 274 def sentry_context(job) { active_job: job.class.name, arguments: sentry_serialize_arguments(job.arguments), scheduled_at: job.scheduled_at, job_id: job.job_id, provider_job_id: job.provider_job_id, locale: job.locale } end |
.sentry_serialize_arguments(argument) ⇒ Object
285 286 287 |
# File 'lib/sentry/rails/active_job.rb', line 285 def sentry_serialize_arguments(argument) Sentry::Rails::Serializer.serialize(argument) end |
.set_messaging_data(transaction, job) ⇒ Object
186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/sentry/rails/active_job.rb', line 186 def set_messaging_data(transaction, job) transaction.set_data(Sentry::Span::DataConventions::MESSAGING_SYSTEM, messaging_system(job)) transaction.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, job.job_id) transaction.set_data(Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, job.queue_name) if (count = retry_count(job)) transaction.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_RETRY_COUNT, count) end if (latency = compute_latency(job)) transaction.set_data(Sentry::Span::DataConventions::MESSAGING_MESSAGE_RECEIVE_LATENCY, latency) end end |