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_KEEP =

Capture params from env

[
  "REQUEST_METHOD",
  "HTTP_USER_AGENT",
  "HTTP_REFERER",
  "HTTP_ACCEPT_ENCODING",
  "HTTP_ORIGIN",
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Returns a new instance of ErrorRecord.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/scout_apm/error_service/error_record.rb', line 17

def initialize(agent_context, exception, env, context=nil)
  @agent_context = agent_context

  @context = if context
    context.to_flat_hash
  else
    {}
  end
  # Add the transaction_id, as it won't be added to the context normally until the request has been recorded.
  @context[:transaction_id] ||= RequestManager.lookup.transaction_id

  @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"])
  @environment = clean_params(env_captured(env))
  @trace = clean_backtrace(exception.backtrace)
  @request_components = components(env)
  @agent_time = Time.now.iso8601
  @git_sha = @agent_context.environment.git_revision.sha.to_s
end

Instance Attribute Details

#agent_timeObject (readonly)

Returns the value of attribute agent_time.



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

def agent_time
  @agent_time
end

#contextObject (readonly)

Returns the value of attribute context.



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

def context
  @context
end

#environmentObject (readonly)

Returns the value of attribute environment.



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

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

#git_shaObject (readonly)

Returns the value of attribute git_sha.



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

def git_sha
  @git_sha
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.



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

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_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.



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

def trace
  @trace
end

Instance Method Details

#clean_backtrace(backtrace) ⇒ Object

TODO: When was backtrace_cleaner introduced?



88
89
90
91
92
93
94
# File 'lib/scout_apm/error_service/error_record.rb', line 88

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



80
81
82
83
84
85
# File 'lib/scout_apm/error_service/error_record.rb', line 80

def clean_params(params)
  return if params.nil?

  normalized = normalize_data(params)
  filter_params(normalized)
end

#components(env) ⇒ Object

TODO: This is rails specific



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/scout_apm/error_service/error_record.rb', line 40

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[:action] ||= env[:custom_action]

  components
end

#env_captured(env) ⇒ Object



104
105
106
107
108
109
110
111
112
# File 'lib/scout_apm/error_service/error_record.rb', line 104

def env_captured(env)
  env.select { |k, v| KEYS_TO_KEEP.include?(k) }.tap do |filtered_env|
    filtered_env["HTTP_X_FORWARDED_FOR"] = env["HTTP_X_FORWARDED_FOR"] if @agent_context.config.value('collect_remote_ip') && env["HTTP_X_FORWARDED_FOR"]
    
    @agent_context.config.value('errors_env_capture').each do |key|
      filtered_env[key] = env[key] if env[key]
    end
  end
end

#filter_key?(key) ⇒ Boolean

Check, if a key should be filtered

Returns:

  • (Boolean)


153
154
155
156
157
# File 'lib/scout_apm/error_service/error_record.rb', line 153

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



138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/scout_apm/error_service/error_record.rb', line 138

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?



165
166
167
# File 'lib/scout_apm/error_service/error_record.rb', line 165

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?



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/scout_apm/error_service/error_record.rb', line 115

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



159
160
161
# File 'lib/scout_apm/error_service/error_record.rb', line 159

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?



57
58
59
60
61
62
63
64
65
66
67
# File 'lib/scout_apm/error_service/error_record.rb', line 57

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



69
70
71
72
73
74
75
76
77
# File 'lib/scout_apm/error_service/error_record.rb', line 69

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



169
170
171
172
173
174
# File 'lib/scout_apm/error_service/error_record.rb', line 169

def rails_filtered_params
  return [] unless defined?(Rails)
  Rails.configuration.filter_parameters
rescue 
  []
end