lib/sentry/rails/action_cable.rb



module Sentry
  module Rails
    module ActionCableExtensions
      class ErrorHandler
        class << self
          def capture(env, transaction_name:, extra_context: nil, &block)
            Sentry.with_scope do |scope|
              scope.set_rack_env(env)
              scope.set_context("action_cable", extra_context) if extra_context
              scope.set_transaction_name(transaction_name)
              transaction = start_transaction(env, scope.transaction_name)
              scope.set_span(transaction) if transaction

              begin
                block.call
                finish_transaction(transaction, 200)
              rescue Exception => e # rubocop:disable Lint/RescueException
                Sentry::Rails.capture_exception(e)
                finish_transaction(transaction, 500)

                raise
              end
            end
          end

          def start_transaction(env, transaction_name)
            sentry_trace = env["HTTP_SENTRY_TRACE"]
            options = { name: transaction_name, op: "rails.action_cable".freeze }
            transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
            Sentry.start_transaction(transaction: transaction, **options)
          end

          def finish_transaction(transaction, status_code)
            return unless transaction

            transaction.set_http_status(status_code)
            transaction.finish
          end
        end
      end

      module Connection
        private

        def handle_open
          ErrorHandler.capture(env, transaction_name: "#{self.class.name}#connect") do
            super
          end
        end

        def handle_close
          ErrorHandler.capture(env, transaction_name: "#{self.class.name}#disconnect") do
            super
          end
        end
      end

      module Channel
        module Subscriptions
          def self.included(base)
            base.class_eval do
              set_callback :subscribe, :around, ->(_, block) { sentry_capture(:subscribed, &block) }, prepend: true
              set_callback :unsubscribe, :around, ->(_, block) { sentry_capture(:unsubscribed, &block) }, prepend: true
            end
          end

          private

          def sentry_capture(hook, &block)
            extra_context = { params: params }

            ErrorHandler.capture(connection.env, transaction_name: "#{self.class.name}##{hook}", extra_context: extra_context, &block)
          end
        end

        module Actions
          private

          def dispatch_action(action, data)
            extra_context = { params: params, data: data }

            ErrorHandler.capture(connection.env, transaction_name: "#{self.class.name}##{action}", extra_context: extra_context) do
              super
            end
          end
        end
      end
    end
  end
end