Class: ScoutApm::ErrorService::ErrorRecord

Inherits:
Object
  • Object
show all
Defined in:
lib/scout_apm/error_service/error_record.rb

Overview

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

Defined Under Namespace

Classes: LengthLimit

Constant Summary collapse

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.

[
  "rack.request.form_hash",
  "rack.request.form_vars",
  "async.callback",

  # Security related items
  "action_dispatch.secret_key_base",
  "action_dispatch.http_auth_salt",
  "action_dispatch.signed_cookie_salt",
  "action_dispatch.encrypted_cookie_salt",
  "action_dispatch.encrypted_signed_cookie_salt",
  "action_dispatch.authenticated_encrypted_cookie_salt",

  # Raw data from the URL & parameters. Would bypass our normal params filtering
  "QUERY_STRING",
  "REQUEST_URI",
  "REQUEST_PATH",
  "ORIGINAL_FULLPATH",
  "action_dispatch.request.query_parameters",
  "action_dispatch.request.parameters",
  "rack.request.query_string",
  "rack.request.query_hash",
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent_context, exception, env, context = nil) ⇒ ErrorRecord

Returns a new instance of ErrorRecord.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 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

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



14
15
16
# File 'lib/scout_apm/error_service/error_record.rb', line 14

def context
  @context
end

#environmentObject (readonly)

Returns the value of attribute environment.



11
12
13
# File 'lib/scout_apm/error_service/error_record.rb', line 11

def environment
  @environment
end

#exception_classObject (readonly)

Returns the value of attribute exception_class.



6
7
8
# File 'lib/scout_apm/error_service/error_record.rb', line 6

def exception_class
  @exception_class
end

#messageObject (readonly)

Returns the value of attribute message.



7
8
9
# File 'lib/scout_apm/error_service/error_record.rb', line 7

def message
  @message
end

#request_componentsObject (readonly)

Returns the value of attribute request_components.



13
14
15
# File 'lib/scout_apm/error_service/error_record.rb', line 13

def request_components
  @request_components
end

#request_paramsObject (readonly)

Returns the value of attribute request_params.



9
10
11
# File 'lib/scout_apm/error_service/error_record.rb', line 9

def request_params
  @request_params
end

#request_sessionObject (readonly)

Returns the value of attribute request_session.



10
11
12
# File 'lib/scout_apm/error_service/error_record.rb', line 10

def request_session
  @request_session
end

#request_uriObject (readonly)

Returns the value of attribute request_uri.



8
9
10
# File 'lib/scout_apm/error_service/error_record.rb', line 8

def request_uri
  @request_uri
end

#traceObject (readonly)

Returns the value of attribute trace.



12
13
14
# File 'lib/scout_apm/error_service/error_record.rb', line 12

def trace
  @trace
end

Instance Method Details

#clean_backtrace(backtrace) ⇒ Object

TODO: When was backtrace_cleaner introduced?



83
84
85
86
87
88
89
# 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) ⇒ Object

TODO: This name is too vague



75
76
77
78
79
80
# 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) ⇒ Object

TODO: This is rails specific



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 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) ⇒ Boolean

Check, if a key should be filtered

Returns:

  • (Boolean)


173
174
175
176
177
# 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) ⇒ Object

Replaces parameter values with a string / set in config file



158
159
160
161
162
163
164
165
166
167
168
169
170
# 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_configObject

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?



185
186
187
# 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) ⇒ Object

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



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# 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_filterObject



179
180
181
# 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) ⇒ Object

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



52
53
54
55
56
57
58
59
60
61
62
# 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) ⇒ Object



64
65
66
67
68
69
70
71
72
# 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_paramsObject



189
190
191
192
193
194
# 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) ⇒ Object



123
124
125
126
127
128
129
130
131
132
# 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) ⇒ Object



119
120
121
# 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