class ScoutApm::ErrorService::ErrorRecord

Converts the raw error data captured into the captured data, and holds it until it's ready to be reported.

Constants

KEYS_TO_REMOVE

Deletes params from env

These are not configurable, and will leak PII info up to Scout if allowed through. Things like specific parameters can be exposed with the ScoutApm::Context interface.

Attributes

context[R]
environment[R]
exception_class[R]
message[R]
request_components[R]
request_params[R]
request_session[R]
request_uri[R]
trace[R]

Public Class Methods

new(agent_context, exception, env, context=nil) click to toggle source
# File lib/scout_apm/error_service/error_record.rb, line 16
def initialize(agent_context, exception, env, context=nil)
  @agent_context = agent_context

  @context = if context
    context.to_hash
  else
    {}
  end

  @exception_class = LengthLimit.new(exception.class.name).to_s
  @message = LengthLimit.new(exception.message, 100).to_s
  @request_uri = LengthLimit.new(rack_request_url(env), 200).to_s
  @request_params = clean_params(env["action_dispatch.request.parameters"])
  @request_session = clean_params(session_data(env))
  @environment = clean_params(strip_env(env))
  @trace = clean_backtrace(exception.backtrace)
  @request_components = components(env)
end

Public Instance Methods

clean_backtrace(backtrace) click to toggle source

TODO: When was backtrace_cleaner introduced?

# File lib/scout_apm/error_service/error_record.rb, line 83
def clean_backtrace(backtrace)
  if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner)
    Rails.backtrace_cleaner.send(:filter, backtrace)
  else
    backtrace
  end
end
clean_params(params) click to toggle source

TODO: This name is too vague

# File lib/scout_apm/error_service/error_record.rb, line 75
def clean_params(params)
  return if params.nil?

  normalized = normalize_data(params)
  filter_params(normalized)
end
components(env) click to toggle source

TODO: This is rails specific

# File lib/scout_apm/error_service/error_record.rb, line 36
def components(env)
  components = {}
  unless env["action_dispatch.request.parameters"].nil?
    components[:controller] = env["action_dispatch.request.parameters"][:controller] || nil
    components[:action] = env["action_dispatch.request.parameters"][:action] || nil
    components[:module] = env["action_dispatch.request.parameters"][:module] || nil
  end

  # For background workers like sidekiq
  # TODO: extract data creation for background jobs
  components[:controller] ||= env[:custom_controller]

  components
end
filter_key?(key) click to toggle source

Check, if a key should be filtered

# File lib/scout_apm/error_service/error_record.rb, line 173
def filter_key?(key)
  params_to_filter.any? do |filter|
    key.to_s == filter.to_s # key.to_s.include?(filter.to_s)
  end
end
filter_params(params) click to toggle source

Replaces parameter values with a string / set in config file

# File lib/scout_apm/error_service/error_record.rb, line 158
def filter_params(params)
  return params unless filtered_params_config

  params.each do |k, v|
    if filter_key?(k)
      params[k] = "[FILTERED]"
    elsif v.respond_to?(:to_hash)
      filter_params(params[k])
    end
  end

  params
end
filtered_params_config() click to toggle source

Accessor for the filtered params config value. Will be removed as we refactor and clean up this code. TODO: Flip this over to use a new class like filtered exceptions?

# File lib/scout_apm/error_service/error_record.rb, line 185
def filtered_params_config
  @agent_context.config.value("errors_filtered_params")
end
normalize_data(hash) click to toggle source

TODO: Rename and make this clearer. I think it maps over the whole tree of a hash, and to_s each leaf node?

# File lib/scout_apm/error_service/error_record.rb, line 135
def normalize_data(hash)
  new_hash = {}

  hash.each do |key, value|
    if value.respond_to?(:to_hash)
      begin
        new_hash[key] = normalize_data(value.to_hash)
      rescue
        new_hash[key] = LengthLimit.new(value.to_s).to_s
      end
    else
      new_hash[key] = LengthLimit.new(value.to_s).to_s
    end
  end

  new_hash
end
params_to_filter() click to toggle source
# File lib/scout_apm/error_service/error_record.rb, line 179
def params_to_filter
  @params_to_filter ||= filtered_params_config + rails_filtered_params
end
rack_request_url(env) click to toggle source

TODO: Can I use the same thing we use in traces?

# File lib/scout_apm/error_service/error_record.rb, line 52
def rack_request_url(env)
  protocol = rack_scheme(env)
  protocol = protocol.nil? ? "" : "#{protocol}://"

  host = env["SERVER_NAME"] || ""
  path = env["REQUEST_URI"] || ""
  port = env["SERVER_PORT"] || "80"
  port = ["80", "443"].include?(port.to_s) ? "" : ":#{port}"

  protocol.to_s + host.to_s + port.to_s + path.to_s
end
rack_scheme(env) click to toggle source
# File lib/scout_apm/error_service/error_record.rb, line 64
def rack_scheme(env)
  if env["HTTPS"] == "on"
    "https"
  elsif env["HTTP_X_FORWARDED_PROTO"]
    env["HTTP_X_FORWARDED_PROTO"].split(",")[0]
  else
    env["rack.url_scheme"]
  end
end
rails_filtered_params() click to toggle source
# File lib/scout_apm/error_service/error_record.rb, line 189
def rails_filtered_params
  return [] unless defined?(Rails)
  Rails.configuration.filter_parameters
rescue 
  []
end
session_data(env) click to toggle source
# File lib/scout_apm/error_service/error_record.rb, line 123
def session_data(env)
  session = env["action_dispatch.request.session"]
  return if session.nil?

  if session.respond_to?(:to_hash)
    session.to_hash
  else
    session.data
  end
end
strip_env(env) click to toggle source
# File lib/scout_apm/error_service/error_record.rb, line 119
def strip_env(env)
  env.reject { |k, v| KEYS_TO_REMOVE.include?(k) }
end