scout_apm 2.1.32 → 2.2.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/CHANGELOG.markdown +2 -161
  4. data/Rakefile +2 -2
  5. data/ext/allocations/allocations.c +0 -6
  6. data/ext/allocations/extconf.rb +0 -1
  7. data/ext/stacks/extconf.rb +33 -0
  8. data/ext/stacks/scout_atomics.h +86 -0
  9. data/ext/stacks/stacks.c +744 -0
  10. data/lib/scout_apm.rb +16 -24
  11. data/lib/scout_apm/agent.rb +38 -93
  12. data/lib/scout_apm/agent/logging.rb +1 -6
  13. data/lib/scout_apm/agent/reporting.rb +6 -8
  14. data/lib/scout_apm/app_server_load.rb +10 -21
  15. data/lib/scout_apm/attribute_arranger.rb +2 -0
  16. data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -71
  17. data/lib/scout_apm/background_job_integrations/sidekiq.rb +27 -66
  18. data/lib/scout_apm/background_worker.rb +15 -19
  19. data/lib/scout_apm/capacity.rb +57 -0
  20. data/lib/scout_apm/config.rb +29 -135
  21. data/lib/scout_apm/context.rb +5 -9
  22. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +12 -0
  23. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +83 -0
  24. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +12 -0
  25. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +88 -0
  26. data/lib/scout_apm/environment.rb +15 -22
  27. data/lib/scout_apm/histogram.rb +2 -11
  28. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  29. data/lib/scout_apm/instant/middleware.rb +57 -198
  30. data/lib/scout_apm/instruments/action_controller_rails_2.rb +2 -1
  31. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +59 -90
  32. data/lib/scout_apm/instruments/active_record.rb +5 -7
  33. data/lib/scout_apm/instruments/delayed_job.rb +57 -0
  34. data/lib/scout_apm/instruments/grape.rb +3 -4
  35. data/lib/scout_apm/instruments/middleware_detailed.rb +6 -4
  36. data/lib/scout_apm/instruments/middleware_summary.rb +1 -39
  37. data/lib/scout_apm/instruments/mongoid.rb +3 -24
  38. data/lib/scout_apm/instruments/net_http.rb +2 -7
  39. data/lib/scout_apm/instruments/percentile_sampler.rb +19 -36
  40. data/lib/scout_apm/instruments/process/process_cpu.rb +2 -3
  41. data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
  42. data/lib/scout_apm/layaway.rb +33 -76
  43. data/lib/scout_apm/layer.rb +59 -16
  44. data/lib/scout_apm/layer_converters/converter_base.rb +0 -199
  45. data/lib/scout_apm/layer_converters/job_converter.rb +1 -1
  46. data/lib/scout_apm/layer_converters/metric_converter.rb +1 -1
  47. data/lib/scout_apm/layer_converters/slow_job_converter.rb +90 -15
  48. data/lib/scout_apm/layer_converters/slow_request_converter.rb +101 -13
  49. data/lib/scout_apm/metric_set.rb +1 -9
  50. data/lib/scout_apm/metric_stats.rb +8 -8
  51. data/lib/scout_apm/reporter.rb +15 -51
  52. data/lib/scout_apm/request_histograms.rb +0 -4
  53. data/lib/scout_apm/request_manager.rb +1 -2
  54. data/lib/scout_apm/scored_item_set.rb +0 -7
  55. data/lib/scout_apm/serializers/deploy_serializer.rb +16 -0
  56. data/lib/scout_apm/serializers/payload_serializer.rb +3 -9
  57. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +5 -2
  58. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +1 -2
  59. data/lib/scout_apm/server_integrations/puma.rb +2 -5
  60. data/lib/scout_apm/slow_item_set.rb +80 -0
  61. data/lib/scout_apm/slow_job_record.rb +1 -6
  62. data/lib/scout_apm/slow_transaction.rb +2 -20
  63. data/lib/scout_apm/store.rb +12 -50
  64. data/lib/scout_apm/trace_compactor.rb +311 -0
  65. data/lib/scout_apm/tracked_request.rb +37 -128
  66. data/lib/scout_apm/utils/backtrace_parser.rb +5 -7
  67. data/lib/scout_apm/utils/fake_stacks.rb +83 -0
  68. data/lib/scout_apm/version.rb +1 -1
  69. data/scout_apm.gemspec +4 -6
  70. data/test/test_helper.rb +0 -56
  71. data/test/unit/config_test.rb +9 -60
  72. data/test/unit/histogram_test.rb +0 -14
  73. data/test/unit/layaway_test.rb +16 -31
  74. data/test/unit/serializers/payload_serializer_test.rb +105 -3
  75. data/test/unit/slow_item_set_test.rb +94 -0
  76. data/test/unit/slow_job_policy_test.rb +49 -0
  77. data/test/unit/slow_request_policy_test.rb +5 -4
  78. data/test/unit/utils/backtrace_parser_test.rb +0 -19
  79. data/tester.rb +53 -0
  80. metadata +29 -124
  81. data/.rubocop.yml +0 -8
  82. data/Guardfile +0 -42
  83. data/ext/rusage/README.md +0 -26
  84. data/ext/rusage/extconf.rb +0 -5
  85. data/ext/rusage/rusage.c +0 -52
  86. data/lib/scout_apm/background_job_integrations/resque.rb +0 -85
  87. data/lib/scout_apm/background_recorder.rb +0 -43
  88. data/lib/scout_apm/debug.rb +0 -37
  89. data/lib/scout_apm/git_revision.rb +0 -51
  90. data/lib/scout_apm/instruments/action_view.rb +0 -49
  91. data/lib/scout_apm/instruments/resque.rb +0 -40
  92. data/lib/scout_apm/layer_children_set.rb +0 -77
  93. data/lib/scout_apm/limited_layer.rb +0 -122
  94. data/lib/scout_apm/rack.rb +0 -26
  95. data/lib/scout_apm/remote/message.rb +0 -23
  96. data/lib/scout_apm/remote/recorder.rb +0 -57
  97. data/lib/scout_apm/remote/router.rb +0 -49
  98. data/lib/scout_apm/remote/server.rb +0 -58
  99. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +0 -21
  100. data/lib/scout_apm/synchronous_recorder.rb +0 -26
  101. data/lib/scout_apm/utils/gzip_helper.rb +0 -24
  102. data/lib/scout_apm/utils/numbers.rb +0 -14
  103. data/lib/scout_apm/utils/scm.rb +0 -14
  104. data/test/unit/background_job_integrations/sidekiq_test.rb +0 -104
  105. data/test/unit/context_test.rb +0 -30
  106. data/test/unit/git_revision_test.rb +0 -15
  107. data/test/unit/instruments/net_http_test.rb +0 -21
  108. data/test/unit/instruments/percentile_sampler_test.rb +0 -137
  109. data/test/unit/layer_children_set_test.rb +0 -88
  110. data/test/unit/limited_layer_test.rb +0 -53
  111. data/test/unit/remote/test_message.rb +0 -13
  112. data/test/unit/remote/test_router.rb +0 -33
  113. data/test/unit/remote/test_server.rb +0 -15
  114. data/test/unit/store_test.rb +0 -89
  115. data/test/unit/test_tracked_request.rb +0 -87
  116. data/test/unit/utils/numbers_test.rb +0 -15
  117. data/test/unit/utils/scm.rb +0 -17
@@ -75,10 +75,8 @@ module ScoutApm
75
75
  def value_valid?(key_value)
76
76
  # ensure one of our accepted types.
77
77
  value = key_value.values.last
78
- if value.nil?
79
- false # don't log this ... easy to happen
80
- elsif !valid_type?([String, Symbol, Numeric, Time, Date, TrueClass, FalseClass],value)
81
- ScoutApm::Agent.instance.logger.info "The value for [#{key_value.keys.first}] is not a valid type [#{value.class}]."
78
+ if !valid_type?([String, Symbol, Numeric, Time, Date, TrueClass, FalseClass],value)
79
+ ScoutApm::Agent.instance.logger.warn "The value for [#{key_value.keys.first}] is not a valid type [#{value.class}]."
82
80
  false
83
81
  else
84
82
  true
@@ -88,16 +86,14 @@ module ScoutApm
88
86
  # for consistently with #value_valid?, takes a hash eventhough the value isn't yet used.
89
87
  def key_valid?(key_value)
90
88
  key = key_value.keys.first
91
- if key.nil?
92
- false # don't log this ... easy to happen
93
89
  # ensure a string or a symbol
94
- elsif !valid_type?([String, Symbol],key)
95
- ScoutApm::Agent.instance.logger.info "The key [#{key}] is not a valid type [#{key.class}]."
90
+ if !valid_type?([String, Symbol],key)
91
+ ScoutApm::Agent.instance.logger.warn "The key [#{key}] is not a valid type [#{key.class}]."
96
92
  return false
97
93
  end
98
94
  # only alphanumeric, dash, and underscore allowed.
99
95
  if key.to_s.match(/[^\w-]/)
100
- ScoutApm::Agent.instance.logger.info "They key name [#{key}] is not valid."
96
+ ScoutApm::Agent.instance.logger.warn "They key name [#{key}] is not valid."
101
97
  return false
102
98
  end
103
99
  true
@@ -0,0 +1,12 @@
1
+ namespace :scout_apm do
2
+ namespace :deploy do
3
+ task :starting do
4
+ # Warn if missing scout apm deploy creds?
5
+ end
6
+ task :finished do
7
+ ScoutApm::Agent.instance.deploy_integration.report
8
+ end
9
+ end
10
+ end
11
+
12
+ after 'deploy:finished', 'scout_apm:deploy:finished'
@@ -0,0 +1,83 @@
1
+ require 'scout_apm'
2
+
3
+ module ScoutApm
4
+ module DeployIntegrations
5
+ class Capistrano2
6
+ attr_reader :logger
7
+
8
+ def initialize(logger)
9
+ @logger = logger
10
+ @cap = defined?(Capistrano::Configuration) ? ObjectSpace.each_object(Capistrano::Configuration).map.first : nil rescue nil
11
+ end
12
+
13
+ def name
14
+ :capistrano_2
15
+ end
16
+
17
+ def version
18
+ present? ? Capistrano::VERSION : nil
19
+ end
20
+
21
+ def present?
22
+ if !@cap.nil? && @cap.is_a?(Capistrano::Configuration)
23
+ require 'capistrano/version'
24
+ defined?(Capistrano::VERSION) && Gem::Dependency.new('', '~> 2.0').match?('', Capistrano::VERSION.to_s)
25
+ else
26
+ return false
27
+ end
28
+ return true
29
+ rescue
30
+ return false
31
+ end
32
+
33
+ def install
34
+ logger.debug "Initializing Capistrano2 Deploy Integration."
35
+ @cap.load File.expand_path("../capistrano_2.cap", __FILE__)
36
+ end
37
+
38
+ def root
39
+ '.'
40
+ end
41
+
42
+ def env
43
+ @cap.fetch(:stage)
44
+ end
45
+
46
+ def found?
47
+ true
48
+ end
49
+
50
+ def report
51
+ if reporter.can_report?
52
+ data = deploy_data
53
+ logger.debug "Sending deploy hook data: #{data}"
54
+ payload = ScoutApm::Serializers::DeploySerializer.serialize(data)
55
+ reporter.report(payload, ScoutApm::Serializers::DeploySerializer::HTTP_HEADERS)
56
+ else
57
+ logger.warn "Unable to post deploy hook data"
58
+ end
59
+ end
60
+
61
+ def reporter
62
+ @reporter ||= ScoutApm::Reporter.new(:deploy_hook, ScoutApm::Agent.instance.config, @logger)
63
+ end
64
+
65
+ def deploy_data
66
+ {:revision => current_revision, :branch => branch, :deployed_by => deployed_by}
67
+ end
68
+
69
+ def branch
70
+ @cap.fetch(:branch)
71
+ end
72
+
73
+ def current_revision
74
+ @cap.fetch(:current_revision) || `git rev-list --max-count=1 --abbrev-commit --abbrev=12 #{branch}`.chomp
75
+ end
76
+
77
+ def deployed_by
78
+ ScoutApm::Agent.instance.config.value('deployed_by')
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,12 @@
1
+ namespace :scout_apm do
2
+ namespace :deploy do
3
+ task :starting do
4
+ # Warn if missing scout apm deploy creds?
5
+ end
6
+ task :finished do
7
+ ScoutApm::Agent.instance.deploy_integration.report
8
+ end
9
+ end
10
+ end
11
+
12
+ after 'deploy:finished', 'scout_apm:deploy:finished'
@@ -0,0 +1,88 @@
1
+ require 'scout_apm'
2
+
3
+ module ScoutApm
4
+ module DeployIntegrations
5
+ class Capistrano3
6
+ attr_reader :logger
7
+
8
+ def initialize(logger)
9
+ @logger = logger
10
+ @cap = Rake.application rescue nil
11
+ end
12
+
13
+ def name
14
+ :capistrano_3
15
+ end
16
+
17
+ def version
18
+ present? ? Capistrano::VERSION : nil
19
+ end
20
+
21
+ def present?
22
+ if !@cap.nil? && @cap.is_a?(Capistrano::Application)
23
+ require 'capistrano/version'
24
+ defined?(Capistrano::VERSION) && Gem::Dependency.new('', '~> 3.0').match?('', Capistrano::VERSION.to_s)
25
+ else
26
+ return false
27
+ end
28
+ rescue
29
+ return false
30
+ end
31
+
32
+ def install
33
+ logger.debug "Initializing Capistrano3 Deploy Integration."
34
+ load File.expand_path("../capistrano_3.cap", __FILE__)
35
+ end
36
+
37
+ def root
38
+ '.'
39
+ end
40
+
41
+ def env
42
+ @cap.fetch(:stage).to_s
43
+ end
44
+
45
+ def found?
46
+ true
47
+ end
48
+
49
+ def report
50
+ if reporter.can_report?
51
+ data = deploy_data
52
+ logger.debug "Sending deploy hook data: #{data}"
53
+ payload = ScoutApm::Serializers::DeploySerializer.serialize(data)
54
+ reporter.report(payload, ScoutApm::Serializers::DeploySerializer::HTTP_HEADERS)
55
+ else
56
+ logger.warn "Unable to post deploy hook data"
57
+ end
58
+ end
59
+
60
+ def reporter
61
+ config = if env == ''
62
+ ScoutApm::Agent.instance.config
63
+ else
64
+ ScoutApm::Config.with_file(nil, {:file => { :environment => env }}) # instantiate our own config, with an overridden environment for the deploy-to app name instead of deploy-from app name)
65
+ end
66
+
67
+ @reporter ||= ScoutApm::Reporter.new(:deploy_hook, config, @logger)
68
+ end
69
+
70
+ def deploy_data
71
+ {:revision => current_revision, :branch => branch, :deployed_by => deployed_by}
72
+ end
73
+
74
+ def branch
75
+ @cap.fetch(:branch)
76
+ end
77
+
78
+ def current_revision
79
+ @cap.fetch(:current_revision) || `git rev-list --max-count=1 --abbrev-commit --abbrev=12 #{branch}`.chomp
80
+ end
81
+
82
+ def deployed_by
83
+ ScoutApm::Agent.instance.config.value('deployed_by')
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -24,9 +24,8 @@ module ScoutApm
24
24
  ]
25
25
 
26
26
  BACKGROUND_JOB_INTEGRATIONS = [
27
- ScoutApm::BackgroundJobIntegrations::Resque.new,
28
27
  ScoutApm::BackgroundJobIntegrations::Sidekiq.new,
29
- ScoutApm::BackgroundJobIntegrations::DelayedJob.new,
28
+ # ScoutApm::BackgroundJobIntegrations::DelayedJob.new
30
29
  ]
31
30
 
32
31
  FRAMEWORK_INTEGRATIONS = [
@@ -42,8 +41,13 @@ module ScoutApm
42
41
  ScoutApm::PlatformIntegrations::Server.new,
43
42
  ]
44
43
 
44
+ DEPLOY_INTEGRATIONS = [
45
+ ScoutApm::DeployIntegrations::Capistrano3.new(STDOUT_LOGGER),
46
+ # ScoutApm::DeployIntegrations::Capistrano2.new(STDOUT_LOGGER),
47
+ ]
48
+
45
49
  def env
46
- @env ||= framework_integration.env
50
+ @env ||= deploy_integration? ? deploy_integration.env : framework_integration.env
47
51
  end
48
52
 
49
53
  def framework
@@ -59,9 +63,7 @@ module ScoutApm
59
63
  end
60
64
 
61
65
  def application_name
62
- Agent.instance.config.value("name") ||
63
- framework_integration.application_name ||
64
- "App"
66
+ Agent.instance.config.value("name") || framework_integration.application_name
65
67
  end
66
68
 
67
69
  def database_engine
@@ -85,16 +87,8 @@ module ScoutApm
85
87
  end
86
88
  end
87
89
 
88
- def scm_subdirectory
89
- @scm_subdirectory ||= if Agent.instance.config.value('scm_subdirectory').empty?
90
- ''
91
- else
92
- Agent.instance.config.value('scm_subdirectory').sub(/^\//, '') # Trim any leading slash
93
- end
94
- end
95
-
96
90
  def root
97
- @root ||= framework_root
91
+ @root ||= deploy_integration? ? deploy_integration.root : framework_root
98
92
  end
99
93
 
100
94
  def framework_root
@@ -116,10 +110,6 @@ module ScoutApm
116
110
  @hostname ||= Agent.instance.config.value("hostname") || platform_integration.hostname
117
111
  end
118
112
 
119
- def git_revision
120
- @git_revision ||= ScoutApm::GitRevision.new
121
- end
122
-
123
113
  # Returns the whole integration object
124
114
  # This needs to be improved. Frequently, multiple app servers gem are present and which
125
115
  # ever is checked first becomes the designated app server.
@@ -153,9 +143,12 @@ module ScoutApm
153
143
  background_job_integration && background_job_integration.name
154
144
  end
155
145
 
156
- # If both stdin & stdout are interactive and the Rails::Console constant is defined
157
- def interactive?
158
- defined?(::Rails::Console) && $stdout.isatty && $stdin.isatty
146
+ def deploy_integration
147
+ @deploy_integration ||= DEPLOY_INTEGRATIONS.detect{ |integration| integration.present? }
148
+ end
149
+
150
+ def deploy_integration?
151
+ !@deploy_integration.nil?
159
152
  end
160
153
 
161
154
  ### ruby checks
@@ -90,11 +90,7 @@ module ScoutApm
90
90
  def combine!(other)
91
91
  mutex.synchronize do
92
92
  other.mutex.synchronize do
93
- @bins = (other.bins + @bins).
94
- group_by {|b| b.value }.
95
- map {|val, bs| [val, bs.inject(0) {|sum, b| sum + b.count }] }.
96
- map {|val, sum| HistogramBin.new(val,sum) }.
97
- sort_by { |b| b.value }
93
+ @bins = (other.bins + @bins).sort_by {|b| b.value }
98
94
  @total += other.total
99
95
  trim
100
96
  self
@@ -104,12 +100,7 @@ module ScoutApm
104
100
 
105
101
  def as_json
106
102
  mutex.synchronize do
107
- bins.map{ |b|
108
- [
109
- ScoutApm::Utils::Numbers.round(b.value, 4),
110
- b.count
111
- ]
112
- }
103
+ bins.map{|b| [b.value, b.count]}
113
104
  end
114
105
  end
115
106
 
@@ -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{var 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{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,11 +5,6 @@ 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
-
13
8
  @to_add_to_head = []
14
9
  @to_add_to_body = []
15
10
  end
@@ -46,204 +41,68 @@ module ScoutApm
46
41
  # Note that this middleware never even gets inserted unless Rails environment is development (See Railtie)
47
42
  class Middleware
48
43
  def initialize(app)
49
- @app = app
44
+ ScoutApm::Agent.instance.logger.info("Activating Scout DevTrace because environment=development and dev_trace=true in scout_apm config")
45
+ @app = app
50
46
  end
51
47
 
52
48
  def call(env)
53
- rack_response = @app.call(env)
54
- begin
55
- DevTraceResponseManipulator.new(env, rack_response).call
56
- rescue Exception => e
57
- # If anything went wrong at all, just bail out and return the unmodified response.
58
- ScoutApm::Agent.instance.logger.debug("DevTrace: Raised an exception: #{e.message}, #{e.backtrace}")
59
- rack_response
60
- end
61
- end
62
- end
63
-
64
- class DevTraceResponseManipulator
65
- attr_reader :rack_response
66
- attr_reader :rack_status, :rack_headers, :rack_body
67
- attr_reader :env
68
-
69
- def initialize(env, rack_response)
70
- @env = env
71
- @rack_response = rack_response
72
-
73
- @rack_status = rack_response[0]
74
- @rack_headers = rack_response[1]
75
- @rack_body = rack_response[2]
76
- end
77
-
78
- def call
79
- return rack_response unless preconditions_met?
80
-
81
- if ajax_request?
82
- 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}")
83
- adjust_ajax_header
84
- else
85
- adjust_html_response
86
- end
87
-
88
- rebuild_rack_response
89
- end
90
-
91
- ###########################
92
- # Precondition checking #
93
- ###########################
94
-
95
- def preconditions_met?
96
- if dev_trace_disabled?
97
- logger.debug("DevTrace: isn't activated via config. Try: SCOUT_DEV_TRACE=true rails server")
98
- return false
99
- end
100
-
101
- # Don't attempt to instrument assets.
102
- # Don't log this case, since it would be very noisy
103
- logger.debug("DevTrace: dev asset ignored") and return false if development_asset?
104
-
105
- # If we didn't have a tracked_request object, or we explicitly ignored
106
- # this request, don't do any work.
107
- logger.debug("DevTrace: no tracked request") and return false if tracked_request.nil? || tracked_request.ignoring_request?
108
-
109
- # If we didn't get a trace, we can't show a trace...
110
- if trace.nil?
111
- logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body, but no trace was found. Path=#{path}; ContentType=#{content_type}")
112
- return false
113
- end
114
-
115
- true
116
- end
117
-
118
- def dev_trace_disabled?
119
- ! ScoutApm::Agent.instance.config.value('dev_trace')
120
- end
121
-
122
- ########################
123
- # Response Injection #
124
- ########################
125
-
126
- def rebuild_rack_response
127
- [rack_status, rack_headers, rack_body]
128
- end
129
-
130
- def adjust_ajax_header
131
- rack_headers['X-scoutapminstant'] = payload
132
- end
133
-
134
- def adjust_html_response
135
- case true
136
- when older_rails_response? then adjust_older_rails_response
137
- when newer_rails_response? then adjust_newer_rails_response
138
- when rack_proxy_response? then adjust_rack_proxy_response
49
+ status, headers, response = @app.call(env)
50
+ path, content_type = env['PATH_INFO'], headers['Content-Type']
51
+ if ScoutApm::Agent.instance.config.value('dev_trace')
52
+ if response.respond_to?(:body)
53
+ req = ScoutApm::RequestManager.lookup
54
+ slow_converter = LayerConverters::SlowRequestConverter.new(req)
55
+ trace = slow_converter.call
56
+ if trace
57
+ metadata = {
58
+ :app_root => ScoutApm::Environment.instance.root.to_s,
59
+ :unique_id => env['action_dispatch.request_id'], # note, this is a different unique_id than what "normal" payloads use
60
+ :agent_version => ScoutApm::VERSION,
61
+ :platform => "ruby",
62
+ }
63
+ hash = ScoutApm::Serializers::PayloadSerializerToJson.rearrange_slow_transaction(trace)
64
+ hash.merge!(metadata:metadata)
65
+ payload = ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(hash)
66
+
67
+ if env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || content_type.include?("application/json")
68
+ 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}")
69
+ # Add the payload as a header if it's an AJAX call or JSON
70
+ headers['X-scoutapminstant'] = payload
71
+ [status, headers, response]
72
+ else
73
+ # 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,
74
+ #duration = (req.root_layer.total_call_time*1000).to_i
75
+ apm_host=ScoutApm::Agent.instance.config.value("direct_host")
76
+ page = ScoutApm::Instant::Page.new(response.body)
77
+ 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.
78
+ page.add_to_head("<link href='#{apm_host}/instant/scout_instant.css?cachebust=#{Time.now.to_i}' media='all' rel='stylesheet' />")
79
+ page.add_to_body("<script src='#{apm_host}/instant/scout_instant.js?cachebust=#{Time.now.to_i}'></script>")
80
+ page.add_to_body("<script>var scoutInstantPageTrace=#{payload};window.scoutInstant=window.scoutInstant('#{apm_host}', scoutInstantPageTrace)</script>")
81
+
82
+ if response.is_a?(ActionDispatch::Response)
83
+ 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}")
84
+ # preserve the ActionDispatch::Response when applicable
85
+ response.body=[page.res]
86
+ [status, headers, response]
87
+ else
88
+ 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}")
89
+ # otherwise, just return an array
90
+ [status, headers, [page.res]]
91
+ end
92
+ end
93
+ else
94
+ 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}")
95
+ [status, headers, response]
96
+ end
97
+ else
98
+ # don't log anything here - this is the path for all assets served in development, and the log would get noisy
99
+ [status, headers, response]
100
+ end
139
101
  else
140
- # No action taken, we only adjust if we know exactly what we have.
141
- end
142
- end
143
-
144
- def older_rails_response?
145
- if defined?(ActionDispatch::Response)
146
- return true if rack_body.is_a?(ActionDispatch::Response)
147
- end
148
- end
149
-
150
- def newer_rails_response?
151
- if defined?(ActionDispatch::Response::RackBody)
152
- return true if rack_body.is_a?(ActionDispatch::Response::RackBody)
102
+ ScoutApm::Agent.instance.logger.debug("DevTrace: isn't activated via config. Try: SCOUT_DEV_TRACE=true rails server")
103
+ [status, headers, response]
153
104
  end
154
105
  end
155
-
156
- def rack_proxy_response?
157
- rack_body.is_a?(Rack::BodyProxy)
158
- end
159
-
160
- def adjust_older_rails_response
161
- logger.debug("DevTrace: in middleware, dev_trace is active, and response has a (older) body. This appears to be an HTML page and an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
162
- rack_body.body = [ html_manipulator.res ]
163
- end
164
-
165
- # Preserve the ActionDispatch::Response object we're working with
166
- def adjust_newer_rails_response
167
- logger.debug("DevTrace: in middleware, dev_trace is active, and response has a (newer) body. This appears to be an HTML page and an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
168
- @rack_body = [ html_manipulator.res ]
169
- end
170
-
171
- def adjust_rack_proxy_response
172
- 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}")
173
- @rack_body = [ html_manipulator.res ]
174
- @rack_headers.delete("Content-Length")
175
- end
176
-
177
- def html_manipulator
178
- @html_manipulator ||=
179
- begin
180
- page = ScoutApm::Instant::Page.new(rack_body.body)
181
-
182
- # 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.
183
- page.add_to_head(ScoutApm::Instant::Util.read_asset("xmlhttp_instrumentation.html"))
184
-
185
- # Add a link to CSS, then JS
186
- page.add_to_head("<link href='#{apm_host}/instant/scout_instant.css?cachebust=#{Time.now.to_i}' media='all' rel='stylesheet' />")
187
- page.add_to_body("<script src='#{apm_host}/instant/scout_instant.js?cachebust=#{Time.now.to_i}'></script>")
188
- page.add_to_body("<script>var scoutInstantPageTrace=#{payload};window.scoutInstant=window.scoutInstant('#{apm_host}', scoutInstantPageTrace)</script>")
189
-
190
- page
191
- end
192
- end
193
-
194
- def ajax_request?
195
- env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || content_type.include?("application/json")
196
- end
197
-
198
- def development_asset?
199
- !rack_body.respond_to?(:body)
200
- end
201
-
202
- def path
203
- env['PATH_INFO']
204
- end
205
-
206
- def content_type
207
- rack_headers['Content-Type']
208
- end
209
-
210
- ##############################
211
- # APM Helpers & Shorthands #
212
- ##############################
213
-
214
- def logger
215
- ScoutApm::Agent.instance.logger
216
- end
217
-
218
- def tracked_request
219
- @tracked_request ||= ScoutApm::RequestManager.lookup
220
- end
221
-
222
- def apm_host
223
- ScoutApm::Agent.instance.config.value("direct_host")
224
- end
225
-
226
- def trace
227
- @trace ||= LayerConverters::SlowRequestConverter.new(tracked_request).call
228
- end
229
-
230
- def payload
231
- @payload ||=
232
- begin
233
- metadata = {
234
- :app_root => ScoutApm::Environment.instance.root.to_s,
235
- :unique_id => env['action_dispatch.request_id'], # note, this is a different unique_id than what "normal" payloads use
236
- :agent_version => ScoutApm::VERSION,
237
- :platform => "ruby",
238
- }
239
-
240
- hash = ScoutApm::Serializers::PayloadSerializerToJson.
241
- rearrange_slow_transaction(trace).
242
- merge!(:metadata => metadata)
243
- ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(hash)
244
- end
245
- end
246
-
247
106
  end
248
107
  end
249
- end
108
+ end