scout_apm 2.2.0.pre3 → 2.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/CHANGELOG.markdown +147 -2
  4. data/Guardfile +43 -0
  5. data/Rakefile +2 -2
  6. data/ext/allocations/allocations.c +6 -0
  7. data/ext/allocations/extconf.rb +1 -0
  8. data/ext/rusage/README.md +26 -0
  9. data/ext/rusage/extconf.rb +5 -0
  10. data/ext/rusage/rusage.c +52 -0
  11. data/lib/scout_apm.rb +28 -15
  12. data/lib/scout_apm/agent.rb +89 -37
  13. data/lib/scout_apm/agent/logging.rb +6 -1
  14. data/lib/scout_apm/agent/reporting.rb +9 -6
  15. data/lib/scout_apm/app_server_load.rb +21 -10
  16. data/lib/scout_apm/attribute_arranger.rb +6 -3
  17. data/lib/scout_apm/background_job_integrations/delayed_job.rb +71 -1
  18. data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
  19. data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -20
  20. data/lib/scout_apm/background_recorder.rb +43 -0
  21. data/lib/scout_apm/background_worker.rb +19 -15
  22. data/lib/scout_apm/config.rb +138 -28
  23. data/lib/scout_apm/db_query_metric_set.rb +80 -0
  24. data/lib/scout_apm/db_query_metric_stats.rb +102 -0
  25. data/lib/scout_apm/debug.rb +37 -0
  26. data/lib/scout_apm/environment.rb +22 -15
  27. data/lib/scout_apm/git_revision.rb +51 -0
  28. data/lib/scout_apm/histogram.rb +11 -2
  29. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  30. data/lib/scout_apm/instant/middleware.rb +196 -54
  31. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +89 -68
  32. data/lib/scout_apm/instruments/action_view.rb +49 -0
  33. data/lib/scout_apm/instruments/active_record.rb +127 -3
  34. data/lib/scout_apm/instruments/grape.rb +4 -3
  35. data/lib/scout_apm/instruments/middleware_detailed.rb +4 -6
  36. data/lib/scout_apm/instruments/mongoid.rb +24 -3
  37. data/lib/scout_apm/instruments/net_http.rb +7 -2
  38. data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
  39. data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
  40. data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
  41. data/lib/scout_apm/instruments/resque.rb +40 -0
  42. data/lib/scout_apm/layaway.rb +67 -28
  43. data/lib/scout_apm/layer.rb +19 -59
  44. data/lib/scout_apm/layer_children_set.rb +77 -0
  45. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
  46. data/lib/scout_apm/layer_converters/converter_base.rb +201 -14
  47. data/lib/scout_apm/layer_converters/database_converter.rb +55 -0
  48. data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
  49. data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
  50. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
  51. data/lib/scout_apm/layer_converters/histograms.rb +14 -0
  52. data/lib/scout_apm/layer_converters/job_converter.rb +36 -50
  53. data/lib/scout_apm/layer_converters/metric_converter.rb +17 -19
  54. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
  55. data/lib/scout_apm/layer_converters/slow_job_converter.rb +41 -115
  56. data/lib/scout_apm/layer_converters/slow_request_converter.rb +33 -117
  57. data/lib/scout_apm/limited_layer.rb +126 -0
  58. data/lib/scout_apm/metric_meta.rb +0 -5
  59. data/lib/scout_apm/metric_set.rb +9 -1
  60. data/lib/scout_apm/metric_stats.rb +7 -8
  61. data/lib/scout_apm/rack.rb +26 -0
  62. data/lib/scout_apm/remote/message.rb +23 -0
  63. data/lib/scout_apm/remote/recorder.rb +57 -0
  64. data/lib/scout_apm/remote/router.rb +49 -0
  65. data/lib/scout_apm/remote/server.rb +58 -0
  66. data/lib/scout_apm/reporter.rb +51 -15
  67. data/lib/scout_apm/request_histograms.rb +4 -0
  68. data/lib/scout_apm/request_manager.rb +2 -1
  69. data/lib/scout_apm/scored_item_set.rb +7 -0
  70. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
  71. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
  72. data/lib/scout_apm/serializers/payload_serializer.rb +10 -3
  73. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +6 -6
  74. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -1
  75. data/lib/scout_apm/server_integrations/puma.rb +5 -2
  76. data/lib/scout_apm/slow_job_policy.rb +1 -10
  77. data/lib/scout_apm/slow_job_record.rb +6 -1
  78. data/lib/scout_apm/slow_request_policy.rb +1 -10
  79. data/lib/scout_apm/slow_transaction.rb +20 -2
  80. data/lib/scout_apm/store.rb +66 -12
  81. data/lib/scout_apm/synchronous_recorder.rb +26 -0
  82. data/lib/scout_apm/tracked_request.rb +136 -71
  83. data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
  84. data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
  85. data/lib/scout_apm/utils/gzip_helper.rb +24 -0
  86. data/lib/scout_apm/utils/numbers.rb +14 -0
  87. data/lib/scout_apm/utils/scm.rb +14 -0
  88. data/lib/scout_apm/version.rb +1 -1
  89. data/scout_apm.gemspec +5 -4
  90. data/test/test_helper.rb +18 -0
  91. data/test/unit/config_test.rb +59 -8
  92. data/test/unit/db_query_metric_set_test.rb +56 -0
  93. data/test/unit/db_query_metric_stats_test.rb +113 -0
  94. data/test/unit/git_revision_test.rb +15 -0
  95. data/test/unit/histogram_test.rb +14 -0
  96. data/test/unit/instruments/net_http_test.rb +21 -0
  97. data/test/unit/instruments/percentile_sampler_test.rb +137 -0
  98. data/test/unit/layaway_test.rb +20 -0
  99. data/test/unit/layer_children_set_test.rb +88 -0
  100. data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
  101. data/test/unit/layer_converters/metric_converter_test.rb +22 -0
  102. data/test/unit/layer_converters/stubs.rb +33 -0
  103. data/test/unit/limited_layer_test.rb +53 -0
  104. data/test/unit/remote/test_message.rb +13 -0
  105. data/test/unit/remote/test_router.rb +33 -0
  106. data/test/unit/remote/test_server.rb +15 -0
  107. data/test/unit/serializers/payload_serializer_test.rb +3 -12
  108. data/test/unit/store_test.rb +66 -0
  109. data/test/unit/test_tracked_request.rb +87 -0
  110. data/test/unit/utils/active_record_metric_name_test.rb +8 -0
  111. data/test/unit/utils/backtrace_parser_test.rb +5 -0
  112. data/test/unit/utils/numbers_test.rb +15 -0
  113. data/test/unit/utils/scm.rb +17 -0
  114. metadata +125 -30
  115. data/ext/stacks/extconf.rb +0 -37
  116. data/ext/stacks/scout_atomics.h +0 -86
  117. data/ext/stacks/stacks.c +0 -811
  118. data/lib/scout_apm/capacity.rb +0 -57
  119. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +0 -12
  120. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +0 -83
  121. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +0 -12
  122. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +0 -88
  123. data/lib/scout_apm/instruments/delayed_job.rb +0 -57
  124. data/lib/scout_apm/serializers/deploy_serializer.rb +0 -16
  125. data/lib/scout_apm/trace_compactor.rb +0 -312
  126. data/lib/scout_apm/utils/fake_stacks.rb +0 -87
  127. data/tester.rb +0 -53
@@ -0,0 +1,77 @@
1
+ module ScoutApm
2
+ # A set of children records for any given Layer. This implements some
3
+ # rate-limiting logic.
4
+ #
5
+ # We store the first `unique_cutoff` count of each layer type. So if cutoff
6
+ # is 1000, we'd store 1000 HTTP layers, and 1000 ActiveRecord calls, and 1000
7
+ # of each other layer type. After that, make a LimitedLayer object and store
8
+ # only aggregate counts and times of future layers of that type. (So the
9
+ # 1001st an onward of ActiveRecord would get only aggregate times, and
10
+ # counts, without any detail about the SQL called)
11
+ #
12
+ # When the set of children is small, keep them unique
13
+ # When the set of children gets large enough, stop keeping details
14
+ #
15
+ # The next optimization, which is not yet implemented:
16
+ # when the set of children gets larger, attempt to merge them without data loss
17
+ class LayerChildrenSet
18
+ include Enumerable
19
+
20
+ # By default, how many unique children of a type do we store before
21
+ # flipping over to storing only aggregate info.
22
+ DEFAULT_UNIQUE_CUTOFF = 2000
23
+ attr_reader :unique_cutoff
24
+
25
+ # The Set of children objects
26
+ attr_reader :children
27
+ private :children
28
+
29
+ def initialize(unique_cutoff = DEFAULT_UNIQUE_CUTOFF)
30
+ @children = Hash.new
31
+ @limited_layers = nil # populated when needed
32
+ @unique_cutoff = unique_cutoff
33
+ end
34
+
35
+ def child_set(metric_type)
36
+ children[metric_type] = Set.new if !children.has_key?(metric_type)
37
+ children[metric_type]
38
+ end
39
+
40
+ # Add a new layer into this set
41
+ # Only add completed layers - otherwise this will collect up incorrect info
42
+ # into the created LimitedLayer, since it will "freeze" any current data for
43
+ # total_call_time and similar methods.
44
+ def <<(child)
45
+ metric_type = child.type
46
+ set = child_set(metric_type)
47
+
48
+ if set.size >= unique_cutoff
49
+ # find limited_layer
50
+ @limited_layers || init_limited_layers
51
+ @limited_layers[metric_type].absorb(child)
52
+ else
53
+ # we have space just add it
54
+ set << child
55
+ end
56
+ end
57
+
58
+ def each
59
+ children.each do |_type, set|
60
+ set.each do |child_layer|
61
+ yield child_layer
62
+ end
63
+ end
64
+
65
+ if @limited_layers
66
+ @limited_layers.each do |_type, limited_layer|
67
+ yield limited_layer
68
+ end
69
+ end
70
+ end
71
+
72
+ # hold off initializing this until we know we need it
73
+ def init_limited_layers
74
+ @limited_layers ||= Hash.new { |hash, key| hash[key] = LimitedLayer.new(key) }
75
+ end
76
+ end
77
+ end
@@ -1,16 +1,15 @@
1
1
  module ScoutApm
2
2
  module LayerConverters
3
3
  class AllocationMetricConverter < ConverterBase
4
- def call
5
- scope = scope_layer
6
- return {} unless scope
7
- return {} unless ScoutApm::Instruments::Allocations::ENABLED
4
+ def record!
5
+ return unless scope_layer
6
+ return unless ScoutApm::Instruments::Allocations::ENABLED
8
7
 
9
- meta = MetricMeta.new("ObjectAllocations", {:scope => scope.legacy_metric_name})
8
+ meta = MetricMeta.new("ObjectAllocations", {:scope => scope_layer.legacy_metric_name})
10
9
  stat = MetricStats.new
11
10
  stat.update!(root_layer.total_allocations)
12
11
 
13
- { meta => stat }
12
+ @store.track!({ meta => stat })
14
13
  end
15
14
  end
16
15
  end
@@ -1,31 +1,218 @@
1
1
  module ScoutApm
2
2
  module LayerConverters
3
3
  class ConverterBase
4
- attr_reader :walker
4
+
5
5
  attr_reader :request
6
6
  attr_reader :root_layer
7
+ attr_reader :layer_finder
7
8
 
8
- def initialize(request)
9
+ def initialize(request, layer_finder, store=nil)
9
10
  @request = request
11
+ @layer_finder = layer_finder
12
+ @store = store
13
+
10
14
  @root_layer = request.root_layer
11
- @walker = DepthFirstWalker.new(root_layer)
15
+ @backtraces = []
16
+ @limited = false
12
17
  end
13
18
 
14
- # Scope is determined by the first Controller we hit. Most of the time
15
- # there will only be 1 anyway. But if you have a controller that calls
16
- # another controller method, we may pick that up:
17
- # def update
18
- # show
19
- # render :update
20
- # end
21
19
  def scope_layer
22
- @scope_layer ||= find_first_layer_of_type("Controller") || find_first_layer_of_type("Job")
20
+ layer_finder.scope
21
+ end
22
+
23
+ ################################################################################
24
+ # Subscoping
25
+ ################################################################################
26
+ #
27
+ # Keep a list of subscopes, but only ever use the front one. The rest
28
+ # get pushed/popped in cases when we have many levels of subscopable
29
+ # layers. This lets us push/pop without otherwise keeping track very closely.
30
+ def register_hooks(walker)
31
+ @subscope_layers = []
32
+
33
+ walker.before do |layer|
34
+ if layer.subscopable?
35
+ @subscope_layers.push(layer)
36
+ end
37
+ end
38
+
39
+ walker.after do |layer|
40
+ if layer.subscopable?
41
+ @subscope_layers.pop
42
+ end
43
+ end
44
+ end
45
+
46
+ def subscoped?(layer)
47
+ @subscope_layers.first && layer != @subscope_layers.first # Don't scope under ourself.
48
+ end
49
+
50
+ def subscope_name
51
+ @subscope_layers.first.legacy_metric_name
52
+ end
53
+
54
+
55
+ ################################################################################
56
+ # Backtrace Handling
57
+ ################################################################################
58
+ #
59
+ # Because we get several layers for the same thing if you call an
60
+ # instrumented thing repeatedly, and only some of them may have
61
+ # backtraces captured, we store the backtraces off into another spot
62
+ # during processing, then at the end, we loop over those saved
63
+ # backtraces, putting them back into the metrics hash.
64
+ #
65
+ # This comes up most often when capturing n+1 backtraces. Because the
66
+ # query may be fast enough to evade our time-limit based backtrace
67
+ # capture, only the Nth item (see TrackedRequest for more detail) has a
68
+ # backtrack captured. This sequence makes sure that we report up that
69
+ # backtrace in the aggregated set of metrics around that call.
70
+
71
+ # Call this as you are processing each layer. It will store off backtraces
72
+ def store_backtrace(layer, meta)
73
+ return unless layer.backtrace
74
+
75
+ bt = ScoutApm::Utils::BacktraceParser.new(layer.backtrace).call
76
+ if bt.any?
77
+ meta.backtrace = bt
78
+ @backtraces << meta
79
+ end
23
80
  end
24
81
 
25
- def find_first_layer_of_type(layer_type)
26
- walker.walk do |layer|
27
- return layer if layer.type == layer_type
82
+ # Call this after you finish walking the layers, and want to take the
83
+ # set-aside backtraces and place them into the metas they match
84
+ def attach_backtraces(metric_hash)
85
+ @backtraces.each do |meta_with_backtrace|
86
+ metric_hash.keys.find { |k| k == meta_with_backtrace }.backtrace = meta_with_backtrace.backtrace
28
87
  end
88
+ metric_hash
89
+ end
90
+
91
+
92
+ ################################################################################
93
+ # Limit Handling
94
+ ################################################################################
95
+
96
+ # To prevent huge traces from being generated, we should stop collecting
97
+ # detailed metrics as we go beyond some reasonably large count.
98
+ #
99
+ # We should still add up the /all aggregates.
100
+
101
+ MAX_METRICS = 500
102
+
103
+ def over_metric_limit?(metric_hash)
104
+ if metric_hash.size > MAX_METRICS
105
+ @limited = true
106
+ else
107
+ false
108
+ end
109
+ end
110
+
111
+ def limited?
112
+ !! @limited
113
+ end
114
+
115
+ ################################################################################
116
+ # Meta Scope
117
+ ################################################################################
118
+
119
+ # When we make MetricMeta records, we need to determine a few things from layer.
120
+ def make_meta_options(layer)
121
+ scope_hash = make_meta_options_scope(layer)
122
+ desc_hash = make_meta_options_desc_hash(layer)
123
+
124
+ scope_hash.merge(desc_hash)
125
+ end
126
+
127
+ def make_meta_options_scope(layer)
128
+ # This layer is scoped under another thing. Typically that means this is a layer under a view.
129
+ # Like: Controller -> View/users/show -> ActiveRecord/user/find
130
+ # in that example, the scope is the View/users/show
131
+ if subscoped?(layer)
132
+ {:scope => subscope_name}
133
+
134
+ # We don't scope the controller under itself
135
+ elsif layer == scope_layer
136
+ {}
137
+
138
+ # This layer is a top level metric ("ActiveRecord", or "HTTP" or
139
+ # whatever, directly under the controller), so scope to the
140
+ # Controller
141
+ else
142
+ {:scope => scope_layer.legacy_metric_name}
143
+ end
144
+ end
145
+
146
+ def make_meta_options_desc_hash(layer, max_desc_length=1000)
147
+ if layer.desc
148
+ desc_s = layer.desc.to_s
149
+ trimmed_desc = desc_s[0 .. max_desc_length]
150
+ {:desc => trimmed_desc}
151
+ else
152
+ {}
153
+ end
154
+ end
155
+
156
+
157
+ ################################################################################
158
+ # Storing metrics into the hashes
159
+ ################################################################################
160
+
161
+ # This is the detailed metric - type, name, backtrace, annotations, etc.
162
+ def store_specific_metric(layer, metric_hash, allocation_metric_hash)
163
+ return false if over_metric_limit?(metric_hash)
164
+
165
+ meta_options = make_meta_options(layer)
166
+
167
+ meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
168
+ meta.extra.merge!(layer.annotations) if layer.annotations
169
+
170
+ store_backtrace(layer, meta)
171
+
172
+ metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
173
+ allocation_metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
174
+
175
+ # timing
176
+ stat = metric_hash[meta]
177
+ stat.update!(layer.total_call_time, layer.total_exclusive_time)
178
+
179
+ # allocations
180
+ stat = allocation_metric_hash[meta]
181
+ stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
182
+
183
+ if LimitedLayer === layer
184
+ metric_hash[meta].call_count = layer.count
185
+ allocation_metric_hash[meta].call_count = layer.count
186
+ end
187
+ end
188
+
189
+ # Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)
190
+ def store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
191
+ meta = MetricMeta.new("#{layer.type}/all")
192
+
193
+ metric_hash[meta] ||= MetricStats.new(false)
194
+ allocation_metric_hash[meta] ||= MetricStats.new(false)
195
+
196
+ # timing
197
+ stat = metric_hash[meta]
198
+ stat.update!(layer.total_call_time, layer.total_exclusive_time)
199
+
200
+ # allocations
201
+ stat = allocation_metric_hash[meta]
202
+ stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
203
+ end
204
+
205
+ ################################################################################
206
+ # Misc Helpers
207
+ ################################################################################
208
+
209
+ # Sometimes we start capturing a layer without knowing if we really
210
+ # want to make an entry for it. See ActiveRecord instrumentation for
211
+ # an example. We start capturing before we know if a query is cached
212
+ # or not, and want to skip any cached queries.
213
+ def skip_layer?(layer)
214
+ return false if layer.annotations.nil?
215
+ return true if layer.annotations[:ignorable]
29
216
  end
30
217
  end
31
218
  end
@@ -0,0 +1,55 @@
1
+ module ScoutApm
2
+ module LayerConverters
3
+ class DatabaseConverter < ConverterBase
4
+ def initialize(*)
5
+ super
6
+ @db_query_metric_set = DbQueryMetricSet.new
7
+ end
8
+
9
+ def register_hooks(walker)
10
+ super
11
+
12
+ return unless scope_layer
13
+
14
+ walker.on do |layer|
15
+ next if skip_layer?(layer)
16
+
17
+ stat = DbQueryMetricStats.new(
18
+ layer.name.model,
19
+ layer.name.normalized_operation,
20
+ scope_layer.legacy_metric_name, # controller_scope
21
+ 1, # count, this is a single query, so 1
22
+ layer.total_call_time,
23
+ records_returned(layer)
24
+ )
25
+ @db_query_metric_set << stat
26
+ end
27
+ end
28
+
29
+ def skip_layer?(layer)
30
+ layer.type != 'ActiveRecord' ||
31
+ layer.limited? ||
32
+ ( ! layer.name.respond_to?(:model)) ||
33
+ ( ! layer.name.respond_to?(:normalized_operation)) ||
34
+ layer.name.model.nil? ||
35
+ layer.name.normalized_operation.nil?
36
+ end
37
+
38
+ def record!
39
+ # Everything in the metric set here is from a single transaction, which
40
+ # we want to keep track of. (One web call did a User#find 10 times, but
41
+ # only due to 1 http request)
42
+ @db_query_metric_set.increment_transaction_count!
43
+ @store.track_db_query_metrics!(@db_query_metric_set)
44
+ end
45
+
46
+ def records_returned(layer)
47
+ if layer.annotations
48
+ layer.annotations.fetch(:record_count, 0)
49
+ else
50
+ 0
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -5,30 +5,42 @@ module ScoutApm
5
5
 
6
6
  def initialize(root_layer)
7
7
  @root_layer = root_layer
8
+
9
+ @on_blocks = []
10
+ @before_blocks = []
11
+ @after_blocks = []
8
12
  end
9
13
 
10
14
  def before(&block)
11
- @before_block = block
15
+ @before_blocks << block
12
16
  end
13
17
 
14
18
  def after(&block)
15
- @after_block = block
19
+ @after_blocks << block
20
+ end
21
+
22
+ def on(&block)
23
+ @on_blocks << block
16
24
  end
17
25
 
18
- def walk(layer=root_layer, &block)
26
+ def walk(layer=root_layer)
19
27
  # Need to run this for the root layer the first time through.
20
28
  if layer == root_layer
21
- @before_block.call(layer) if @before_block
22
- yield layer
23
- @after_block.call(layer) if @after_block
29
+ @before_blocks.each{|b| b.call(layer) }
30
+ @on_blocks.each{|b| b.call(layer) }
24
31
  end
25
32
 
26
33
  layer.children.each do |child|
27
- @before_block.call(child) if @before_block
28
- yield child
29
- walk(child, &block)
30
- @after_block.call(child) if @after_block
34
+ @before_blocks.each{|b| b.call(child) }
35
+ @on_blocks.each{|b| b.call(child) }
36
+ walk(child)
37
+ @after_blocks.each{|b| b.call(child) }
31
38
  end
39
+
40
+ if layer == root_layer
41
+ @after_blocks.each{|b| b.call(layer) }
42
+ end
43
+
32
44
  nil
33
45
  end
34
46
  end
@@ -1,19 +1,17 @@
1
1
  module ScoutApm
2
2
  module LayerConverters
3
3
  class ErrorConverter < ConverterBase
4
- def call
5
- scope = scope_layer
6
-
4
+ def record!
7
5
  # Should we mark a request as errored out if a middleware raises?
8
6
  # How does that interact w/ a tool like Sentry or Honeybadger?
9
- return {} unless scope
10
- return {} unless request.error?
7
+ return unless scope_layer
8
+ return unless request.error?
11
9
 
12
- meta = MetricMeta.new("Errors/#{scope.legacy_metric_name}", {})
10
+ meta = MetricMeta.new("Errors/#{scope_layer.legacy_metric_name}", {})
13
11
  stat = MetricStats.new
14
12
  stat.update!(1)
15
13
 
16
- { meta => stat }
14
+ @store.track!({ meta => stat })
17
15
  end
18
16
  end
19
17
  end