scout_apm 3.0.0.pre27 → 4.0.3
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.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +49 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +5 -5
- data/.travis.yml +19 -14
- data/CHANGELOG.markdown +139 -4
- data/Gemfile +1 -7
- data/README.markdown +13 -4
- data/Rakefile +1 -1
- data/ext/allocations/allocations.c +2 -0
- data/gems/README.md +28 -0
- data/gems/octoshark.gemfile +4 -0
- data/gems/rails3.gemfile +5 -0
- data/gems/rails4.gemfile +4 -0
- data/gems/rails5.gemfile +4 -0
- data/gems/rails6.gemfile +4 -0
- data/lib/scout_apm.rb +38 -9
- data/lib/scout_apm/agent.rb +29 -10
- data/lib/scout_apm/agent/exit_handler.rb +0 -1
- data/lib/scout_apm/agent_context.rb +22 -3
- data/lib/scout_apm/app_server_load.rb +7 -2
- data/lib/scout_apm/attribute_arranger.rb +0 -2
- data/lib/scout_apm/auto_instrument.rb +5 -0
- data/lib/scout_apm/auto_instrument/instruction_sequence.rb +31 -0
- data/lib/scout_apm/auto_instrument/layer.rb +23 -0
- data/lib/scout_apm/auto_instrument/parser.rb +27 -0
- data/lib/scout_apm/auto_instrument/rails.rb +175 -0
- data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +55 -0
- data/lib/scout_apm/background_job_integrations/que.rb +134 -0
- data/lib/scout_apm/background_job_integrations/resque.rb +6 -2
- data/lib/scout_apm/background_job_integrations/shoryuken.rb +124 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +5 -19
- data/lib/scout_apm/background_job_integrations/sneakers.rb +87 -0
- data/lib/scout_apm/config.rb +45 -8
- data/lib/scout_apm/detailed_trace.rb +217 -0
- data/lib/scout_apm/environment.rb +19 -1
- data/lib/scout_apm/error.rb +27 -0
- data/lib/scout_apm/error_service.rb +32 -0
- data/lib/scout_apm/error_service/error_buffer.rb +39 -0
- data/lib/scout_apm/error_service/error_record.rb +211 -0
- data/lib/scout_apm/error_service/ignored_exceptions.rb +66 -0
- data/lib/scout_apm/error_service/middleware.rb +32 -0
- data/lib/scout_apm/error_service/notifier.rb +33 -0
- data/lib/scout_apm/error_service/payload.rb +47 -0
- data/lib/scout_apm/error_service/periodic_work.rb +17 -0
- data/lib/scout_apm/error_service/railtie.rb +11 -0
- data/lib/scout_apm/error_service/sidekiq.rb +80 -0
- data/lib/scout_apm/extensions/transaction_callback_payload.rb +1 -1
- data/lib/scout_apm/fake_store.rb +3 -0
- data/lib/scout_apm/framework_integrations/rails_2.rb +2 -1
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +3 -1
- data/lib/scout_apm/git_revision.rb +6 -3
- data/lib/scout_apm/instant/middleware.rb +2 -1
- data/lib/scout_apm/instrument_manager.rb +9 -7
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +3 -1
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +56 -55
- data/lib/scout_apm/instruments/action_view.rb +122 -26
- data/lib/scout_apm/instruments/active_record.rb +66 -18
- data/lib/scout_apm/instruments/http.rb +48 -0
- data/lib/scout_apm/instruments/memcached.rb +43 -0
- data/lib/scout_apm/instruments/mongoid.rb +9 -4
- data/lib/scout_apm/instruments/net_http.rb +8 -1
- data/lib/scout_apm/instruments/typhoeus.rb +88 -0
- data/lib/scout_apm/job_record.rb +4 -2
- data/lib/scout_apm/layaway_file.rb +4 -0
- data/lib/scout_apm/layer.rb +6 -57
- data/lib/scout_apm/layer_children_set.rb +9 -8
- data/lib/scout_apm/layer_converters/converter_base.rb +15 -30
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +12 -2
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +14 -4
- data/lib/scout_apm/layer_converters/trace_converter.rb +184 -0
- data/lib/scout_apm/limited_layer.rb +0 -7
- data/lib/scout_apm/metric_stats.rb +0 -8
- data/lib/scout_apm/middleware.rb +1 -1
- data/lib/scout_apm/periodic_work.rb +19 -0
- data/lib/scout_apm/remote/message.rb +4 -0
- data/lib/scout_apm/remote/server.rb +13 -1
- data/lib/scout_apm/reporter.rb +8 -3
- data/lib/scout_apm/reporting.rb +2 -1
- data/lib/scout_apm/request_histograms.rb +8 -0
- data/lib/scout_apm/serializers/app_server_load_serializer.rb +4 -0
- data/lib/scout_apm/serializers/directive_serializer.rb +4 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +2 -2
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +30 -15
- data/lib/scout_apm/slow_job_record.rb +5 -1
- data/lib/scout_apm/slow_policy/age_policy.rb +33 -0
- data/lib/scout_apm/slow_policy/percent_policy.rb +22 -0
- data/lib/scout_apm/slow_policy/percentile_policy.rb +24 -0
- data/lib/scout_apm/slow_policy/policy.rb +21 -0
- data/lib/scout_apm/slow_policy/speed_policy.rb +16 -0
- data/lib/scout_apm/slow_request_policy.rb +18 -77
- data/lib/scout_apm/slow_transaction.rb +3 -1
- data/lib/scout_apm/store.rb +0 -1
- data/lib/scout_apm/tracked_request.rb +39 -30
- data/lib/scout_apm/utils/backtrace_parser.rb +3 -0
- data/lib/scout_apm/utils/marshal_logging.rb +90 -0
- data/lib/scout_apm/utils/sql_sanitizer.rb +10 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +8 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +6 -0
- data/lib/scout_apm/utils/unique_id.rb +27 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +13 -7
- data/test/test_helper.rb +2 -2
- data/test/unit/agent_context_test.rb +29 -0
- data/test/unit/auto_instrument/assignments-instrumented.rb +31 -0
- data/test/unit/auto_instrument/assignments.rb +31 -0
- data/test/unit/auto_instrument/controller-ast.txt +57 -0
- data/test/unit/auto_instrument/controller-instrumented.rb +49 -0
- data/test/unit/auto_instrument/controller.rb +49 -0
- data/test/unit/auto_instrument/rescue_from-instrumented.rb +13 -0
- data/test/unit/auto_instrument/rescue_from.rb +13 -0
- data/test/unit/auto_instrument_test.rb +54 -0
- data/test/unit/environment_test.rb +2 -2
- data/test/unit/error_service/error_buffer_test.rb +25 -0
- data/test/unit/error_service/ignored_exceptions_test.rb +49 -0
- data/test/unit/instruments/active_record_test.rb +40 -0
- data/test/unit/layer_children_set_test.rb +9 -0
- data/test/unit/request_histograms_test.rb +17 -0
- data/test/unit/serializers/payload_serializer_test.rb +39 -5
- data/test/unit/slow_request_policy_test.rb +41 -13
- data/test/unit/sql_sanitizer_test.rb +78 -0
- metadata +101 -62
- data/ext/stacks/extconf.rb +0 -37
- data/ext/stacks/scout_atomics.h +0 -86
- data/ext/stacks/stacks.c +0 -814
- data/lib/scout_apm/slow_job_policy.rb +0 -111
- data/lib/scout_apm/trace_compactor.rb +0 -312
- data/lib/scout_apm/utils/fake_stacks.rb +0 -88
- data/test/unit/instruments/active_record_instruments_test.rb +0 -5
- data/test/unit/slow_job_policy_test.rb +0 -6
- data/tester.rb +0 -53
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# DetailedTrace contains all details about a certain transaction, spans with
|
|
2
|
+
# start & stop times, tags, etc.
|
|
3
|
+
|
|
4
|
+
# {
|
|
5
|
+
# "version": 1,
|
|
6
|
+
# "identity": {
|
|
7
|
+
# "transaction_id": "req-....",
|
|
8
|
+
# "revision": "abcdef",
|
|
9
|
+
# "start_instant": "01-01-01T00:00:00.0000Z",
|
|
10
|
+
# "stop_instant": "01-01-01T00:00:01.0000Z",
|
|
11
|
+
# "type": "Web",
|
|
12
|
+
# "naming": {
|
|
13
|
+
# "path": "/users",
|
|
14
|
+
# "code": "UsersController#index",
|
|
15
|
+
# },
|
|
16
|
+
# "score": {
|
|
17
|
+
# "total": 10.5,
|
|
18
|
+
# "percentile": 4.5,
|
|
19
|
+
# "age": 2.0,
|
|
20
|
+
# "memory_delta": 3,
|
|
21
|
+
# "allocations": 1
|
|
22
|
+
# }
|
|
23
|
+
# },
|
|
24
|
+
#
|
|
25
|
+
# "tags": {
|
|
26
|
+
# "allocations": 1000
|
|
27
|
+
# },
|
|
28
|
+
#
|
|
29
|
+
# "spans": [
|
|
30
|
+
# ...
|
|
31
|
+
# ]
|
|
32
|
+
|
|
33
|
+
class DetailedTrace
|
|
34
|
+
attr_reader :spans
|
|
35
|
+
attr_reader :tags
|
|
36
|
+
|
|
37
|
+
attr_reader :transaction_id
|
|
38
|
+
attr_reader :revision
|
|
39
|
+
attr_reader :start_instant
|
|
40
|
+
attr_reader :stop_instant
|
|
41
|
+
attr_reader :duration
|
|
42
|
+
attr_reader :type # "Web" or "Job"
|
|
43
|
+
attr_reader :host
|
|
44
|
+
|
|
45
|
+
attr_reader :path # /users/1
|
|
46
|
+
attr_reader :code # UsersController#show or similar
|
|
47
|
+
|
|
48
|
+
attr_reader :total_score
|
|
49
|
+
attr_reader :percentile_score
|
|
50
|
+
attr_reader :age_score
|
|
51
|
+
attr_reader :memory_delta_score
|
|
52
|
+
attr_reader :memory_allocations_score
|
|
53
|
+
|
|
54
|
+
VERSION = 1
|
|
55
|
+
|
|
56
|
+
def initialize(transaction_id, revision, host, start_instant, stop_instant, type, path, code, spans, tags)
|
|
57
|
+
@spans = spans
|
|
58
|
+
@tags = DetailedTraceTags(tags)
|
|
59
|
+
|
|
60
|
+
@transaction_id = transaction_id
|
|
61
|
+
@revision = revision
|
|
62
|
+
@host = host
|
|
63
|
+
@start_instant = start_instant
|
|
64
|
+
@stop_instant = stop_instant
|
|
65
|
+
@type = type
|
|
66
|
+
|
|
67
|
+
@path = path
|
|
68
|
+
@code = code
|
|
69
|
+
|
|
70
|
+
@total_score = 0
|
|
71
|
+
@percentile_score = 0
|
|
72
|
+
@age_score = 0
|
|
73
|
+
@memory_delta_score = 0
|
|
74
|
+
@memory_allocations_score = 0
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def as_json(*)
|
|
79
|
+
{
|
|
80
|
+
:version => VERSION,
|
|
81
|
+
:identity => {
|
|
82
|
+
:transaction_id => transaction_id,
|
|
83
|
+
:revision => revision,
|
|
84
|
+
:host => host,
|
|
85
|
+
:start_instant => start_instant.iso8601(6),
|
|
86
|
+
:stop_instant => stop_instant.iso8601(6),
|
|
87
|
+
:type => type,
|
|
88
|
+
:naming => {
|
|
89
|
+
:path => path,
|
|
90
|
+
:code => code,
|
|
91
|
+
},
|
|
92
|
+
:score => {
|
|
93
|
+
:total => total_score,
|
|
94
|
+
:percentile => percentile_score,
|
|
95
|
+
:age => age_score,
|
|
96
|
+
:memory_delta => memory_delta_score,
|
|
97
|
+
:allocations => memory_allocations_score,
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
:tags => tags.as_json,
|
|
101
|
+
:spans => spans.map{|span| span.as_json},
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
########################
|
|
106
|
+
# Scorable interface
|
|
107
|
+
#
|
|
108
|
+
# Needed so we can merge ScoredItemSet instances
|
|
109
|
+
def call
|
|
110
|
+
self
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def name
|
|
114
|
+
code
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def score
|
|
118
|
+
@total_score
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
##########
|
|
124
|
+
# SPAN #
|
|
125
|
+
##########
|
|
126
|
+
|
|
127
|
+
#
|
|
128
|
+
# {
|
|
129
|
+
# "type": "Standard",
|
|
130
|
+
# "identity": {
|
|
131
|
+
# "id": "....",
|
|
132
|
+
# "parent_id": "....",
|
|
133
|
+
# "start_time": "01-01-01T00:00:00.0000Z",
|
|
134
|
+
# "stop_time": "01-01-01T00:00:00.0001Z",
|
|
135
|
+
# "operation": "SQL/User/find"
|
|
136
|
+
# },
|
|
137
|
+
# "tags": {
|
|
138
|
+
# "allocations": 1000,
|
|
139
|
+
# "db.statement": "SELECT * FROM users where id = 1",
|
|
140
|
+
# "db.rows": 1,
|
|
141
|
+
# "backtrace": [ {
|
|
142
|
+
# "file": "app/controllers/users_controller.rb",
|
|
143
|
+
# "line": 10,
|
|
144
|
+
# "function": "index"
|
|
145
|
+
# } ]
|
|
146
|
+
# }
|
|
147
|
+
class DetailedTraceSpan
|
|
148
|
+
attr_reader :tags
|
|
149
|
+
|
|
150
|
+
attr_reader :span_type
|
|
151
|
+
attr_reader :span_id, :parent_id
|
|
152
|
+
attr_reader :start_instant, :stop_instant
|
|
153
|
+
|
|
154
|
+
# What is the "name" of this span.
|
|
155
|
+
#
|
|
156
|
+
# Examples:
|
|
157
|
+
# SQL/User/find
|
|
158
|
+
# Controller/Users/index
|
|
159
|
+
# HTTP/GET/example.com
|
|
160
|
+
attr_reader :operation
|
|
161
|
+
|
|
162
|
+
def initialize(span_id, parent_id, start_instant, stop_instant, operation, tags)
|
|
163
|
+
# This will be dynamic when we implement limited spans
|
|
164
|
+
@span_type = "Standard"
|
|
165
|
+
|
|
166
|
+
@span_id = span_id
|
|
167
|
+
@parent_id = parent_id
|
|
168
|
+
|
|
169
|
+
@start_instant = start_instant
|
|
170
|
+
@stop_instant = stop_instant
|
|
171
|
+
@operation = operation
|
|
172
|
+
@tags = DetailedTraceTags(tags)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def as_json(*)
|
|
176
|
+
{
|
|
177
|
+
:type => @span_type,
|
|
178
|
+
:identity => {
|
|
179
|
+
:id => span_id,
|
|
180
|
+
:parent_id => parent_id,
|
|
181
|
+
:start_instant => start_instant.iso8601(6),
|
|
182
|
+
:stop_instant => stop_instant.iso8601(6),
|
|
183
|
+
:operation => operation,
|
|
184
|
+
},
|
|
185
|
+
:tags => @tags.as_json,
|
|
186
|
+
}
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
#############
|
|
192
|
+
# content #
|
|
193
|
+
#############
|
|
194
|
+
|
|
195
|
+
# Tags for either a request, or a span
|
|
196
|
+
class DetailedTraceTags
|
|
197
|
+
attr_reader :tags
|
|
198
|
+
|
|
199
|
+
def initialize(hash)
|
|
200
|
+
@tags = hash
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# @tags is already a hash, so no conversion needed
|
|
204
|
+
def as_json(*)
|
|
205
|
+
@tags
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Converter function to turn an input into a DetailedTraceTags object
|
|
210
|
+
def DetailedTraceTags(arg)
|
|
211
|
+
if DetailedTraceTags === arg
|
|
212
|
+
arg
|
|
213
|
+
elsif Hash === arg
|
|
214
|
+
DetailedTraceTags.new(arg)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
@@ -26,7 +26,10 @@ module ScoutApm
|
|
|
26
26
|
BACKGROUND_JOB_INTEGRATIONS = [
|
|
27
27
|
ScoutApm::BackgroundJobIntegrations::Resque.new,
|
|
28
28
|
ScoutApm::BackgroundJobIntegrations::Sidekiq.new,
|
|
29
|
+
ScoutApm::BackgroundJobIntegrations::Shoryuken.new,
|
|
30
|
+
ScoutApm::BackgroundJobIntegrations::Sneakers.new,
|
|
29
31
|
ScoutApm::BackgroundJobIntegrations::DelayedJob.new,
|
|
32
|
+
ScoutApm::BackgroundJobIntegrations::Que.new,
|
|
30
33
|
]
|
|
31
34
|
|
|
32
35
|
FRAMEWORK_INTEGRATIONS = [
|
|
@@ -179,9 +182,24 @@ module ScoutApm
|
|
|
179
182
|
@ruby_2 = defined?(RUBY_VERSION) && RUBY_VERSION.match(/^2/)
|
|
180
183
|
end
|
|
181
184
|
|
|
185
|
+
def ruby_3?
|
|
186
|
+
return @ruby_3 if defined?(@ruby_3)
|
|
187
|
+
@ruby_3 = defined?(RUBY_VERSION) && RUBY_VERSION.match(/^3/)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def ruby_minor
|
|
191
|
+
return @ruby_minor if defined?(@ruby_minor)
|
|
192
|
+
@ruby_minor = defined?(RUBY_VERSION) && RUBY_VERSION.split(".")[1].to_i
|
|
193
|
+
end
|
|
194
|
+
|
|
182
195
|
# Returns true if this Ruby version supports Module#prepend.
|
|
183
196
|
def supports_module_prepend?
|
|
184
|
-
ruby_2?
|
|
197
|
+
ruby_2? || ruby_3?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Returns true if this Ruby version supports Module#prepend.
|
|
201
|
+
def supports_kwarg_delegation?
|
|
202
|
+
ruby_3? || (ruby_2? && ruby_minor >= 7)
|
|
185
203
|
end
|
|
186
204
|
|
|
187
205
|
# Returns a string representation of the OS (ex: darwin, linux)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Public API for the Scout Error Monitoring service
|
|
2
|
+
#
|
|
3
|
+
# See-Also ScoutApm::Transaction and ScoutApm::Tracing for APM related APIs
|
|
4
|
+
module ScoutApm
|
|
5
|
+
module Error
|
|
6
|
+
# Capture an exception, optionally with an environment hash. This may be a
|
|
7
|
+
# Rack environment, but is not required.
|
|
8
|
+
def self.capture(exception, env={})
|
|
9
|
+
context = ScoutApm::Agent.instance.context
|
|
10
|
+
|
|
11
|
+
# Skip if error monitoring isn't enabled at all
|
|
12
|
+
if ! context.config.value("errors_enabled")
|
|
13
|
+
return false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Skip if this one error is ignored
|
|
17
|
+
if context.ignored_exceptions.ignored?(exception)
|
|
18
|
+
return false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Capture the error for further processing and shipping
|
|
22
|
+
context.error_buffer.capture(exception, env)
|
|
23
|
+
|
|
24
|
+
return true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "net/https"
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module ScoutApm
|
|
6
|
+
module ErrorService
|
|
7
|
+
API_VERSION = "1"
|
|
8
|
+
|
|
9
|
+
HEADERS = {
|
|
10
|
+
"Content-type" => "application/json",
|
|
11
|
+
"Accept" => "application/json"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Public API to force a given exception to be captured.
|
|
15
|
+
# Still obeys the ignore list
|
|
16
|
+
# Used internally by SidekiqException
|
|
17
|
+
def self.capture(exception, params = {})
|
|
18
|
+
return if disabled?
|
|
19
|
+
return if ScoutApm::Agent.instance.context.ignored_exceptions.ignore?(exception)
|
|
20
|
+
|
|
21
|
+
context.errors_buffer.capture(exception, env)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.enabled?
|
|
25
|
+
ScoutApm::Agent.instance.context.config.value("errors_enabled")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.disabled?
|
|
29
|
+
!enabled?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Holds onto exceptions, and moves them forward to shipping when appropriate
|
|
2
|
+
module ScoutApm
|
|
3
|
+
module ErrorService
|
|
4
|
+
class ErrorBuffer
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
attr_reader :agent_context
|
|
8
|
+
|
|
9
|
+
def initialize(agent_context)
|
|
10
|
+
@agent_context = agent_context
|
|
11
|
+
@error_records = []
|
|
12
|
+
@mutex = Monitor.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def capture(exception, env)
|
|
16
|
+
context = ScoutApm::Context.current
|
|
17
|
+
|
|
18
|
+
@mutex.synchronize {
|
|
19
|
+
@error_records << ErrorRecord.new(agent_context, exception, env, context)
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get_and_reset_error_records
|
|
24
|
+
@mutex.synchronize {
|
|
25
|
+
ret = @error_records
|
|
26
|
+
@error_records = []
|
|
27
|
+
ret
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Enables enumerable - for count and each and similar methods
|
|
32
|
+
def each
|
|
33
|
+
@error_records.each do |error_record|
|
|
34
|
+
yield error_record
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
# Converts the raw error data captured into the captured data, and holds it
|
|
4
|
+
# until it's ready to be reported.
|
|
5
|
+
class ErrorRecord
|
|
6
|
+
attr_reader :exception_class
|
|
7
|
+
attr_reader :message
|
|
8
|
+
attr_reader :request_uri
|
|
9
|
+
attr_reader :request_params
|
|
10
|
+
attr_reader :request_session
|
|
11
|
+
attr_reader :environment
|
|
12
|
+
attr_reader :trace
|
|
13
|
+
attr_reader :request_components
|
|
14
|
+
attr_reader :context
|
|
15
|
+
|
|
16
|
+
def initialize(agent_context, exception, env, context=nil)
|
|
17
|
+
@agent_context = agent_context
|
|
18
|
+
|
|
19
|
+
@context = if context
|
|
20
|
+
context.to_hash
|
|
21
|
+
else
|
|
22
|
+
{}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@exception_class = LengthLimit.new(exception.class.name).to_s
|
|
26
|
+
@message = LengthLimit.new(exception.message, 100).to_s
|
|
27
|
+
@request_uri = LengthLimit.new(rack_request_url(env), 200).to_s
|
|
28
|
+
@request_params = clean_params(env["action_dispatch.request.parameters"])
|
|
29
|
+
@request_session = clean_params(session_data(env))
|
|
30
|
+
@environment = clean_params(strip_env(env))
|
|
31
|
+
@trace = clean_backtrace(exception.backtrace)
|
|
32
|
+
@request_components = components(env)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# TODO: This is rails specific
|
|
36
|
+
def components(env)
|
|
37
|
+
components = {}
|
|
38
|
+
unless env["action_dispatch.request.parameters"].nil?
|
|
39
|
+
components[:controller] = env["action_dispatch.request.parameters"][:controller] || nil
|
|
40
|
+
components[:action] = env["action_dispatch.request.parameters"][:action] || nil
|
|
41
|
+
components[:module] = env["action_dispatch.request.parameters"][:module] || nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# For background workers like sidekiq
|
|
45
|
+
# TODO: extract data creation for background jobs
|
|
46
|
+
components[:controller] ||= env[:custom_controller]
|
|
47
|
+
|
|
48
|
+
components
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# TODO: Can I use the same thing we use in traces?
|
|
52
|
+
def rack_request_url(env)
|
|
53
|
+
protocol = rack_scheme(env)
|
|
54
|
+
protocol = protocol.nil? ? "" : "#{protocol}://"
|
|
55
|
+
|
|
56
|
+
host = env["SERVER_NAME"] || ""
|
|
57
|
+
path = env["REQUEST_URI"] || ""
|
|
58
|
+
port = env["SERVER_PORT"] || "80"
|
|
59
|
+
port = ["80", "443"].include?(port.to_s) ? "" : ":#{port}"
|
|
60
|
+
|
|
61
|
+
protocol.to_s + host.to_s + port.to_s + path.to_s
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def rack_scheme(env)
|
|
65
|
+
if env["HTTPS"] == "on"
|
|
66
|
+
"https"
|
|
67
|
+
elsif env["HTTP_X_FORWARDED_PROTO"]
|
|
68
|
+
env["HTTP_X_FORWARDED_PROTO"].split(",")[0]
|
|
69
|
+
else
|
|
70
|
+
env["rack.url_scheme"]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# TODO: This name is too vague
|
|
75
|
+
def clean_params(params)
|
|
76
|
+
return if params.nil?
|
|
77
|
+
|
|
78
|
+
normalized = normalize_data(params)
|
|
79
|
+
filter_params(normalized)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# TODO: When was backtrace_cleaner introduced?
|
|
83
|
+
def clean_backtrace(backtrace)
|
|
84
|
+
if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner)
|
|
85
|
+
Rails.backtrace_cleaner.send(:filter, backtrace)
|
|
86
|
+
else
|
|
87
|
+
backtrace
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Deletes params from env
|
|
92
|
+
#
|
|
93
|
+
# These are not configurable, and will leak PII info up to Scout if
|
|
94
|
+
# allowed through. Things like specific parameters can be exposed with
|
|
95
|
+
# the ScoutApm::Context interface.
|
|
96
|
+
KEYS_TO_REMOVE = [
|
|
97
|
+
"rack.request.form_hash",
|
|
98
|
+
"rack.request.form_vars",
|
|
99
|
+
"async.callback",
|
|
100
|
+
|
|
101
|
+
# Security related items
|
|
102
|
+
"action_dispatch.secret_key_base",
|
|
103
|
+
"action_dispatch.http_auth_salt",
|
|
104
|
+
"action_dispatch.signed_cookie_salt",
|
|
105
|
+
"action_dispatch.encrypted_cookie_salt",
|
|
106
|
+
"action_dispatch.encrypted_signed_cookie_salt",
|
|
107
|
+
"action_dispatch.authenticated_encrypted_cookie_salt",
|
|
108
|
+
|
|
109
|
+
# Raw data from the URL & parameters. Would bypass our normal params filtering
|
|
110
|
+
"QUERY_STRING",
|
|
111
|
+
"REQUEST_URI",
|
|
112
|
+
"REQUEST_PATH",
|
|
113
|
+
"ORIGINAL_FULLPATH",
|
|
114
|
+
"action_dispatch.request.query_parameters",
|
|
115
|
+
"action_dispatch.request.parameters",
|
|
116
|
+
"rack.request.query_string",
|
|
117
|
+
"rack.request.query_hash",
|
|
118
|
+
]
|
|
119
|
+
def strip_env(env)
|
|
120
|
+
env.reject { |k, v| KEYS_TO_REMOVE.include?(k) }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def session_data(env)
|
|
124
|
+
session = env["action_dispatch.request.session"]
|
|
125
|
+
return if session.nil?
|
|
126
|
+
|
|
127
|
+
if session.respond_to?(:to_hash)
|
|
128
|
+
session.to_hash
|
|
129
|
+
else
|
|
130
|
+
session.data
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# TODO: Rename and make this clearer. I think it maps over the whole tree of a hash, and to_s each leaf node?
|
|
135
|
+
def normalize_data(hash)
|
|
136
|
+
new_hash = {}
|
|
137
|
+
|
|
138
|
+
hash.each do |key, value|
|
|
139
|
+
if value.respond_to?(:to_hash)
|
|
140
|
+
begin
|
|
141
|
+
new_hash[key] = normalize_data(value.to_hash)
|
|
142
|
+
rescue
|
|
143
|
+
new_hash[key] = LengthLimit.new(value.to_s).to_s
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
new_hash[key] = LengthLimit.new(value.to_s).to_s
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
new_hash
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
###################
|
|
154
|
+
# Filtering Params
|
|
155
|
+
###################
|
|
156
|
+
|
|
157
|
+
# Replaces parameter values with a string / set in config file
|
|
158
|
+
def filter_params(params)
|
|
159
|
+
return params unless filtered_params_config
|
|
160
|
+
|
|
161
|
+
params.each do |k, v|
|
|
162
|
+
if filter_key?(k)
|
|
163
|
+
params[k] = "[FILTERED]"
|
|
164
|
+
elsif v.respond_to?(:to_hash)
|
|
165
|
+
filter_params(params[k])
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
params
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Check, if a key should be filtered
|
|
173
|
+
def filter_key?(key)
|
|
174
|
+
params_to_filter.any? do |filter|
|
|
175
|
+
key.to_s == filter.to_s # key.to_s.include?(filter.to_s)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def params_to_filter
|
|
180
|
+
@params_to_filter ||= filtered_params_config + rails_filtered_params
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Accessor for the filtered params config value. Will be removed as we refactor and clean up this code.
|
|
184
|
+
# TODO: Flip this over to use a new class like filtered exceptions?
|
|
185
|
+
def filtered_params_config
|
|
186
|
+
@agent_context.config.value("errors_filtered_params")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def rails_filtered_params
|
|
190
|
+
return [] unless defined?(Rails)
|
|
191
|
+
Rails.configuration.filter_parameters
|
|
192
|
+
rescue
|
|
193
|
+
[]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
class LengthLimit
|
|
197
|
+
attr_reader :text
|
|
198
|
+
attr_reader :char_limit
|
|
199
|
+
|
|
200
|
+
def initialize(text, char_limit=100)
|
|
201
|
+
@text = text
|
|
202
|
+
@char_limit = char_limit
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def to_s
|
|
206
|
+
text[0..char_limit]
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|