scout_apm 2.1.20 → 2.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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