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)
- 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 informat -
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)
- See: https://docs.appsignal.com/guides/custom-data/sample-data.html -
See: Helpers::Instrumentation#add_custom_data -
Returns:
-
(void)-
Parameters:
-
data(Hash) -- 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)
- 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)
- 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)
- 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)
- 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) -- The parameters to set on the, Array
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)
- 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) -- The parameters to set on the, Array
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)
- 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)
- 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 = {})
- 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)
-
(Array-)
def after_create(&block) @after_create ||= Set.new return @after_create if block.nil? @after_create << block end
def before_complete(&block)
-
(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)
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!
Remove current transaction from current Thread.
def clear_current_transaction! Thread.current[:appsignal_transaction] = nil end
def complete
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!
-
(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?
def completed? @completed end
def create(namespace)
-
(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
-
(Appsignal::Transaction, Appsignal::Transaction::NilTransaction)-
Other tags:
- See: .current? -
def current Thread.current[:appsignal_transaction] || NilTransaction.new end
def current?
-
(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!
def discard! @discarded = true end
def discarded?
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?
def duplicate? @is_duplicate end
def finish_event(name, title, body, body_format = Appsignal::EventFormatter::DEFAULT)
- 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)
- 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)
- 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)
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
def last_errors @last_errors ||= [] end
def nil_transaction?
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!
def pause! @paused = true end
def paused?
def paused? @paused == true end
def record_event(name, title, body, duration, body_format = Appsignal::EventFormatter::DEFAULT)
- 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!
def restore! @discarded = false end
def resume!
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
-
(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
-
(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
- 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)
- 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)
- 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)
def set_current_transaction(transaction) Thread.current[:appsignal_transaction] = transaction end
def set_empty_params!
- 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)
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)
- 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)
-
(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
- See: Helpers::Instrumentation#instrument -
def start_event return if paused? @ext.start_event(0) end
def store(key)
def store(key) @store[key] end
def to_h
def to_h JSON.parse(@ext.to_json) end
def with_transaction(transaction)
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