lib/appsignal/transaction.rb in appsignal-2.1.0.alpha.3 vs lib/appsignal/transaction.rb in appsignal-2.1.0.beta.1

- old
+ new

@@ -1,28 +1,31 @@ -require 'json' +require "json" module Appsignal class Transaction - HTTP_REQUEST = 'http_request'.freeze - BACKGROUND_JOB = 'background_job'.freeze - FRONTEND = 'frontend'.freeze - BLANK = ''.freeze + HTTP_REQUEST = "http_request".freeze + BACKGROUND_JOB = "background_job".freeze + FRONTEND = "frontend".freeze + BLANK = "".freeze # Based on what Rails uses + some variables we'd like to show - ENV_METHODS = %w(CONTENT_LENGTH AUTH_TYPE GATEWAY_INTERFACE - PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER REMOTE_ADDR - REQUEST_METHOD SERVER_NAME SERVER_PORT SERVER_PROTOCOL REQUEST_URI PATH_INFO + ENV_METHODS = %w( + CONTENT_LENGTH AUTH_TYPE GATEWAY_INTERFACE + PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER REMOTE_ADDR + REQUEST_METHOD SERVER_NAME SERVER_PORT SERVER_PROTOCOL REQUEST_URI + PATH_INFO - HTTP_X_REQUEST_START HTTP_X_MIDDLEWARE_START HTTP_X_QUEUE_START - HTTP_X_QUEUE_TIME HTTP_X_HEROKU_QUEUE_WAIT_TIME HTTP_X_APPLICATION_START - HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE - HTTP_CACHE_CONTROL HTTP_CONNECTION HTTP_USER_AGENT HTTP_FROM HTTP_NEGOTIATE - HTTP_PRAGMA HTTP_REFERER HTTP_X_FORWARDED_FOR HTTP_CLIENT_IP HTTP_RANGE - HTTP_X_AUTH_TOKEN) + HTTP_X_REQUEST_START HTTP_X_MIDDLEWARE_START HTTP_X_QUEUE_START + HTTP_X_QUEUE_TIME HTTP_X_HEROKU_QUEUE_WAIT_TIME HTTP_X_APPLICATION_START + HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE + HTTP_CACHE_CONTROL HTTP_CONNECTION HTTP_USER_AGENT HTTP_FROM + HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_X_FORWARDED_FOR + HTTP_CLIENT_IP HTTP_RANGE HTTP_X_AUTH_TOKEN + ) class << self - def create(id, namespace, request, options={}) + def create(id, namespace, request, options = {}) # Allow middleware to force a new transaction if options.include?(:force) && options[:force] Thread.current[:appsignal_transaction] = nil end @@ -56,11 +59,11 @@ end end attr_reader :ext, :transaction_id, :namespace, :request, :paused, :tags, :options, :discarded - def initialize(transaction_id, namespace, request, options={}) + def initialize(transaction_id, namespace, request, options = {}) @transaction_id = transaction_id @namespace = namespace @request = request @paused = false @discarded = false @@ -80,11 +83,11 @@ false end def complete if discarded? - Appsignal.logger.debug('Skipping transaction because it was manually discarded.'.freeze) + Appsignal.logger.debug("Skipping transaction because it was manually discarded.".freeze) return end if @ext.finish(self.class.garbage_collection_profiler.total_time) sample_data end @@ -117,26 +120,26 @@ def store(key) @store[key] end - def set_tags(given_tags={}) + def set_tags(given_tags = {}) @tags.merge!(given_tags) end def set_action(action) return unless action @ext.set_action(action) end - def set_http_or_background_action(from=request.params) + def set_http_or_background_action(from = request.params) return unless from group_and_action = [ from[:controller] || from[:class], from[:action] || from[:method] ] - set_action(group_and_action.compact.join('#')) + set_action(group_and_action.compact.join("#")) end def set_queue_start(start) return unless start @ext.set_queue_start(start) @@ -180,11 +183,10 @@ end def set_error(error) return unless error return unless Appsignal.active? - return if Appsignal.is_ignored_error?(error) backtrace = cleaned_backtrace(error.backtrace) @ext.set_error( error.class.name, error.message.to_s, @@ -195,31 +197,31 @@ def start_event @ext.start_event(self.class.garbage_collection_profiler.total_time) end - def finish_event(name, title, body, body_format=Appsignal::EventFormatter::DEFAULT) + def finish_event(name, title, body, body_format = Appsignal::EventFormatter::DEFAULT) @ext.finish_event( name, title || BLANK, body || BLANK, body_format || Appsignal::EventFormatter::DEFAULT, self.class.garbage_collection_profiler.total_time ) end - def record_event(name, title, body, duration, body_format=Appsignal::EventFormatter::DEFAULT) + def record_event(name, title, body, duration, body_format = Appsignal::EventFormatter::DEFAULT) @ext.record_event( name, title || BLANK, body || BLANK, duration, body_format || Appsignal::EventFormatter::DEFAULT ) end - def instrument(name, title=nil, body=nil, body_format=Appsignal::EventFormatter::DEFAULT) + def instrument(name, title = nil, body = nil, body_format = Appsignal::EventFormatter::DEFAULT) start_event r = yield finish_event(name, title, body, body_format) r end @@ -236,27 +238,44 @@ end end protected + # Returns calculated background queue start time in milliseconds, based on + # environment values. + # + # @return [nil] if no {#environment} is present. + # @return [nil] if there is no `:queue_start` in the {#environment}. + # @return [Integer] def background_queue_start - return unless request.env - return unless queue_start = request.env[:queue_start] + env = environment + return unless env + queue_start = env[:queue_start] + return unless queue_start + (queue_start.to_f * 1000.0).to_i end + # Returns HTTP queue start time in milliseconds. + # + # @return [nil] if no queue start time is found. + # @return [nil] if begin time is too low to be plausible. + # @return [Integer] queue start in milliseconds. def http_queue_start - return unless request.env - return unless env_var = request.env['HTTP_X_QUEUE_START'.freeze] || request.env['HTTP_X_REQUEST_START'.freeze] - cleaned_value = env_var.tr('^0-9'.freeze, ''.freeze) + env = environment + return unless env + env_var = env["HTTP_X_QUEUE_START".freeze] || env["HTTP_X_REQUEST_START".freeze] + return unless env_var + cleaned_value = env_var.tr("^0-9".freeze, "".freeze) return if cleaned_value.empty? + value = cleaned_value.to_i if value > 4_102_441_200_000 - # Value is in microseconds + # Value is in microseconds. Transform to milliseconds. value / 1_000 elsif value < 946_681_200_000 - # Value is to low to be plausible + # Value is too low to be plausible nil else # Value is in milliseconds value end @@ -281,38 +300,76 @@ options[:filter_parameters] = Appsignal.config[:filter_parameters] end Appsignal::Utils::ParamsSanitizer.sanitize params, options end + # Returns sanitized environment for a transaction. + # + # The environment of a transaction can contain a lot of information, not + # all of it useful for debugging. + # + # Only the values from the keys specified in `ENV_METHODS` are returned. + # + # @return [nil] if no environment is present. + # @return [Hash<String, Object>] def sanitized_environment - return unless request.env + env = environment + return if env.empty? + {}.tap do |out| ENV_METHODS.each do |key| - out[key] = request.env[key] if request.env[key] + out[key] = env[key] if env[key] end end end + # Returns sanitized session data. + # + # The session data is sanitized by the {Appsignal::Utils::ParamsSanitizer}. + # + # @return [nil] if `:skip_session_data` config is set to `true`. + # @return [nil] if the {#request} object doesn't respond to `#session`. + # @return [nil] if the {#request} session data is `nil`. + # @return [Hash<String, Object>] def sanitized_session_data - return if Appsignal.config[:skip_session_data] || !request.respond_to?(:session) - return unless session = request.session + return if Appsignal.config[:skip_session_data] || + !request.respond_to?(:session) + session = request.session + return unless session + Appsignal::Utils::ParamsSanitizer.sanitize(session.to_hash) end + # Returns metadata from the environment. + # + # @return [nil] if no `:metadata` key is present in the {#environment}. + # @return [Hash<String, Object>] def metadata - return unless request.env - request.env[:metadata] + environment[:metadata] end + # Returns the environment for a transaction. + # + # Returns an empty Hash when the {#request} object doesn't listen to the + # `#env` method or the `#env` is nil. + # + # @return [Hash<String, Object>] + def environment + return {} unless request.respond_to?(:env) + return {} unless request.env + + request.env + end + # Only keep tags if they meet the following criteria: # * Key is a symbol or string with less then 100 chars # * Value is a symbol or string with less then 100 chars # * Value is an integer def sanitized_tags @tags.select do |k, v| (k.is_a?(Symbol) || k.is_a?(String) && k.length <= 100) && - (((v.is_a?(Symbol) || v.is_a?(String)) && v.length <= 100) || (v.is_a?(Integer))) + (((v.is_a?(Symbol) || v.is_a?(String)) && v.length <= 100) || v.is_a?(Integer)) end end def cleaned_backtrace(backtrace) if defined?(::Rails) && backtrace @@ -327,10 +384,10 @@ class NilTransaction def method_missing(m, *args, &block) end # Instrument should still yield - def instrument(*args) + def instrument(*_args) yield end def nil_transaction? true