scout_apm 3.0.0.pre27 → 4.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|