scout_apm 2.6.9 → 2.6.10
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 +4 -4
- data/CHANGELOG.markdown +4 -0
- data/lib/scout_apm.rb +15 -0
- data/lib/scout_apm/agent.rb +22 -0
- data/lib/scout_apm/agent_context.rb +12 -0
- data/lib/scout_apm/config.rb +15 -2
- 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/middleware.rb +1 -1
- data/lib/scout_apm/reporter.rb +7 -1
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +28 -10
- data/lib/scout_apm/version.rb +1 -1
- data/test/unit/agent_context_test.rb +15 -0
- 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/serializers/payload_serializer_test.rb +36 -0
- metadata +18 -57
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 503d33cb7c689cb239c328bee4ba2a1df380df703f3e3a6f5f843676b9bff0e4
|
|
4
|
+
data.tar.gz: 444e240dc8c4922ee932684c1c10b3fb5d658e96228c901b33a9a5ca3cb1e469
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 715c6a3f044d2091207667fc6a9857573da528cf03ee5d64ccb758c10ec387b49829a891ae43bb51b444077c898f57fc73257f2d963407820ed7114fdedb52c4
|
|
7
|
+
data.tar.gz: 373b60dc42e0417e6003698c143138ff50024802285ff3754cc706efc1bc305a941fddbe27e6a471d8f09b3ffc84b5a3cfb43e57a1715968d7cac47ee069f5b3
|
data/CHANGELOG.markdown
CHANGED
data/lib/scout_apm.rb
CHANGED
|
@@ -186,6 +186,16 @@ require 'scout_apm/tasks/support'
|
|
|
186
186
|
require 'scout_apm/extensions/config'
|
|
187
187
|
require 'scout_apm/extensions/transaction_callback_payload'
|
|
188
188
|
|
|
189
|
+
require 'scout_apm/error_service'
|
|
190
|
+
require 'scout_apm/error_service/middleware'
|
|
191
|
+
require 'scout_apm/error_service/notifier'
|
|
192
|
+
require 'scout_apm/error_service/sidekiq'
|
|
193
|
+
require 'scout_apm/error_service/ignored_exceptions'
|
|
194
|
+
require 'scout_apm/error_service/error_buffer'
|
|
195
|
+
require 'scout_apm/error_service/error_record'
|
|
196
|
+
require 'scout_apm/error_service/periodic_work'
|
|
197
|
+
require 'scout_apm/error_service/payload'
|
|
198
|
+
|
|
189
199
|
if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR >= 3 && defined?(Rails::Railtie)
|
|
190
200
|
module ScoutApm
|
|
191
201
|
class Railtie < Rails::Railtie
|
|
@@ -205,6 +215,11 @@ if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR
|
|
|
205
215
|
ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is disabled.")
|
|
206
216
|
end
|
|
207
217
|
|
|
218
|
+
if ScoutApm::Agent.instance.context.config.value("errors_enabled")
|
|
219
|
+
app.config.middleware.insert_after ActionDispatch::DebugExceptions, ScoutApm::ErrorService::Middleware
|
|
220
|
+
ScoutApm::ErrorService::Sidekiq.new.install
|
|
221
|
+
end
|
|
222
|
+
|
|
208
223
|
# Install the middleware every time in development mode.
|
|
209
224
|
# The middleware is a noop if dev_trace is not enabled in config
|
|
210
225
|
if Rails.env.development?
|
data/lib/scout_apm/agent.rb
CHANGED
|
@@ -66,6 +66,7 @@ module ScoutApm
|
|
|
66
66
|
|
|
67
67
|
if context.started?
|
|
68
68
|
start_background_worker unless background_worker_running?
|
|
69
|
+
start_error_service_background_worker unless error_service_background_worker_running?
|
|
69
70
|
return
|
|
70
71
|
end
|
|
71
72
|
|
|
@@ -81,6 +82,7 @@ module ScoutApm
|
|
|
81
82
|
@app_server_load ||= AppServerLoad.new(context).run
|
|
82
83
|
|
|
83
84
|
start_background_worker
|
|
85
|
+
start_error_service_background_worker
|
|
84
86
|
end
|
|
85
87
|
|
|
86
88
|
def instrument_manager
|
|
@@ -198,5 +200,25 @@ module ScoutApm
|
|
|
198
200
|
@background_worker &&
|
|
199
201
|
@background_worker.running?
|
|
200
202
|
end
|
|
203
|
+
|
|
204
|
+
# seconds to batch error reports
|
|
205
|
+
ERROR_SEND_FREQUENCY = 5
|
|
206
|
+
def start_error_service_background_worker
|
|
207
|
+
periodic_work = ScoutApm::ErrorService::PeriodicWork.new(context)
|
|
208
|
+
|
|
209
|
+
@error_service_background_worker = ScoutApm::BackgroundWorker.new(context, ERROR_SEND_FREQUENCY)
|
|
210
|
+
@error_service_background_worker_thread = Thread.new do
|
|
211
|
+
@error_service_background_worker.start {
|
|
212
|
+
periodic_work.run
|
|
213
|
+
}
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def error_service_background_worker_running?
|
|
218
|
+
@error_service_background_worker_thread &&
|
|
219
|
+
@error_service_background_worker_thread.alive? &&
|
|
220
|
+
@error_service_background_worker &&
|
|
221
|
+
@error_service_background_worker.running?
|
|
222
|
+
end
|
|
201
223
|
end
|
|
202
224
|
end
|
|
@@ -142,6 +142,18 @@ module ScoutApm
|
|
|
142
142
|
config.value('dev_trace') && environment.env == "development"
|
|
143
143
|
end
|
|
144
144
|
|
|
145
|
+
###################
|
|
146
|
+
# Error Service #
|
|
147
|
+
###################
|
|
148
|
+
|
|
149
|
+
def error_buffer
|
|
150
|
+
@error_buffer ||= ScoutApm::ErrorService::ErrorBuffer.new(self)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def ignored_exceptions
|
|
154
|
+
@ignored_exceptions ||= ScoutApm::ErrorService::IgnoredExceptions.new(self, config.value('errors_ignored_exceptions'))
|
|
155
|
+
end
|
|
156
|
+
|
|
145
157
|
#############
|
|
146
158
|
# Setters #
|
|
147
159
|
#############
|
data/lib/scout_apm/config.rb
CHANGED
|
@@ -80,7 +80,13 @@ module ScoutApm
|
|
|
80
80
|
'instrument_http_url_length',
|
|
81
81
|
'timeline_traces',
|
|
82
82
|
'auto_instruments',
|
|
83
|
-
'auto_instruments_ignore'
|
|
83
|
+
'auto_instruments_ignore',
|
|
84
|
+
|
|
85
|
+
# Error Service Related Configuration
|
|
86
|
+
'errors_enabled',
|
|
87
|
+
'errors_ignored_exceptions',
|
|
88
|
+
'errors_filtered_params',
|
|
89
|
+
'errors_host',
|
|
84
90
|
]
|
|
85
91
|
|
|
86
92
|
################################################################################
|
|
@@ -176,6 +182,9 @@ module ScoutApm
|
|
|
176
182
|
'timeline_traces' => BooleanCoercion.new,
|
|
177
183
|
'auto_instruments' => BooleanCoercion.new,
|
|
178
184
|
'auto_instruments_ignore' => JsonCoercion.new,
|
|
185
|
+
'errors_enabled' => BooleanCoercion.new,
|
|
186
|
+
'errors_ignored_exceptions' => JsonCoercion.new,
|
|
187
|
+
'errors_filtered_params' => JsonCoercion.new,
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
|
|
@@ -286,7 +295,11 @@ module ScoutApm
|
|
|
286
295
|
'timeline_traces' => true,
|
|
287
296
|
'auto_instruments' => false,
|
|
288
297
|
'auto_instruments_ignore' => [],
|
|
289
|
-
'ssl_cert_file' => File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] )
|
|
298
|
+
'ssl_cert_file' => File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] ),
|
|
299
|
+
'errors_enabled' => false,
|
|
300
|
+
'errors_ignored_exceptions' => %w(ActiveRecord::RecordNotFound ActionController::RoutingError),
|
|
301
|
+
'errors_filtered_params' => %w(password s3-key),
|
|
302
|
+
'errors_host' => 'https://errors.scoutapm.com',
|
|
290
303
|
}.freeze
|
|
291
304
|
|
|
292
305
|
def value(key)
|
|
@@ -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
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Encapsulates the management and checking of ignored exceptions. Allows using
|
|
2
|
+
# string matches on the class name, or arbitrary matching with a callback
|
|
3
|
+
module ScoutApm
|
|
4
|
+
module ErrorService
|
|
5
|
+
class IgnoredExceptions
|
|
6
|
+
attr_reader :ignored_exceptions
|
|
7
|
+
attr_reader :blocks
|
|
8
|
+
|
|
9
|
+
def initialize(context, from_config)
|
|
10
|
+
@context = context
|
|
11
|
+
@ignored_exceptions = Array(from_config).map{ |e| normalize_as_klass(e) }
|
|
12
|
+
@blocks = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Add a single ignored exception by class name
|
|
16
|
+
def add(klass_or_str)
|
|
17
|
+
@ignored_exceptions << normalize_as_klass(klass_or_str)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Add a callback block that will be called on every error. If it returns
|
|
21
|
+
# Signature of blocks: ->(exception object): truthy or falsy value
|
|
22
|
+
def add_callback(&block)
|
|
23
|
+
@blocks << block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ignored?(exception_object)
|
|
27
|
+
klass = normalize_as_klass(exception_object)
|
|
28
|
+
|
|
29
|
+
# Check if we ignored this error by name (typical way to ignore)
|
|
30
|
+
if ignored_exceptions.any? { |ignored| klass.ancestors.include?(ignored) }
|
|
31
|
+
return true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# For each block, see if it says we should ignore this error
|
|
35
|
+
blocks.each do |b|
|
|
36
|
+
if b.call(exception_object)
|
|
37
|
+
return true
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def normalize_as_klass(klass_or_str)
|
|
47
|
+
if Module === klass_or_str
|
|
48
|
+
return klass_or_str
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if klass_or_str.is_a?(Exception)
|
|
52
|
+
return klass_or_str.class
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if String === klass_or_str
|
|
56
|
+
maybe = ScoutApm::Utils::KlassHelper.lookup(klass_or_str)
|
|
57
|
+
if Module === maybe
|
|
58
|
+
return maybe
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
klass_or_str
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class Middleware
|
|
4
|
+
def initialize(app)
|
|
5
|
+
@app = app
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
9
|
+
begin
|
|
10
|
+
response = @app.call(env)
|
|
11
|
+
rescue Exception => exception
|
|
12
|
+
puts "[Scout Error Service] Caught Exception: #{exception.class.name}"
|
|
13
|
+
|
|
14
|
+
context = ScoutApm::Agent.instance.context
|
|
15
|
+
|
|
16
|
+
# Bail out early, and reraise if the error is not interesting.
|
|
17
|
+
if context.ignored_exceptions.ignored?(exception)
|
|
18
|
+
raise
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Capture the error for further processing and shipping
|
|
22
|
+
context.error_buffer.capture(exception, env)
|
|
23
|
+
|
|
24
|
+
# Finally re-raise
|
|
25
|
+
raise
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
response
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class Notifier
|
|
4
|
+
attr_reader :context
|
|
5
|
+
attr_reader :reporter
|
|
6
|
+
|
|
7
|
+
def initialize(context)
|
|
8
|
+
@context = context
|
|
9
|
+
@reporter = ScoutApm::Reporter.new(context, :errors)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def ship
|
|
13
|
+
error_records = context.error_buffer.get_and_reset_error_records
|
|
14
|
+
if error_records.any?
|
|
15
|
+
payload = ScoutApm::ErrorService::Payload.new(context, error_records)
|
|
16
|
+
reporter.report(
|
|
17
|
+
payload.serialize,
|
|
18
|
+
default_headers.merge("X-Error-Count" => error_records.length.to_s)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def default_headers
|
|
26
|
+
{
|
|
27
|
+
"Content-Type" => "application/json",
|
|
28
|
+
"Accept" => "application/json"
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class Payload
|
|
4
|
+
attr_reader :context
|
|
5
|
+
attr_reader :errors
|
|
6
|
+
|
|
7
|
+
def initialize(context, errors)
|
|
8
|
+
@context = context
|
|
9
|
+
@errors = errors
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# TODO: Don't use to_json since it isn't supported in Ruby 1.8.7
|
|
13
|
+
def serialize
|
|
14
|
+
payload = as_json.to_json
|
|
15
|
+
context.logger.info(payload)
|
|
16
|
+
payload
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def as_json
|
|
20
|
+
serialized_errors = errors.map do |error_record|
|
|
21
|
+
serialize_error_record(error_record)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
:notifier => "scout_apm_ruby",
|
|
26
|
+
:environment => context.environment.env,
|
|
27
|
+
:root => context.environment.root,
|
|
28
|
+
:problems => serialized_errors,
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def serialize_error_record(error_record)
|
|
33
|
+
{
|
|
34
|
+
:exception_class => error_record.exception_class,
|
|
35
|
+
:message => error_record.message,
|
|
36
|
+
:request_uri => error_record.request_uri,
|
|
37
|
+
:request_params => error_record.request_params,
|
|
38
|
+
:request_session => error_record.request_session,
|
|
39
|
+
:environment => error_record.environment,
|
|
40
|
+
:trace => error_record.trace,
|
|
41
|
+
:request_components => error_record.request_components,
|
|
42
|
+
:context => error_record.context,
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class PeriodicWork
|
|
4
|
+
attr_reader :context
|
|
5
|
+
|
|
6
|
+
def initialize(context)
|
|
7
|
+
@context = context
|
|
8
|
+
@notifier = ScoutApm::ErrorService::Notifier.new(context)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Expected to be called many times over the life of the agent
|
|
12
|
+
def run
|
|
13
|
+
@notifier.ship
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class Railtie < Rails::Railtie
|
|
4
|
+
initializer "scoutapm_error_service.middleware" do |app|
|
|
5
|
+
next if ScoutApm::Agent.instance.config.value("error_service")
|
|
6
|
+
|
|
7
|
+
app.config.middleware.insert_after ActionDispatch::DebugExceptions, ScoutApm::ErrorService::Rack
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class Sidekiq
|
|
4
|
+
def initialize
|
|
5
|
+
@context = ScoutApm::Agent.instance.context
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def install
|
|
9
|
+
return false unless defined?(::Sidekiq)
|
|
10
|
+
|
|
11
|
+
if ::Sidekiq::VERSION < "3"
|
|
12
|
+
install_sidekiq_with_middleware
|
|
13
|
+
else
|
|
14
|
+
install_sidekiq_with_error_handler
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def install_sidekiq_with_middleware
|
|
21
|
+
# old behavior
|
|
22
|
+
::Sidekiq.configure_server do |config|
|
|
23
|
+
config.server_middleware do |chain|
|
|
24
|
+
chain.add ScoutApm::ErrorService::Sidekiq::SidekiqExceptionMiddleware
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def install_sidekiq_with_error_handler
|
|
30
|
+
::Sidekiq.configure_server do |config|
|
|
31
|
+
config.error_handlers << proc { |exception, job_info|
|
|
32
|
+
context = ScoutApm::Agent.instance.context
|
|
33
|
+
|
|
34
|
+
# Bail out early, and reraise if the error is not interesting.
|
|
35
|
+
if context.ignored_exceptions.ignored?(exception)
|
|
36
|
+
raise
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
job_class =
|
|
40
|
+
begin
|
|
41
|
+
job_class = job_info[:job]["class"]
|
|
42
|
+
job_class = job_info[:job]["args"][0]["job_class"] if job_class == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
|
43
|
+
job_class
|
|
44
|
+
rescue
|
|
45
|
+
"UnknownJob"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Capture the error for further processing and shipping
|
|
49
|
+
context.error_buffer.capture(exception, job_info.merge(:custom_controller => job_class))
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class SidekiqExceptionMiddleware
|
|
55
|
+
def call(worker, msg, queue)
|
|
56
|
+
yield
|
|
57
|
+
rescue => exception
|
|
58
|
+
context = ScoutApm::Agent.instance.context
|
|
59
|
+
|
|
60
|
+
# Bail out early, and reraise if the error is not interesting.
|
|
61
|
+
if context.ignored_exceptions.ignored?(exception)
|
|
62
|
+
raise
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Capture the error for further processing and shipping
|
|
66
|
+
context.error_buffer.capture(
|
|
67
|
+
exception,
|
|
68
|
+
{
|
|
69
|
+
:custom_params => msg,
|
|
70
|
+
:custom_controller => msg["class"]
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Finally, reraise
|
|
75
|
+
raise exception
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/scout_apm/middleware.rb
CHANGED
|
@@ -26,7 +26,7 @@ module ScoutApm
|
|
|
26
26
|
ScoutApm::Agent.instance.start
|
|
27
27
|
@started = ScoutApm::Agent.instance.context.started? && ScoutApm::Agent.instance.background_worker_running?
|
|
28
28
|
rescue => e
|
|
29
|
-
ScoutApm::Agent.instance.context.logger("Failed to start via Middleware: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
|
|
29
|
+
ScoutApm::Agent.instance.context.logger.info("Failed to start via Middleware: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
data/lib/scout_apm/reporter.rb
CHANGED
|
@@ -22,6 +22,7 @@ module ScoutApm
|
|
|
22
22
|
context.logger
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# The fully serialized string payload to be sent
|
|
25
26
|
def report(payload, headers = {})
|
|
26
27
|
hosts = determine_hosts
|
|
27
28
|
|
|
@@ -35,6 +36,7 @@ module ScoutApm
|
|
|
35
36
|
logger.debug("Original Size: #{original_payload_size} Compressed Size: #{compress_payload_size}")
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
logger.info("Posting payload to #{hosts.inspect}")
|
|
38
40
|
post_payload(hosts, payload, headers)
|
|
39
41
|
end
|
|
40
42
|
|
|
@@ -51,6 +53,8 @@ module ScoutApm
|
|
|
51
53
|
URI.parse("#{host}/apps/deploy.scout?key=#{key}&name=#{encoded_app_name}")
|
|
52
54
|
when :instant_trace
|
|
53
55
|
URI.parse("#{host}/apps/instant_trace.scout?key=#{key}&name=#{encoded_app_name}&instant_key=#{instant_key}")
|
|
56
|
+
when :errors
|
|
57
|
+
URI.parse("#{host}/apps/error.scout?key=#{key}&name=#{encoded_app_name}")
|
|
54
58
|
end.tap { |u| logger.debug("Posting to #{u}") }
|
|
55
59
|
end
|
|
56
60
|
|
|
@@ -89,7 +93,7 @@ module ScoutApm
|
|
|
89
93
|
logger.debug "got response: #{response.inspect}"
|
|
90
94
|
case response
|
|
91
95
|
when Net::HTTPSuccess, Net::HTTPNotModified
|
|
92
|
-
logger.debug "
|
|
96
|
+
logger.debug "#{type} OK"
|
|
93
97
|
when Net::HTTPBadRequest
|
|
94
98
|
logger.warn "/#{type} FAILED: The Account Key [#{config.value('key')}] is invalid."
|
|
95
99
|
when Net::HTTPUnprocessableEntity
|
|
@@ -141,6 +145,8 @@ module ScoutApm
|
|
|
141
145
|
def determine_hosts
|
|
142
146
|
if [:deploy_hook, :instant_trace].include?(type)
|
|
143
147
|
config.value('direct_host')
|
|
148
|
+
elsif [:errors].include?(type)
|
|
149
|
+
config.value('errors_host')
|
|
144
150
|
else
|
|
145
151
|
config.value('host')
|
|
146
152
|
end
|
|
@@ -45,18 +45,36 @@ module ScoutApm
|
|
|
45
45
|
"{#{str_parts.join(",")}}"
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
# Ruby 1.8.7 seems to be fundamentally different in how gsub or regexes
|
|
49
|
+
# work. This is a hack and will be removed as soon as we can drop
|
|
50
|
+
# support
|
|
51
|
+
if RUBY_VERSION == "1.8.7"
|
|
52
|
+
ESCAPE_MAPPINGS = {
|
|
53
|
+
"\b" => '\\b',
|
|
54
|
+
"\t" => '\\t',
|
|
55
|
+
"\n" => '\\n',
|
|
56
|
+
"\f" => '\\f',
|
|
57
|
+
"\r" => '\\r',
|
|
58
|
+
'"' => '\\"',
|
|
59
|
+
'\\' => '\\\\',
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
ESCAPE_MAPPINGS = {
|
|
63
|
+
# Stackoverflow answer on gsub matches and backslashes - https://stackoverflow.com/a/4149087/2705125
|
|
64
|
+
'\\' => '\\\\\\\\',
|
|
65
|
+
"\b" => '\\b',
|
|
66
|
+
"\t" => '\\t',
|
|
67
|
+
"\n" => '\\n',
|
|
68
|
+
"\f" => '\\f',
|
|
69
|
+
"\r" => '\\r',
|
|
70
|
+
'"' => '\\"',
|
|
71
|
+
}
|
|
72
|
+
end
|
|
57
73
|
|
|
58
74
|
def escape(string)
|
|
59
|
-
ESCAPE_MAPPINGS.inject(string.to_s) {|s, (bad, good)|
|
|
75
|
+
ESCAPE_MAPPINGS.inject(string.to_s) {|s, (bad, good)|
|
|
76
|
+
s.gsub(bad, good)
|
|
77
|
+
}
|
|
60
78
|
end
|
|
61
79
|
|
|
62
80
|
def format_by_type(formatee)
|
data/lib/scout_apm/version.rb
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
require "scout_apm/agent_context"
|
|
4
|
+
|
|
5
|
+
class AgentContextTest < Minitest::Test
|
|
6
|
+
def test_has_error_service_ignored_exceptions
|
|
7
|
+
context = ScoutApm::AgentContext.new
|
|
8
|
+
assert ScoutApm::ErrorService::IgnoredExceptions, context.ignored_exceptions.class
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_has_error_buffer
|
|
12
|
+
context = ScoutApm::AgentContext.new
|
|
13
|
+
assert ScoutApm::ErrorService::ErrorBuffer, context.error_buffer.class
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class ErrorBufferTest < Minitest::Test
|
|
4
|
+
class FakeError < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def test_captures_and_stores_exceptions_and_env
|
|
8
|
+
eb = ScoutApm::ErrorService::ErrorBuffer.new(context)
|
|
9
|
+
eb.capture(ex, env)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
#### Helpers
|
|
13
|
+
|
|
14
|
+
def context
|
|
15
|
+
ScoutApm::AgentContext.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def env
|
|
19
|
+
{}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ex(msg="Whoops")
|
|
23
|
+
FakeError.new(msg)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class IgnoredExceptionsTest < Minitest::Test
|
|
4
|
+
class FakeError < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class SubclassFakeError < FakeError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_ignores_with_string_match
|
|
11
|
+
ig = ScoutApm::ErrorService::IgnoredExceptions.new(context, ["RuntimeError"])
|
|
12
|
+
assert ig.ignored?(RuntimeError.new("something went wrong"))
|
|
13
|
+
assert !ig.ignored?(FakeError.new("something went wrong"))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_ignores_with_block
|
|
17
|
+
ig = ScoutApm::ErrorService::IgnoredExceptions.new(context, [])
|
|
18
|
+
ig.add_callback { |e| e.message == "ignore me" }
|
|
19
|
+
|
|
20
|
+
should_ignore = RuntimeError.new("ignore me")
|
|
21
|
+
should_not_ignore = RuntimeError.new("super legit")
|
|
22
|
+
|
|
23
|
+
assert ig.ignored?(should_ignore)
|
|
24
|
+
assert !ig.ignored?(should_not_ignore)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_ignores_subclasses
|
|
28
|
+
ig = ScoutApm::ErrorService::IgnoredExceptions.new(context, ["IgnoredExceptionsTest::FakeError"])
|
|
29
|
+
assert ig.ignored?(SubclassFakeError.new("Subclass"))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Check that a bad exception in the list doesn't stop the whole thing from working
|
|
33
|
+
def test_does_not_consider_unknown_errors
|
|
34
|
+
ig = ScoutApm::ErrorService::IgnoredExceptions.new(context, ["ThisDoesNotExist", "IgnoredExceptionsTest::FakeError"])
|
|
35
|
+
assert ig.ignored?(FakeError.new("ignore this one"))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_add_module
|
|
39
|
+
ig = ScoutApm::ErrorService::IgnoredExceptions.new(context, [])
|
|
40
|
+
ig.add(IgnoredExceptionsTest::FakeError)
|
|
41
|
+
assert ig.ignored?(FakeError.new("ignore this one"))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#### Helpers
|
|
45
|
+
|
|
46
|
+
def context
|
|
47
|
+
ScoutApm::AgentContext.new
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -108,4 +108,40 @@ class PayloadSerializerTest < Minitest::Test
|
|
|
108
108
|
json = { "foo" => "\bbar\nbaz\r" }
|
|
109
109
|
assert_equal json, JSON.parse(ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(json))
|
|
110
110
|
end
|
|
111
|
+
|
|
112
|
+
def test_escapes_escaped_quotes
|
|
113
|
+
# Some escapes haven't ever worked on 1.8.7, and is not the issue I'm
|
|
114
|
+
# fixing now. Remove this when we drop support for ancient ruby
|
|
115
|
+
skip if RUBY_VERSION == "1.8.7"
|
|
116
|
+
|
|
117
|
+
json = {"foo" => %q|`additional_details` = '{\"amount\":1}'|}
|
|
118
|
+
result = ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(json)
|
|
119
|
+
assert_equal json, JSON.parse(result)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def test_escapes_various_special_characters
|
|
123
|
+
# Some escapes haven't ever worked on 1.8.7, and is not the issue I'm
|
|
124
|
+
# fixing now. Remove this when we drop support for ancient ruby
|
|
125
|
+
skip if RUBY_VERSION == "1.8.7"
|
|
126
|
+
|
|
127
|
+
json = {"foo" => [
|
|
128
|
+
%Q|\fbar|,
|
|
129
|
+
%Q|\rbar|,
|
|
130
|
+
%Q|\nbar|,
|
|
131
|
+
%Q|\tbar|,
|
|
132
|
+
%Q|"bar|,
|
|
133
|
+
%Q|'bar|,
|
|
134
|
+
%Q|{bar|,
|
|
135
|
+
%Q|}bar|,
|
|
136
|
+
%Q|\\bar|,
|
|
137
|
+
if RUBY_VERSION == '1.8.7'
|
|
138
|
+
""
|
|
139
|
+
else
|
|
140
|
+
%Q|\\\nbar|
|
|
141
|
+
end,
|
|
142
|
+
]}
|
|
143
|
+
|
|
144
|
+
result = ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(json)
|
|
145
|
+
assert_equal json, JSON.parse(result)
|
|
146
|
+
end
|
|
111
147
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: scout_apm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.6.
|
|
4
|
+
version: 2.6.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Derek Haynes
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2020-
|
|
12
|
+
date: 2020-10-20 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: minitest
|
|
@@ -267,6 +267,17 @@ files:
|
|
|
267
267
|
- lib/scout_apm/debug.rb
|
|
268
268
|
- lib/scout_apm/detailed_trace.rb
|
|
269
269
|
- lib/scout_apm/environment.rb
|
|
270
|
+
- lib/scout_apm/error.rb
|
|
271
|
+
- lib/scout_apm/error_service.rb
|
|
272
|
+
- lib/scout_apm/error_service/error_buffer.rb
|
|
273
|
+
- lib/scout_apm/error_service/error_record.rb
|
|
274
|
+
- lib/scout_apm/error_service/ignored_exceptions.rb
|
|
275
|
+
- lib/scout_apm/error_service/middleware.rb
|
|
276
|
+
- lib/scout_apm/error_service/notifier.rb
|
|
277
|
+
- lib/scout_apm/error_service/payload.rb
|
|
278
|
+
- lib/scout_apm/error_service/periodic_work.rb
|
|
279
|
+
- lib/scout_apm/error_service/railtie.rb
|
|
280
|
+
- lib/scout_apm/error_service/sidekiq.rb
|
|
270
281
|
- lib/scout_apm/extensions/config.rb
|
|
271
282
|
- lib/scout_apm/extensions/transaction_callback_payload.rb
|
|
272
283
|
- lib/scout_apm/fake_store.rb
|
|
@@ -390,6 +401,7 @@ files:
|
|
|
390
401
|
- test/data/config_test_1.yml
|
|
391
402
|
- test/test_helper.rb
|
|
392
403
|
- test/tmp/README.md
|
|
404
|
+
- test/unit/agent_context_test.rb
|
|
393
405
|
- test/unit/agent_test.rb
|
|
394
406
|
- test/unit/auto_instrument/assignments-instrumented.rb
|
|
395
407
|
- test/unit/auto_instrument/assignments.rb
|
|
@@ -405,6 +417,8 @@ files:
|
|
|
405
417
|
- test/unit/db_query_metric_set_test.rb
|
|
406
418
|
- test/unit/db_query_metric_stats_test.rb
|
|
407
419
|
- test/unit/environment_test.rb
|
|
420
|
+
- test/unit/error_service/error_buffer_test.rb
|
|
421
|
+
- test/unit/error_service/ignored_exceptions_test.rb
|
|
408
422
|
- test/unit/extensions/periodic_callbacks_test.rb
|
|
409
423
|
- test/unit/extensions/transaction_callbacks_test.rb
|
|
410
424
|
- test/unit/fake_store_test.rb
|
|
@@ -460,61 +474,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
460
474
|
- !ruby/object:Gem::Version
|
|
461
475
|
version: '0'
|
|
462
476
|
requirements: []
|
|
463
|
-
rubygems_version: 3.0.
|
|
477
|
+
rubygems_version: 3.0.8
|
|
464
478
|
signing_key:
|
|
465
479
|
specification_version: 4
|
|
466
480
|
summary: Ruby application performance monitoring
|
|
467
|
-
test_files:
|
|
468
|
-
- test/data/config_test_1.yml
|
|
469
|
-
- test/test_helper.rb
|
|
470
|
-
- test/tmp/README.md
|
|
471
|
-
- test/unit/agent_test.rb
|
|
472
|
-
- test/unit/auto_instrument/assignments-instrumented.rb
|
|
473
|
-
- test/unit/auto_instrument/assignments.rb
|
|
474
|
-
- test/unit/auto_instrument/controller-ast.txt
|
|
475
|
-
- test/unit/auto_instrument/controller-instrumented.rb
|
|
476
|
-
- test/unit/auto_instrument/controller.rb
|
|
477
|
-
- test/unit/auto_instrument/rescue_from-instrumented.rb
|
|
478
|
-
- test/unit/auto_instrument/rescue_from.rb
|
|
479
|
-
- test/unit/auto_instrument_test.rb
|
|
480
|
-
- test/unit/background_job_integrations/sidekiq_test.rb
|
|
481
|
-
- test/unit/config_test.rb
|
|
482
|
-
- test/unit/context_test.rb
|
|
483
|
-
- test/unit/db_query_metric_set_test.rb
|
|
484
|
-
- test/unit/db_query_metric_stats_test.rb
|
|
485
|
-
- test/unit/environment_test.rb
|
|
486
|
-
- test/unit/extensions/periodic_callbacks_test.rb
|
|
487
|
-
- test/unit/extensions/transaction_callbacks_test.rb
|
|
488
|
-
- test/unit/fake_store_test.rb
|
|
489
|
-
- test/unit/git_revision_test.rb
|
|
490
|
-
- test/unit/histogram_test.rb
|
|
491
|
-
- test/unit/ignored_uris_test.rb
|
|
492
|
-
- test/unit/instruments/active_record_test.rb
|
|
493
|
-
- test/unit/instruments/net_http_test.rb
|
|
494
|
-
- test/unit/instruments/percentile_sampler_test.rb
|
|
495
|
-
- test/unit/layaway_test.rb
|
|
496
|
-
- test/unit/layer_children_set_test.rb
|
|
497
|
-
- test/unit/layer_converters/depth_first_walker_test.rb
|
|
498
|
-
- test/unit/layer_converters/metric_converter_test.rb
|
|
499
|
-
- test/unit/layer_converters/stubs.rb
|
|
500
|
-
- test/unit/limited_layer_test.rb
|
|
501
|
-
- test/unit/logger_test.rb
|
|
502
|
-
- test/unit/metric_set_test.rb
|
|
503
|
-
- test/unit/remote/test_message.rb
|
|
504
|
-
- test/unit/remote/test_router.rb
|
|
505
|
-
- test/unit/remote/test_server.rb
|
|
506
|
-
- test/unit/request_histograms_test.rb
|
|
507
|
-
- test/unit/scored_item_set_test.rb
|
|
508
|
-
- test/unit/serializers/payload_serializer_test.rb
|
|
509
|
-
- test/unit/slow_job_policy_test.rb
|
|
510
|
-
- test/unit/slow_request_policy_test.rb
|
|
511
|
-
- test/unit/sql_sanitizer_test.rb
|
|
512
|
-
- test/unit/store_test.rb
|
|
513
|
-
- test/unit/tracer_test.rb
|
|
514
|
-
- test/unit/tracked_request_test.rb
|
|
515
|
-
- test/unit/transaction_test.rb
|
|
516
|
-
- test/unit/transaction_time_consumed_test.rb
|
|
517
|
-
- test/unit/utils/active_record_metric_name_test.rb
|
|
518
|
-
- test/unit/utils/backtrace_parser_test.rb
|
|
519
|
-
- test/unit/utils/numbers_test.rb
|
|
520
|
-
- test/unit/utils/scm.rb
|
|
481
|
+
test_files: []
|