Class: RorVsWild::Client

Inherits:
Object
  • Object
show all
Includes:
Location
Defined in:
lib/rorvswild/client.rb

Constant Summary collapse

IGNORED_QUERIES =
%w[EXPLAIN SCHEMA].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Location

cleanup_method_name, #extract_most_relevant_location, #gem_home, #gem_home_regex, #relative_path, split_file_location

Constructor Details

#initialize(config) ⇒ Client

Returns a new instance of Client.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/rorvswild/client.rb', line 23

def initialize(config)
  config = self.class.default_config.merge(config)
  @explain_sql_threshold = config[:explain_sql_threshold]
  @ignored_exceptions = config[:ignored_exceptions]
  @app_root = config[:app_root]
  @api_url = config[:api_url]
  @api_key = config[:api_key]
  @app_id = config[:app_id]
  @logger = config[:logger]
  @threads = Set.new
  @data = {}

  if defined?(Rails)
    @logger ||= Rails.logger
    @app_root ||= Rails.root.to_s
    config = Rails.application.config
    @parameter_filter = ActionDispatch::Http::ParameterFilter.new(config.filter_parameters)
    @ignored_exceptions ||= %w[ActionController::RoutingError] + config.action_dispatch.rescue_responses.map { |(key,value)| key }
  end

  @logger ||= Logger.new(STDERR)
  @app_root_regex = app_root ? /\A#{app_root}/ : nil

  setup_callbacks
  RorVsWild.register_client(self)
end

Instance Attribute Details

#api_keyObject (readonly)

Returns the value of attribute api_key.



19
20
21
# File 'lib/rorvswild/client.rb', line 19

def api_key
  @api_key
end

#api_urlObject (readonly)

Returns the value of attribute api_url.



19
20
21
# File 'lib/rorvswild/client.rb', line 19

def api_url
  @api_url
end

#app_idObject (readonly)

Returns the value of attribute app_id.



19
20
21
# File 'lib/rorvswild/client.rb', line 19

def app_id
  @app_id
end

#app_rootObject (readonly)

Returns the value of attribute app_root.



19
20
21
# File 'lib/rorvswild/client.rb', line 19

def app_root
  @app_root
end

#app_root_regexObject (readonly)

Returns the value of attribute app_root_regex.



21
22
23
# File 'lib/rorvswild/client.rb', line 21

def app_root_regex
  @app_root_regex
end

#explain_sql_thresholdObject (readonly)

Returns the value of attribute explain_sql_threshold.



19
20
21
# File 'lib/rorvswild/client.rb', line 19

def explain_sql_threshold
  @explain_sql_threshold
end

#ignored_exceptionsObject (readonly)

Returns the value of attribute ignored_exceptions.



19
20
21
# File 'lib/rorvswild/client.rb', line 19

def ignored_exceptions
  @ignored_exceptions
end

#threadsObject (readonly)

Returns the value of attribute threads.



21
22
23
# File 'lib/rorvswild/client.rb', line 21

def threads
  @threads
end

Class Method Details

.default_configObject



11
12
13
14
15
16
17
# File 'lib/rorvswild/client.rb', line 11

def self.default_config
  {
    api_url: "https://www.rorvswild.com/api",
    explain_sql_threshold: 500,
    ignored_exceptions: [],
  }
end

Instance Method Details

#after_exception(exception, controller) ⇒ Object



105
106
107
108
109
110
111
112
113
114
# File 'lib/rorvswild/client.rb', line 105

def after_exception(exception, controller)
  if !ignored_exception?(exception)
    file, line = exception.backtrace.first.split(":")
    request[:error] = exception_to_hash(exception).merge(
      session: controller.session.to_hash,
      environment_variables: filter_sensitive_data(filter_environment_variables(controller.request.env))
    )
  end
  raise exception
end

#after_http_request(name, start, finish, id, payload) ⇒ Object



72
73
74
75
76
77
78
79
80
# File 'lib/rorvswild/client.rb', line 72

def after_http_request(name, start, finish, id, payload)
  request[:db_runtime] = (payload[:db_runtime] || 0).round
  request[:view_runtime] = (payload[:view_runtime] || 0).round
  request[:other_runtime] = compute_duration(start, finish) - request[:db_runtime] - request[:view_runtime]
  request[:error][:parameters] = filter_sensitive_data(payload[:params]) if request[:error]
  post_request
rescue => exception
  log_error(exception)
end

#after_sql_query(name, start, finish, id, payload) ⇒ Object



84
85
86
87
88
89
90
91
92
# File 'lib/rorvswild/client.rb', line 84

def after_sql_query(name, start, finish, id, payload)
  return if !queries || IGNORED_QUERIES.include?(payload[:name])
  file, line, method = extract_most_relevant_location(caller)
  runtime, sql = compute_duration(start, finish), payload[:sql]
  plan = runtime >= explain_sql_threshold ? explain(payload[:sql], payload[:binds]) : nil
  push_query(file: file, line: line, method: method, sql: sql, plan: plan, runtime: runtime)
rescue => exception
  log_error(exception)
end

#after_view_rendering(name, start, finish, id, payload) ⇒ Object



94
95
96
97
98
99
100
101
102
103
# File 'lib/rorvswild/client.rb', line 94

def after_view_rendering(name, start, finish, id, payload)
  if views
    if view = views[file = relative_path(payload[:identifier])]
      view[:runtime] += compute_duration(start, finish)
      view[:times] += 1
    else
      views[file] = {file: file, runtime: compute_duration(start, finish), times: 1}
    end
  end
end

#around_active_job(job, block) ⇒ Object



116
117
118
# File 'lib/rorvswild/client.rb', line 116

def around_active_job(job, block)
  measure_block(job.class.name, &block)
end

#around_delayed_job(job, &block) ⇒ Object



120
121
122
# File 'lib/rorvswild/client.rb', line 120

def around_delayed_job(job, &block)
  measure_block(job.name) { block.call(job) }
end

#before_http_request(name, start, finish, id, payload) ⇒ Object



68
69
70
# File 'lib/rorvswild/client.rb', line 68

def before_http_request(name, start, finish, id, payload)
  request.merge!(controller: payload[:controller], action: payload[:action], path: payload[:path], queries: [], views: {})
end

#catch_error(extra_details = nil, &block) ⇒ Object



146
147
148
149
150
151
152
153
# File 'lib/rorvswild/client.rb', line 146

def catch_error(extra_details = nil, &block)
  begin
    block.call
  rescue Exception => ex
    record_error(ex, extra_details) if !ignored_exception?(ex)
    ex
  end
end

#cpu_timeObject



159
160
161
162
# File 'lib/rorvswild/client.rb', line 159

def cpu_time
  time = Process.times
  time.utime + time.stime + time.cutime + time.cstime
end

#measure_block(name, &block) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/rorvswild/client.rb', line 128

def measure_block(name, &block)
  return block.call if job[:name] # Prevent from recursive jobs
  job[:name] = name
  job[:queries] = []
  started_at = Time.now
  cpu_time_offset = cpu_time
  begin
    block.call
  rescue Exception => ex
    job[:error] = exception_to_hash(ex) if !ignored_exception?(ex)
    raise
  ensure
    job[:runtime] = (Time.now - started_at) * 1000
    job[:cpu_runtime] = (cpu_time -  cpu_time_offset) * 1000
    post_job
  end
end

#measure_code(code) ⇒ Object



124
125
126
# File 'lib/rorvswild/client.rb', line 124

def measure_code(code)
  measure_block(code) { eval(code) }
end

#record_error(exception, extra_details = nil) ⇒ Object



155
156
157
# File 'lib/rorvswild/client.rb', line 155

def record_error(exception, extra_details = nil)
  post_error(exception_to_hash(exception, extra_details))
end

#setup_callbacksObject



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/rorvswild/client.rb', line 50

def setup_callbacks
  client = self
  if defined?(ActiveSupport::Notifications)
    ActiveSupport::Notifications.subscribe("sql.active_record", &method(:after_sql_query))
    ActiveSupport::Notifications.subscribe("render_partial.action_view", &method(:after_view_rendering))
    ActiveSupport::Notifications.subscribe("render_template.action_view", &method(:after_view_rendering))
    ActiveSupport::Notifications.subscribe("process_action.action_controller", &method(:after_http_request))
    ActiveSupport::Notifications.subscribe("start_processing.action_controller", &method(:before_http_request))
    ActionController::Base.rescue_from(StandardError) { |exception| client.after_exception(exception, self) }
  end

  Plugin::Resque.setup
  Plugin::Sidekiq.setup
  Kernel.at_exit(&method(:at_exit))
  ActiveJob::Base.around_perform(&method(:around_active_job)) if defined?(ActiveJob::Base)
  Delayed::Worker.lifecycle.around(:invoke_job, &method(:around_delayed_job)) if defined?(Delayed::Worker)
end