scout_apm 2.1.20 → 2.1.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,9 @@
1
+ # 2.1.21
2
+
3
+ * Fix edge case, causing DevTrace to fail
4
+ * Add debug tooling, allowing custom functions to be inserted into the agent at
5
+ key points.
6
+
1
7
  # 2.1.20
2
8
 
3
9
  * Add a `detailed_middleware` boolean configuration option to capture
@@ -25,6 +25,7 @@ require 'rusage'
25
25
  #####################################
26
26
  require 'scout_apm/version'
27
27
 
28
+ require 'scout_apm/debug'
28
29
  require 'scout_apm/tracked_request'
29
30
  require 'scout_apm/layer'
30
31
  require 'scout_apm/limited_layer'
@@ -259,6 +259,7 @@ module ScoutApm
259
259
  @background_worker = ScoutApm::BackgroundWorker.new
260
260
  @background_worker_thread = Thread.new do
261
261
  @background_worker.start {
262
+ ScoutApm::Debug.instance.call_periodic_hooks
262
263
  ScoutApm::Agent.instance.process_metrics
263
264
  clean_old_percentiles
264
265
  }
@@ -72,7 +72,7 @@ module ScoutApm
72
72
  log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)
73
73
 
74
74
  payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms)
75
- # logger.debug("Payload: #{payload}")
75
+ logger.debug("Sending payload w/ Headers: #{headers.inspect}")
76
76
 
77
77
  reporter.report(payload, headers)
78
78
  rescue => e
@@ -0,0 +1,37 @@
1
+ module ScoutApm
2
+ class Debug
3
+ # see self.instance
4
+ @@instance = nil
5
+
6
+ def self.instance
7
+ @@instance ||= new
8
+ end
9
+
10
+ def register_periodic_hook(&hook)
11
+ @periodic_hooks << hook
12
+ end
13
+
14
+ def call_periodic_hooks
15
+ @periodic_hooks.each do |hook|
16
+ begin
17
+ hook.call
18
+ rescue => e
19
+ logger.info("Periodic debug hook failed to run: #{e}\n\t#{e.backtrace.join("\n\t")}")
20
+ end
21
+ end
22
+ rescue
23
+ # Something went super wrong for the inner rescue to not catch this. Just
24
+ # swallow the error. The debug tool should never crash the app.
25
+ end
26
+
27
+ private
28
+
29
+ def initialize
30
+ @periodic_hooks = []
31
+ end
32
+
33
+ def logger
34
+ ScoutApm::Agent.instance.logger
35
+ end
36
+ end
37
+ end
@@ -3,7 +3,7 @@
3
3
  (function(){var open=window.XMLHttpRequest.prototype.open;var send=window.XMLHttpRequest.prototype.send;function openReplacement(method,url,async,user,password){this._url=url;return open.apply(this,arguments);}
4
4
  function sendReplacement(data){if(this.onload){this._onload=this.onload;}
5
5
  this.onload=onLoadReplacement;return send.apply(this,arguments);}
6
- function onLoadReplacement(){if(this._url.startsWith(window.location.protocol+"//"+window.location.host)||!this._url.startsWith("http")){try{traceText=this.getResponseHeader("X-scoutapminstant");if(traceText){setTimeout(function(){window.scoutInstant("addTrace",traceText)},0);}}catch(e){console.debug("Problem getting X-scoutapminstant header");}}
6
+ function onLoadReplacement(){if(this._url.startsWith(window.location.protocol+"//"+window.location.host)||!this._url.startsWith("http")){try{var traceText=this.getResponseHeader("X-scoutapminstant");if(traceText){setTimeout(function(){window.scoutInstant("addTrace",traceText)},0);}}catch(e){console.debug("Problem getting X-scoutapminstant header");}}
7
7
  if(this._onload){return this._onload.apply(this,arguments);}}
8
8
  window.XMLHttpRequest.prototype.open=openReplacement;window.XMLHttpRequest.prototype.send=sendReplacement;})();
9
- </script>
9
+ </script>
@@ -5,6 +5,11 @@ module ScoutApm
5
5
  class Page
6
6
  def initialize(html)
7
7
  @html = html
8
+
9
+ if html.is_a?(Array)
10
+ @html = html.inject("") { |memo, str| memo + str }
11
+ end
12
+
8
13
  @to_add_to_head = []
9
14
  @to_add_to_body = []
10
15
  end
@@ -45,66 +50,185 @@ module ScoutApm
45
50
  end
46
51
 
47
52
  def call(env)
48
- status, headers, response = @app.call(env)
49
- path, content_type = env['PATH_INFO'], headers['Content-Type']
50
- if ScoutApm::Agent.instance.config.value('dev_trace')
51
- if response.respond_to?(:body)
52
- req = ScoutApm::RequestManager.lookup
53
-
54
- return [status, headers, response] if req.ignoring_request?
55
-
56
- slow_converter = LayerConverters::SlowRequestConverter.new(req)
57
- trace = slow_converter.call
58
- if trace
59
- metadata = {
60
- :app_root => ScoutApm::Environment.instance.root.to_s,
61
- :unique_id => env['action_dispatch.request_id'], # note, this is a different unique_id than what "normal" payloads use
62
- :agent_version => ScoutApm::VERSION,
63
- :platform => "ruby",
64
- }
65
- hash = ScoutApm::Serializers::PayloadSerializerToJson.rearrange_slow_transaction(trace)
66
- hash.merge!(:metadata => metadata)
67
- payload = ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(hash)
68
-
69
- if env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || content_type.include?("application/json")
70
- ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This is either AJAX or JSON. Path=#{path}; ContentType=#{content_type}")
71
- # Add the payload as a header if it's an AJAX call or JSON
72
- headers['X-scoutapminstant'] = payload
73
- [status, headers, response]
74
- else
75
- # otherwise, attempt to add it inline in the page, along with the appropriate JS & CSS. Note, if page doesn't have a head or body,
76
- #duration = (req.root_layer.total_call_time*1000).to_i
77
- apm_host=ScoutApm::Agent.instance.config.value("direct_host")
78
- page = ScoutApm::Instant::Page.new(response.body)
79
- page.add_to_head(ScoutApm::Instant::Util.read_asset("xmlhttp_instrumentation.html")) # This monkey-patches XMLHttpRequest. It could possibly be part of the main scout_instant.js too. Putting it here so it runs as soon as possible.
80
- page.add_to_head("<link href='#{apm_host}/instant/scout_instant.css?cachebust=#{Time.now.to_i}' media='all' rel='stylesheet' />")
81
- page.add_to_body("<script src='#{apm_host}/instant/scout_instant.js?cachebust=#{Time.now.to_i}'></script>")
82
- page.add_to_body("<script>var scoutInstantPageTrace=#{payload};window.scoutInstant=window.scoutInstant('#{apm_host}', scoutInstantPageTrace)</script>")
83
-
84
- if response.is_a?(ActionDispatch::Response)
85
- ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This appears to be an HTML page and an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
86
- # preserve the ActionDispatch::Response when applicable
87
- response.body=[page.res]
88
- [status, headers, response]
89
- else
90
- ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This appears to be an HTML page but not an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
91
- # otherwise, just return an array
92
- [status, headers, [page.res]]
93
- end
94
- end
95
- else
96
- ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body, but no trace was found. Path=#{path}; ContentType=#{content_type}")
97
- [status, headers, response]
98
- end
99
- else
100
- # don't log anything here - this is the path for all assets served in development, and the log would get noisy
101
- [status, headers, response]
102
- end
53
+ rack_response = @app.call(env)
54
+ begin
55
+ DevTraceResponseManipulator.new(env, rack_response).call
56
+ rescue => e
57
+ ScoutApm::Agent.instance.logger.debug("DevTrace: Raised an exception: #{e.message}, #{e.backtrace}")
58
+ rack_response
59
+ end
60
+ end
61
+ end
62
+
63
+ class DevTraceResponseManipulator
64
+ attr_reader :rack_response
65
+ attr_reader :rack_status, :rack_headers, :rack_body
66
+ attr_reader :env
67
+
68
+ def initialize(env, rack_response)
69
+ @env = env
70
+ @rack_response = rack_response
71
+
72
+ @rack_status = rack_response[0]
73
+ @rack_headers = rack_response[1]
74
+ @rack_body = rack_response[2]
75
+ end
76
+
77
+ def call
78
+ return rack_response unless preconditions_met?
79
+
80
+ if ajax_request?
81
+ ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This is either AJAX or JSON. Path=#{path}; ContentType=#{content_type}")
82
+ adjust_ajax_header
103
83
  else
104
- ScoutApm::Agent.instance.logger.debug("DevTrace: isn't activated via config. Try: SCOUT_DEV_TRACE=true rails server")
105
- [status, headers, response]
84
+ adjust_html_response
85
+ end
86
+
87
+ rebuild_rack_response
88
+ end
89
+
90
+ ###########################
91
+ # Precondition checking #
92
+ ###########################
93
+
94
+ def preconditions_met?
95
+ if dev_trace_disabled?
96
+ logger.debug("DevTrace: isn't activated via config. Try: SCOUT_DEV_TRACE=true rails server")
97
+ return false
106
98
  end
99
+
100
+ # Don't attempt to instrument assets.
101
+ # Don't log this case, since it would be very noisy
102
+ logger.debug("DevTrace: dev asset ignored") and return false if development_asset?
103
+
104
+ # If we didn't have a tracked_request object, or we explicitly ignored
105
+ # this request, don't do any work.
106
+ logger.debug("DevTrace: no tracked request") and return false if tracked_request.nil? || tracked_request.ignoring_request?
107
+
108
+ # If we didn't get a trace, we can't show a trace...
109
+ if trace.nil?
110
+ logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body, but no trace was found. Path=#{path}; ContentType=#{content_type}")
111
+ return false
112
+ end
113
+
114
+ true
115
+ end
116
+
117
+ def dev_trace_disabled?
118
+ ! ScoutApm::Agent.instance.config.value('dev_trace')
119
+ end
120
+
121
+ ########################
122
+ # Response Injection #
123
+ ########################
124
+
125
+ def rebuild_rack_response
126
+ [rack_status, rack_headers, rack_body]
127
+ end
128
+
129
+ def adjust_ajax_header
130
+ rack_headers['X-scoutapminstant'] = payload
131
+ end
132
+
133
+ def adjust_html_response
134
+ case true
135
+ when rails_response? then adjust_rails_response
136
+ when rack_proxy_response? then adjust_rack_proxy_response
137
+ else
138
+ # No action taken, we only adjust if we know exactly what we have.
139
+ end
140
+ end
141
+
142
+ def rails_response?
143
+ rack_body.is_a?(ActionDispatch::Response)
144
+ end
145
+
146
+ def rack_proxy_response?
147
+ rack_body.is_a?(Rack::BodyProxy)
148
+ end
149
+
150
+ # Preserve the ActionDispatch::Response object we're working with
151
+ def adjust_rails_response
152
+ logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This appears to be an HTML page and an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
153
+ rack_body.body = [ html_manipulator.res ]
154
+ end
155
+
156
+ def adjust_rack_proxy_response
157
+ logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This appears to be an HTML page and an Rack::BodyProxy. Path=#{path}; ContentType=#{content_type}")
158
+ @rack_body = [ html_manipulator.res ]
159
+ @rack_headers.delete("Content-Length")
160
+ end
161
+
162
+ def html_manipulator
163
+ @html_manipulator ||=
164
+ begin
165
+ page = ScoutApm::Instant::Page.new(rack_body.body)
166
+
167
+ # This monkey-patches XMLHttpRequest. It could possibly be part of the main scout_instant.js too. Putting it here so it runs as soon as possible.
168
+ page.add_to_head(ScoutApm::Instant::Util.read_asset("xmlhttp_instrumentation.html"))
169
+
170
+ # Add a link to CSS, then JS
171
+ page.add_to_head("<link href='#{apm_host}/instant/scout_instant.css?cachebust=#{Time.now.to_i}' media='all' rel='stylesheet' />")
172
+ page.add_to_body("<script src='#{apm_host}/instant/scout_instant.js?cachebust=#{Time.now.to_i}'></script>")
173
+ page.add_to_body("<script>var scoutInstantPageTrace=#{payload};window.scoutInstant=window.scoutInstant('#{apm_host}', scoutInstantPageTrace)</script>")
174
+
175
+ page
176
+ end
107
177
  end
178
+
179
+ def ajax_request?
180
+ env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || content_type.include?("application/json")
181
+ end
182
+
183
+ def development_asset?
184
+ !rack_body.respond_to?(:body)
185
+ end
186
+
187
+ def path
188
+ env['PATH_INFO']
189
+ end
190
+
191
+ def content_type
192
+ rack_headers['Content-Type']
193
+ end
194
+
195
+ ##############################
196
+ # APM Helpers & Shorthands #
197
+ ##############################
198
+
199
+ def logger
200
+ ScoutApm::Agent.instance.logger
201
+ end
202
+
203
+ def tracked_request
204
+ @tracked_request ||= ScoutApm::RequestManager.lookup
205
+ end
206
+
207
+ def apm_host
208
+ ScoutApm::Agent.instance.config.value("direct_host")
209
+ end
210
+
211
+ def trace
212
+ @trace ||= LayerConverters::SlowRequestConverter.new(tracked_request).call
213
+ end
214
+
215
+ def payload
216
+ @payload ||=
217
+ begin
218
+ metadata = {
219
+ :app_root => ScoutApm::Environment.instance.root.to_s,
220
+ :unique_id => env['action_dispatch.request_id'], # note, this is a different unique_id than what "normal" payloads use
221
+ :agent_version => ScoutApm::VERSION,
222
+ :platform => "ruby",
223
+ }
224
+
225
+ hash = ScoutApm::Serializers::PayloadSerializerToJson.
226
+ rearrange_slow_transaction(trace).
227
+ merge!(:metadata => metadata)
228
+ ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(hash)
229
+ end
230
+ end
231
+
108
232
  end
109
233
  end
110
234
  end
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "2.1.20"
2
+ VERSION = "2.1.21"
3
3
  end
4
4
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.20
4
+ version: 2.1.21
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-01-03 00:00:00.000000000 Z
13
+ date: 2017-01-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: minitest
@@ -159,6 +159,7 @@ files:
159
159
  - lib/scout_apm/call_set.rb
160
160
  - lib/scout_apm/config.rb
161
161
  - lib/scout_apm/context.rb
162
+ - lib/scout_apm/debug.rb
162
163
  - lib/scout_apm/environment.rb
163
164
  - lib/scout_apm/fake_store.rb
164
165
  - lib/scout_apm/framework_integrations/rails_2.rb