class Appsignal::Transaction

def _set_error(error)

def _set_error(error)
  backtrace = cleaned_backtrace(error.backtrace)
  @ext.set_error(
    error.class.name,
    cleaned_error_message(error),
    backtrace ? Appsignal::Utils::Data.generate(backtrace) : Appsignal::Extension.data_array_new
  )
  @error_set = error
  root_cause_missing = false
  causes = []
  while error
    error = error.cause
    break unless error
    if causes.length >= ERROR_CAUSES_LIMIT
      Appsignal.internal_logger.debug "Appsignal::Transaction#add_error: Error has more " \
        "than #{ERROR_CAUSES_LIMIT} error causes. Only the first #{ERROR_CAUSES_LIMIT} " \
        "will be reported."
      root_cause_missing = true
      break
    end
    causes << error
  end
  causes_sample_data = causes.map do |e|
    {
      :name => e.class.name,
      :message => cleaned_error_message(e),
      :first_line => first_formatted_backtrace_line(e)
    }
  end
  causes_sample_data.last[:is_root_cause] = false if root_cause_missing
  set_sample_data(
    "error_causes",
    causes_sample_data
  )
end

def add_breadcrumb(category, action, message = "", metadata = {}, time = Time.now.utc)

Other tags:
    See: https://docs.appsignal.com/ruby/instrumentation/breadcrumbs.html -
    See: Appsignal.add_breadcrumb -

Returns:
  • (void) -

Parameters:
  • time (Time) -- time of breadcrumb, should respond to `.to_i` defaults to `Time.now.utc`
  • metadata (Hash) -- key/value metadata in format
  • message (String) -- optional message in string format
  • action (String) -- name of breadcrumb
  • category (String) -- category of breadcrumb
def add_breadcrumb(category, action, message = "", metadata = {}, time = Time.now.utc)
  unless metadata.is_a? Hash
    Appsignal.internal_logger.error "add_breadcrumb: Cannot add breadcrumb. " \
      "The given metadata argument is not a Hash."
    return
  end
  @breadcrumbs.push(
    :time => time.to_i,
    :category => category,
    :action => action,
    :message => message,
    :metadata => metadata
  )
  @breadcrumbs = @breadcrumbs.last(BREADCRUMB_LIMIT)
end

def add_custom_data(data)

Other tags:
    See: https://docs.appsignal.com/guides/custom-data/sample-data.html -
    See: Helpers::Instrumentation#add_custom_data -

Returns:
  • (void) -

Parameters:
  • data (Hash, Array) -- Custom data to add to
    Other tags:
      Since: - 4.0.0
    def add_custom_data(data)
      @custom_data.add(data)
    end

    def add_error(error, &block)

    Other tags:
      See: Appsignal::Helpers::Instrumentation#report_error -
    def add_error(error, &block)
      unless error.is_a?(Exception)
        Appsignal.internal_logger.error "Appsignal::Transaction#add_error: Cannot add error. " \
          "The given value is not an exception: #{error.inspect}"
        return
      end
      return unless error
      return unless Appsignal.active?
      if error.instance_variable_get(:@__appsignal_error_reported) && !@error_blocks.include?(error)
        return
      end
      internal_set_error(error, &block)
      # Mark errors and their causes as tracked so we don't report duplicates,
      # but also not error causes if the wrapper error is already reported.
      while error
        error.instance_variable_set(:@__appsignal_error_reported, true) unless error.frozen?
        error = error.cause
      end
    end

    def add_headers(given_headers = nil, &block)

    Other tags:
      See: https://docs.appsignal.com/guides/custom-data/sample-data.html -
      See: Helpers::Instrumentation#add_headers -

    Returns:
    • (void) -

    Other tags:
      Yieldreturn: -

    Other tags:
      Yield: - This block is called when the transaction is sampled. The block's

    Parameters:
    • given_headers (Hash) -- A hash containing headers.

    Other tags:
      Since: - 4.0.0
    def add_headers(given_headers = nil, &block)
      @headers.add(given_headers, &block)
    end

    def add_headers_if_nil(given_headers = nil, &block)

    Other tags:
      See: https://docs.appsignal.com/guides/custom-data/sample-data.html -
      See: #add_headers -

    Returns:
    • (void) -

    Other tags:
      Yieldreturn: -

    Other tags:
      Yield: - This block is called when the transaction is sampled. The block's

    Parameters:
    • given_headers (Hash) -- A hash containing headers.

    Other tags:
      Since: - 4.0.0
    def add_headers_if_nil(given_headers = nil, &block)
      add_headers(given_headers, &block) unless @headers.value?
    end

    def add_params(given_params = nil, &block)

    Other tags:
      See: https://docs.appsignal.com/guides/custom-data/sample-data.html -
      See: Helpers::Instrumentation#add_params -

    Returns:
    • (void) -

    Other tags:
      Yieldreturn: -

    Other tags:
      Yield: - This block is called when the transaction is sampled. The block's

    Parameters:
    • given_params (Hash, Array) -- The parameters to set on the
      Other tags:
        Since: - 4.0.0
      def add_params(given_params = nil, &block)
        @params.add(given_params, &block)
      end

      def add_params_if_nil(given_params = nil, &block)

      Other tags:
        See: #add_params -

      Returns:
      • (void) -

      Other tags:
        Yieldreturn: -

      Other tags:
        Yield: - This block is called when the transaction is sampled. The block's

      Parameters:
      • given_params (Hash, Array) -- The parameters to set on the
        Other tags:
          Since: - 4.0.0
        def add_params_if_nil(given_params = nil, &block)
          add_params(given_params, &block) if !@params.value? && !@params.empty?
        end

        def add_session_data(given_session_data = nil, &block)

        Other tags:
          See: https://docs.appsignal.com/guides/custom-data/sample-data.html -
          See: Helpers::Instrumentation#add_session_data -

        Returns:
        • (void) -

        Other tags:
          Yieldreturn: -

        Other tags:
          Yield: - This block is called when the transaction is sampled. The block's

        Parameters:
        • given_session_data (Hash) -- A hash containing session data.

        Other tags:
          Since: - 4.0.0
        def add_session_data(given_session_data = nil, &block)
          @session_data.add(given_session_data, &block)
        end

        def add_session_data_if_nil(given_session_data = nil, &block)

        Other tags:
          See: https://docs.appsignal.com/guides/custom-data/sample-data.html -
          See: #add_session_data -

        Returns:
        • (void) -

        Other tags:
          Yieldreturn: -

        Other tags:
          Yield: - This block is called when the transaction is sampled. The block's

        Parameters:
        • given_session_data (Hash) -- A hash containing session data.

        Other tags:
          Since: - 4.0.0
        def add_session_data_if_nil(given_session_data = nil, &block)
          add_session_data(given_session_data, &block) unless @session_data.value?
        end

        def add_tags(given_tags = {})

        Other tags:
          See: https://docs.appsignal.com/ruby/instrumentation/tagging.html -
          See: Helpers::Instrumentation#add_tags -

        Returns:
        • (void) -

        Options Hash: (**given_tags)
        • "any" (String, Symbol, Integer) --
        • :any (String, Symbol, Integer) --

        Parameters:
        • given_tags (Hash) -- Collection of tags.

        Other tags:
          Since: - 4.0.0
        def add_tags(given_tags = {})
          @tags.merge!(given_tags)
        end

        def after_create(&block)

        Returns:
        • (Array) -
        def after_create(&block)
          @after_create ||= Set.new
          return @after_create if block.nil?
          @after_create << block
        end

        def before_complete(&block)

        Returns:
        • (Array) -
        def before_complete(&block)
          @before_complete ||= Set.new
          return @before_complete if block.nil?
          @before_complete << block
        end

        def cleaned_backtrace(backtrace)

        def cleaned_backtrace(backtrace)
          if defined?(::Rails) && Rails.respond_to?(:backtrace_cleaner) && backtrace
            ::Rails.backtrace_cleaner.clean(backtrace, nil)
          else
            backtrace
          end
        end

        def cleaned_error_message(error)

        Returns an unchanged message otherwise.
        Clean error messages that are known to potentially contain user data.
        def cleaned_error_message(error)
          case error.class.to_s
          when "PG::UniqueViolation", "ActiveRecord::RecordNotUnique"
            error.message.to_s.gsub(/\)=\(.*\)/, ")=(?)")
          else
            error.message.to_s
          end
        end

        def clear_current_transaction!

        @!visibility private
        Remove current transaction from current Thread.
        def clear_current_transaction!
          Thread.current[:appsignal_transaction] = nil
        end

        def complete

        @!visibility private
        def complete
          if discarded?
            Appsignal.internal_logger.debug "Skipping transaction '#{transaction_id}' " \
              "because it was manually discarded."
            return
          end
          # If the transaction is a duplicate, we don't want to finish it,
          # because we want its finish time to be the finish time of the
          # original transaction.
          # Duplicate transactions should always be sampled, as we only
          # create duplicates for errors, which are always sampled.
          should_sample = true
          unless duplicate?
            self.class.last_errors = @error_blocks.keys
            should_sample = @ext.finish(0)
          end
          @error_blocks.each do |error, blocks|
            # Ignore the error that is already set in this transaction.
            next if error == @error_set
            duplicate.tap do |transaction|
              # In the duplicate transaction for each error, set an error
              # with a block that calls all the blocks set for that error
              # in the original transaction.
              transaction.internal_set_error(error) do
                blocks.each { |block| block.call(transaction) }
              end
              transaction.complete
            end
          end
          if @error_set && @error_blocks[@error_set].any?
            self.class.with_transaction(self) do
              @error_blocks[@error_set].each do |block|
                block.call(self)
              end
            end
          end
          run_before_complete_hooks
          sample_data if should_sample
          @completed = true
          @ext.complete
        end

        def complete_current!

        Returns:
        • (void) -
        def complete_current!
          current.complete
        rescue => e
          Appsignal.internal_logger.error(
            "Failed to complete transaction ##{current.transaction_id}. #{e.message}"
          )
        ensure
          clear_current_transaction!
        end

        def completed?

        @!visibility private
        def completed?
          @completed
        end

        def create(namespace)

        Returns:
        • (Transaction) -

        Parameters:
        • namespace (String) -- Namespace of the to be created transaction.
        def create(namespace)
          # Reset the transaction if it was already completed but not cleared
          if Thread.current[:appsignal_transaction]&.completed?
            Thread.current[:appsignal_transaction] = nil
          end
          if Thread.current[:appsignal_transaction].nil?
            # If not, start a new transaction
            set_current_transaction(Appsignal::Transaction.new(namespace))
          else
            transaction = current
            # Otherwise, log the issue about trying to start another transaction
            Appsignal.internal_logger.warn(
              "Trying to start new transaction, but a transaction " \
                "with id '#{transaction.transaction_id}' is already running. " \
                "Using transaction '#{transaction.transaction_id}'."
            )
            # And return the current transaction instead
            transaction
          end
        end

        def current

        Returns:
        • (Appsignal::Transaction, Appsignal::Transaction::NilTransaction) -

        Other tags:
          See: .current? -
        def current
          Thread.current[:appsignal_transaction] || NilTransaction.new
        end

        def current?

        Returns:
        • (Boolean) -

        Other tags:
          See: .current -
        def current?
          current && !current.nil_transaction?
        end

        def custom_data

        def custom_data
          @custom_data.value
        rescue => e
          Appsignal.internal_logger.error("Exception while fetching custom data: #{e.class}: #{e}")
          nil
        end

        def discard!

        @!visibility private
        def discard!
          @discarded = true
        end

        def discarded?

        @!visibility private
        def discarded?
          @discarded == true
        end

        def duplicate

        def duplicate
          new_transaction_id = SecureRandom.uuid
          self.class.new(
            namespace,
            :id => new_transaction_id,
            :ext => @ext.duplicate(new_transaction_id)
          ).tap do |transaction|
            transaction.is_duplicate = true
            transaction.tags = @tags.dup
            transaction.custom_data = @custom_data.dup
            transaction.breadcrumbs = @breadcrumbs.dup
            transaction.params = @params.dup
            transaction.session_data = @session_data.dup
            transaction.headers = @headers.dup
          end
        end

        def duplicate?

        @!visibility private
        def duplicate?
          @is_duplicate
        end

        def finish_event(name, title, body, body_format = Appsignal::EventFormatter::DEFAULT)

        Other tags:
          See: Helpers::Instrumentation#instrument -
        def finish_event(name, title, body, body_format = Appsignal::EventFormatter::DEFAULT)
          return if paused?
          @ext.finish_event(
            name,
            title || BLANK,
            body || BLANK,
            body_format || Appsignal::EventFormatter::DEFAULT,
            0
          )
        end

        def first_formatted_backtrace_line(error)

        def first_formatted_backtrace_line(error)
          backtrace = cleaned_backtrace(error.backtrace)
          first_line = backtrace&.first
          return unless first_line
          captures = BACKTRACE_REGEX.match(first_line)
          return unless captures
          captures.named_captures
            .merge("original" => first_line)
            .tap do |c|
              config = Appsignal.config
              # Strip of whitespace at the end of the gem name
              c["gem"] = c["gem"]&.strip
              # Strip the app path from the path if present
              root_path = config.root_path
              if c["path"].start_with?(root_path)
                c["path"].delete_prefix!(root_path)
                # Relative paths shouldn't start with a slash
                c["path"].delete_prefix!("/")
              end
              # Add revision for linking to the repository from the UI
              c["revision"] = config[:revision]
              # Convert line number to an integer
              c["line"] = c["line"].to_i
            end
        end

        def initialize(namespace, id: SecureRandom.uuid, ext: nil)

        Other tags:
          See: create -

        Parameters:
        • namespace (String) -- Namespace of the to be created transaction.
        def initialize(namespace, id: SecureRandom.uuid, ext: nil)
          @transaction_id = id
          @action = nil
          @namespace = namespace
          @paused = false
          @discarded = false
          @completed = false
          @tags = {}
          @breadcrumbs = []
          @store = Hash.new { |hash, key| hash[key] = {} }
          @error_blocks = Hash.new { |hash, key| hash[key] = [] }
          @is_duplicate = false
          @error_set = nil
          @params = Appsignal::SampleData.new(:params)
          @session_data = Appsignal::SampleData.new(:session_data, Hash)
          @headers = Appsignal::SampleData.new(:headers, Hash)
          @custom_data = Appsignal::SampleData.new(:custom_data)
          @ext = ext || Appsignal::Extension.start_transaction(
            @transaction_id,
            @namespace,
            0
          ) || Appsignal::Extension::MockTransaction.new
          run_after_create_hooks
        end

        def instrument(name, title = nil, body = nil, body_format = Appsignal::EventFormatter::DEFAULT)

        Other tags:
          See: Helpers::Instrumentation#instrument -
        def instrument(name, title = nil, body = nil, body_format = Appsignal::EventFormatter::DEFAULT)
          start_event
          yield if block_given?
        ensure
          finish_event(name, title, body, body_format)
        end

        def internal_set_error(error, &block)

        @!visibility private
        def internal_set_error(error, &block)
          _set_error(error) if @error_blocks.empty?
          if !@error_blocks.include?(error) && @error_blocks.length >= ERRORS_LIMIT
            Appsignal.internal_logger.warn "Appsignal::Transaction#add_error: Transaction has more " \
              "than #{ERRORS_LIMIT} distinct errors. Only the first " \
              "#{ERRORS_LIMIT} distinct errors will be reported."
            return
          end
          @error_blocks[error] << block
          @error_blocks[error].compact!
        end

        def last_errors

        @!visibility private
        def last_errors
          @last_errors ||= []
        end

        def nil_transaction?

        @!visibility private
        def nil_transaction?
          false
        end

        def params

        def params
          @params.value
        rescue => e
          Appsignal.internal_logger.error("Exception while fetching params: #{e.class}: #{e}")
          nil
        end

        def pause!

        @!visibility private
        def pause!
          @paused = true
        end

        def paused?

        @!visibility private
        def paused?
          @paused == true
        end

        def record_event(name, title, body, duration, body_format = Appsignal::EventFormatter::DEFAULT)

        Other tags:
          See: Helpers::Instrumentation#instrument -
        def record_event(name, title, body, duration, body_format = Appsignal::EventFormatter::DEFAULT)
          return if paused?
          @ext.record_event(
            name,
            title || BLANK,
            body || BLANK,
            body_format || Appsignal::EventFormatter::DEFAULT,
            duration,
            0
          )
        end

        def request_headers

        def request_headers
          @headers.value
        rescue => e
          Appsignal.internal_logger.error \
            "Exception while fetching headers: #{e.class}: #{e}"
          nil
        end

        def restore!

        @!visibility private
        def restore!
          @discarded = false
        end

        def resume!

        @!visibility private
        def resume!
          @paused = false
        end

        def run_after_create_hooks

        def run_after_create_hooks
          self.class.after_create.each do |block|
            block.call(self)
          end
        end

        def run_before_complete_hooks

        def run_before_complete_hooks
          self.class.before_complete.each do |block|
            block.call(self, @error_set)
          end
        end

        def sample_data

        def sample_data
          {
            :params => sanitized_params,
            :environment => sanitized_request_headers,
            :session_data => sanitized_session_data,
            :tags => sanitized_tags,
            :breadcrumbs => breadcrumbs,
            :custom_data => custom_data
          }.each do |key, data|
            set_sample_data(key, data)
          end
        end

        def sanitized_params

        def sanitized_params
          return unless Appsignal.config[:send_params]
          filter_keys = Appsignal.config[:filter_parameters] || []
          Appsignal::Utils::SampleDataSanitizer.sanitize(params, filter_keys)
        end

        def sanitized_request_headers

        Returns:
        • (Hash) -
        • (nil) - if no environment is present.
        def sanitized_request_headers
          headers = request_headers
          return unless headers
          {}.tap do |out|
            Appsignal.config[:request_headers].each do |key|
              out[key] = headers[key] if headers[key]
            end
          end
        end

        def sanitized_session_data

        Returns:
        • (Hash) -
        • (nil) - if the {#request} session data is `nil`.
        • (nil) - if the {#request} object doesn't respond to `#session`.
        • (nil) - if `:send_session_data` config is set to `false`.
        def sanitized_session_data
          return unless Appsignal.config[:send_session_data]
          Appsignal::Utils::SampleDataSanitizer.sanitize(
            session_data,
            Appsignal.config[:filter_session_data]
          )
        end

        def sanitized_tags

        Other tags:
          See: https://docs.appsignal.com/ruby/instrumentation/tagging.html -
        def sanitized_tags
          @tags.select do |key, value|
            ALLOWED_TAG_KEY_TYPES.any? { |type| key.is_a? type } &&
              ALLOWED_TAG_VALUE_TYPES.any? { |type| value.is_a? type }
          end
        end

        def session_data

        def session_data
          @session_data.value
        rescue => e
          Appsignal.internal_logger.error \
            "Exception while fetching session data: #{e.class}: #{e}"
          nil
        end

        def set_action(action)

        Other tags:
          See: Appsignal::Helpers::Instrumentation#set_action -

        Returns:
        • (void) -

        Parameters:
        • action (String) -- the action name to set.

        Other tags:
          Since: - 2.2.0
        def set_action(action)
          return unless action
          @action = action
          @ext.set_action(action)
        end

        def set_action_if_nil(action)

        Other tags:
          See: #set_action -

        Returns:
        • (void) -

        Parameters:
        • action (String) --

        Other tags:
          Since: - 2.2.0
        def set_action_if_nil(action)
          return if @action
          set_action(action)
        end

        def set_current_transaction(transaction)

        @!visibility private
        def set_current_transaction(transaction)
          Thread.current[:appsignal_transaction] = transaction
        end

        def set_empty_params!

        Other tags:
          See: Helpers::Instrumentation#set_empty_params! -

        Returns:
        • (void) -

        Other tags:
          Since: - 4.0.0
        def set_empty_params!
          @params.set_empty_value!
        end

        def set_metadata(key, value)

        @!visibility private
        def set_metadata(key, value)
          return unless key && value
          return if Appsignal.config[:filter_metadata].include?(key.to_s)
          @ext.set_metadata(key, value)
        end

        def set_namespace(namespace)

        Other tags:
          See: https://docs.appsignal.com/guides/namespaces.html -
          See: Appsignal::Helpers::Instrumentation#set_namespace -

        Returns:
        • (void) -

        Parameters:
        • namespace (String) -- namespace name to use for this transaction.

        Other tags:
          Since: - 2.2.0
        def set_namespace(namespace)
          return unless namespace
          @namespace = namespace
          @ext.set_namespace(namespace)
        end

        def set_queue_start(start)

        Returns:
        • (void) -

        Raises:
        • (TypeError) - Raises a TypeError when the given `start` argument is
        • (RangeError) - When the queue start time value is too big, this

        Parameters:
        • start (Integer) -- Queue start time in milliseconds.
        def set_queue_start(start)
          return unless start
          @ext.set_queue_start(start)
        rescue RangeError
          Appsignal.internal_logger.warn("Queue start value #{start} is too big")
        end

        def set_sample_data(key, data)

        def set_sample_data(key, data)
          return unless key && data
          if !data.is_a?(Array) && !data.is_a?(Hash)
            Appsignal.internal_logger.error(
              "Invalid sample data for '#{key}'. Value is not an Array or Hash: '#{data.inspect}'"
            )
            return
          end
          @ext.set_sample_data(
            key.to_s,
            Appsignal::Utils::Data.generate(data)
          )
        rescue RuntimeError => e
          begin
            inspected_data = data.inspect
            Appsignal.internal_logger.error(
              "Error generating data (#{e.class}: #{e.message}) for '#{inspected_data}'"
            )
          rescue => e
            Appsignal.internal_logger.error(
              "Error generating data (#{e.class}: #{e.message}). Can't inspect data."
            )
          end
        end

        def start_event

        Other tags:
          See: Helpers::Instrumentation#instrument -
        def start_event
          return if paused?
          @ext.start_event(0)
        end

        def store(key)

        @!visibility private
        def store(key)
          @store[key]
        end

        def to_h

        @!visibility private
        def to_h
          JSON.parse(@ext.to_json)
        end

        def with_transaction(transaction)

        @!visibility private

        It restores the original transaction (if any) when the block has executed.
        Set the current for the duration of the given block.
        def with_transaction(transaction)
          original_transaction = current if current?
          set_current_transaction(transaction)
          yield
        ensure
          set_current_transaction(original_transaction)
        end