appoptics_apm 4.0.2 → 4.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/Dockerfile_test +7 -6
- data/Rakefile +2 -0
- data/docker-compose.yml +22 -0
- data/gemfiles/noop.gemfile +17 -0
- data/lib/appoptics_apm.rb +2 -2
- data/lib/appoptics_apm/api.rb +1 -2
- data/lib/appoptics_apm/api/layerinit.rb +3 -1
- data/lib/appoptics_apm/api/logging.rb +25 -25
- data/lib/appoptics_apm/api/memcache.rb +3 -1
- data/lib/appoptics_apm/api/profiling.rb +39 -33
- data/lib/appoptics_apm/api/tracing.rb +23 -13
- data/lib/appoptics_apm/api/util.rb +1 -1
- data/lib/appoptics_apm/base.rb +1 -0
- data/lib/appoptics_apm/frameworks/sinatra/templates.rb +1 -1
- data/lib/appoptics_apm/inst/rack.rb +135 -111
- data/lib/appoptics_apm/instrumentation.rb +1 -1
- data/lib/appoptics_apm/version.rb +1 -1
- data/test/instrumentation/httpclient_test.rb +4 -20
- data/test/instrumentation/mongo_v1_test.rb +1 -1
- data/test/instrumentation/rack_test.rb +1 -1
- data/test/instrumentation/sequel_mysql2_test.rb +1 -1
- data/test/instrumentation/sequel_mysql_test.rb +1 -1
- data/test/instrumentation/sequel_pg_test.rb +1 -1
- data/test/instrumentation/twitter-cassandra_test.rb +1 -1
- data/test/minitest_helper.rb +1 -0
- data/test/mocked/curb_mocked_test.rb +14 -1
- data/test/mocked/excon_mocked_test.rb +12 -4
- data/test/mocked/faraday_mocked_test.rb +5 -1
- data/test/mocked/http_mocked_test.rb +6 -0
- data/test/mocked/httpclient_mocked_test.rb +14 -1
- data/test/mocked/rest_client_mocked_test.rb +7 -1
- data/test/mocked/typhoeus_mocked_test.rb +15 -0
- data/test/noop/noop_test.rb +145 -0
- metadata +5 -4
- data/test/support/noop_test.rb +0 -88
data/lib/appoptics_apm/base.rb
CHANGED
@@ -61,6 +61,7 @@ module AppOpticsAPMBase
|
|
61
61
|
|
62
62
|
# Occurs only on Jruby. Indicates that Joboe (the java instrumentation)
|
63
63
|
# has already started tracing before it hit the JRuby instrumentation.
|
64
|
+
# It is used in Rack#call if there is a context when entering rack
|
64
65
|
thread_local :has_incoming_context
|
65
66
|
|
66
67
|
# Indicates the existence of a valid X-Trace request header
|
@@ -4,142 +4,166 @@
|
|
4
4
|
require 'uri'
|
5
5
|
require 'cgi'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
7
|
+
if AppOpticsAPM.loaded
|
8
|
+
module AppOpticsAPM
|
9
|
+
##
|
10
|
+
# AppOpticsAPM::Rack
|
11
|
+
#
|
12
|
+
# The AppOpticsAPM::Rack middleware used to sample a subset of incoming
|
13
|
+
# requests for instrumentation and reporting. Tracing context can
|
14
|
+
# be received here (via the X-Trace HTTP header) or initiated here
|
15
|
+
# based on configured tracing mode.
|
16
|
+
#
|
17
|
+
# After the rack layer passes on to the following layers (Rails, Sinatra,
|
18
|
+
# Padrino, Grape), then the instrumentation downstream will
|
19
|
+
# automatically detect whether this is a sampled request or not
|
20
|
+
# and act accordingly. (to instrument or not)
|
21
|
+
#
|
22
|
+
class Rack
|
23
|
+
attr_reader :app
|
24
|
+
|
25
|
+
def initialize(app)
|
26
|
+
@app = app
|
27
|
+
end
|
28
|
+
|
29
|
+
def collect(req, env)
|
30
|
+
report_kvs = {}
|
27
31
|
|
28
|
-
|
29
|
-
|
32
|
+
begin
|
33
|
+
report_kvs[:'HTTP-Host'] = req.host
|
34
|
+
report_kvs[:Port] = req.port
|
35
|
+
report_kvs[:Proto] = req.scheme
|
36
|
+
report_kvs[:Method] = req.request_method
|
37
|
+
report_kvs[:AJAX] = true if req.xhr?
|
38
|
+
report_kvs[:ClientIP] = req.ip
|
30
39
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
report_kvs[:Proto] = req.scheme
|
35
|
-
report_kvs[:Method] = req.request_method
|
36
|
-
report_kvs[:AJAX] = true if req.xhr?
|
37
|
-
report_kvs[:ClientIP] = req.ip
|
40
|
+
if AppOpticsAPM::Config[:rack][:log_args]
|
41
|
+
report_kvs[:'Query-String'] = ::CGI.unescape(req.query_string) unless req.query_string.empty?
|
42
|
+
end
|
38
43
|
|
39
|
-
|
40
|
-
report_kvs[:'
|
44
|
+
# Report any request queue'ing headers. Report as 'Request-Start' or the summed Queue-Time
|
45
|
+
report_kvs[:'Request-Start'] = env['HTTP_X_REQUEST_START'] if env.key?('HTTP_X_REQUEST_START')
|
46
|
+
report_kvs[:'Request-Start'] = env['HTTP_X_QUEUE_START'] if env.key?('HTTP_X_QUEUE_START')
|
47
|
+
report_kvs[:'Queue-Time'] = env['HTTP_X_QUEUE_TIME'] if env.key?('HTTP_X_QUEUE_TIME')
|
48
|
+
|
49
|
+
report_kvs[:'Forwarded-For'] = env['HTTP_X_FORWARDED_FOR'] if env.key?('HTTP_X_FORWARDED_FOR')
|
50
|
+
report_kvs[:'Forwarded-Host'] = env['HTTP_X_FORWARDED_HOST'] if env.key?('HTTP_X_FORWARDED_HOST')
|
51
|
+
report_kvs[:'Forwarded-Proto'] = env['HTTP_X_FORWARDED_PROTO'] if env.key?('HTTP_X_FORWARDED_PROTO')
|
52
|
+
report_kvs[:'Forwarded-Port'] = env['HTTP_X_FORWARDED_PORT'] if env.key?('HTTP_X_FORWARDED_PORT')
|
53
|
+
|
54
|
+
report_kvs[:'Ruby.AppOpticsAPM.Version'] = ::AppOpticsAPM::Version::STRING
|
55
|
+
report_kvs[:ProcessID] = Process.pid
|
56
|
+
report_kvs[:ThreadID] = Thread.current.to_s[/0x\w*/]
|
57
|
+
rescue StandardError => e
|
58
|
+
# Discard any potential exceptions. Debug log and report whatever we can.
|
59
|
+
AppOpticsAPM.logger.debug "[appoptics_apm/debug] Rack KV collection error: #{e.inspect}"
|
41
60
|
end
|
42
|
-
|
43
|
-
# Report any request queue'ing headers. Report as 'Request-Start' or the summed Queue-Time
|
44
|
-
report_kvs[:'Request-Start'] = env['HTTP_X_REQUEST_START'] if env.key?('HTTP_X_REQUEST_START')
|
45
|
-
report_kvs[:'Request-Start'] = env['HTTP_X_QUEUE_START'] if env.key?('HTTP_X_QUEUE_START')
|
46
|
-
report_kvs[:'Queue-Time'] = env['HTTP_X_QUEUE_TIME'] if env.key?('HTTP_X_QUEUE_TIME')
|
47
|
-
|
48
|
-
report_kvs[:'Forwarded-For'] = env['HTTP_X_FORWARDED_FOR'] if env.key?('HTTP_X_FORWARDED_FOR')
|
49
|
-
report_kvs[:'Forwarded-Host'] = env['HTTP_X_FORWARDED_HOST'] if env.key?('HTTP_X_FORWARDED_HOST')
|
50
|
-
report_kvs[:'Forwarded-Proto'] = env['HTTP_X_FORWARDED_PROTO'] if env.key?('HTTP_X_FORWARDED_PROTO')
|
51
|
-
report_kvs[:'Forwarded-Port'] = env['HTTP_X_FORWARDED_PORT'] if env.key?('HTTP_X_FORWARDED_PORT')
|
52
|
-
|
53
|
-
report_kvs[:'Ruby.AppOpticsAPM.Version'] = ::AppOpticsAPM::Version::STRING
|
54
|
-
report_kvs[:ProcessID] = Process.pid
|
55
|
-
report_kvs[:ThreadID] = Thread.current.to_s[/0x\w*/]
|
56
|
-
rescue StandardError => e
|
57
|
-
# Discard any potential exceptions. Debug log and report whatever we can.
|
58
|
-
AppOpticsAPM.logger.debug "[appoptics_apm/debug] Rack KV collection error: #{e.inspect}"
|
61
|
+
report_kvs
|
59
62
|
end
|
60
|
-
report_kvs
|
61
|
-
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
64
|
+
def call(env)
|
65
|
+
start = Time.now
|
66
|
+
status = 500
|
67
|
+
req = ::Rack::Request.new(env)
|
68
|
+
req_url = req.url # saving it here because rails3.2 overrides it when there is a 500 error
|
69
|
+
|
70
|
+
# In the case of nested Ruby apps such as Grape inside of Rails
|
71
|
+
# or Grape inside of Grape, each app has it's own instance
|
72
|
+
# of rack middleware. We avoid tracing rack more than once and
|
73
|
+
# instead start instrumenting from the first rack pass.
|
74
|
+
if AppOpticsAPM.tracing? && AppOpticsAPM.layer == :rack
|
75
|
+
AppOpticsAPM.logger.debug "[appoptics_apm/rack] Rack skipped!"
|
76
|
+
return @app.call(env)
|
77
|
+
end
|
77
78
|
|
78
|
-
|
79
|
-
|
79
|
+
begin
|
80
|
+
report_kvs = {}
|
80
81
|
|
81
|
-
|
82
|
+
report_kvs[:URL] = AppOpticsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
|
82
83
|
|
83
|
-
|
84
|
-
|
85
|
-
|
84
|
+
# Check for and validate X-Trace request header to pick up tracing context
|
85
|
+
xtrace = env.is_a?(Hash) ? env['HTTP_X_TRACE'] : nil
|
86
|
+
xtrace_header = AppOpticsAPM::XTrace.valid?(xtrace) ? xtrace : nil
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
88
|
+
# Under JRuby, JAppOpticsAPM may have already started a trace. Make note of this
|
89
|
+
# if so and don't clear context on log_end (see appoptics_apm/api/logging.rb)
|
90
|
+
AppOpticsAPM.has_incoming_context = AppOpticsAPM.tracing?
|
91
|
+
AppOpticsAPM.has_xtrace_header = xtrace_header
|
92
|
+
AppOpticsAPM.is_continued_trace = AppOpticsAPM.has_incoming_context || AppOpticsAPM.has_xtrace_header
|
92
93
|
|
93
|
-
|
94
|
+
xtrace = AppOpticsAPM::API.log_start(:rack, xtrace_header, report_kvs)
|
94
95
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
96
|
+
# We only trace a subset of requests based off of sample rate so if
|
97
|
+
# AppOpticsAPM::API.log_start really did start a trace, we act accordingly here.
|
98
|
+
if AppOpticsAPM.tracing?
|
99
|
+
report_kvs = collect(req, env)
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
101
|
+
# We log an info event with the HTTP KVs found in AppOpticsAPM::Rack.collect
|
102
|
+
# This is done here so in the case of stacks that try/catch/abort
|
103
|
+
# (looking at you Grape) we're sure the KVs get reported now as
|
104
|
+
# this code may not be returned to later.
|
105
|
+
AppOpticsAPM::API.log_info(:rack, report_kvs)
|
105
106
|
|
106
|
-
|
107
|
+
status, headers, response = @app.call(env)
|
107
108
|
|
108
|
-
|
109
|
+
xtrace = AppOpticsAPM::API.log_end(:rack, :Status => status)
|
109
110
|
|
110
|
-
|
111
|
-
|
112
|
-
|
111
|
+
else
|
112
|
+
status, headers, response = @app.call(env)
|
113
|
+
end
|
113
114
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
115
|
+
[status, headers, response]
|
116
|
+
rescue Exception => e
|
117
|
+
# it is ok to rescue Exception here because we are reraising it (we just need a chance to log_end)
|
118
|
+
if AppOpticsAPM.tracing?
|
119
|
+
AppOpticsAPM::API.log_exception(:rack, e)
|
120
|
+
xtrace = AppOpticsAPM::API.log_end(:rack, :Status => 500, 'TransactionName' => transaction_name(env))
|
121
|
+
end
|
122
|
+
raise
|
123
|
+
ensure
|
124
|
+
if headers && AppOpticsAPM::XTrace.valid?(xtrace)
|
125
|
+
unless defined?(JRUBY_VERSION) && AppOpticsAPM.is_continued_trace?
|
126
|
+
headers['X-Trace'] = xtrace if headers.is_a?(Hash)
|
127
|
+
end
|
128
|
+
end
|
120
129
|
end
|
121
|
-
raise
|
122
130
|
ensure
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
131
|
+
unless ::AppOpticsAPM::Util.static_asset?(env['PATH_INFO'])
|
132
|
+
status = status.to_i
|
133
|
+
error = status.between?(500,599) ? 1 : 0
|
134
|
+
duration =(1000 * 1000 * (Time.now - start)).round(0)
|
135
|
+
AppOpticsAPM::Span.createHttpSpan(transaction_name(env), req_url, duration, status, req.request_method, error)
|
127
136
|
end
|
128
137
|
end
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
138
|
+
|
139
|
+
def self.noop?
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def transaction_name(env)
|
146
|
+
if env['appoptics_apm.controller'] || env['appoptics_apm.action']
|
147
|
+
[env['appoptics_apm.controller'], env['appoptics_apm.action']].join('.')
|
148
|
+
end
|
135
149
|
end
|
136
150
|
end
|
151
|
+
end
|
152
|
+
else
|
153
|
+
module AppOpticsAPM
|
154
|
+
class Rack
|
155
|
+
attr_reader :app
|
137
156
|
|
138
|
-
|
157
|
+
def initialize(app)
|
158
|
+
@app = app
|
159
|
+
end
|
160
|
+
|
161
|
+
def call(env)
|
162
|
+
@app.call(env)
|
163
|
+
end
|
139
164
|
|
140
|
-
|
141
|
-
|
142
|
-
[env['appoptics_apm.controller'], env['appoptics_apm.action']].join('.')
|
165
|
+
def self.noop?
|
166
|
+
true
|
143
167
|
end
|
144
168
|
end
|
145
169
|
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
module AppOpticsAPM
|
5
5
|
##
|
6
6
|
# The Inst module holds all of the instrumentation extensions for various
|
7
|
-
# libraries
|
7
|
+
# libraries such as Redis, Dalli and Resque.
|
8
8
|
module Inst
|
9
9
|
def self.load_instrumentation
|
10
10
|
# Load the general instrumentation
|
@@ -18,6 +18,10 @@ unless defined?(JRUBY_VERSION)
|
|
18
18
|
AppOpticsAPM::Config[:tracing_mode] = :always
|
19
19
|
end
|
20
20
|
|
21
|
+
def teardown
|
22
|
+
clear_all_traces
|
23
|
+
end
|
24
|
+
|
21
25
|
def test_reports_version_init
|
22
26
|
init_kvs = ::AppOpticsAPM::Util.build_init_report
|
23
27
|
assert init_kvs.key?('Ruby.httpclient.Version')
|
@@ -25,8 +29,6 @@ unless defined?(JRUBY_VERSION)
|
|
25
29
|
end
|
26
30
|
|
27
31
|
def test_get_request
|
28
|
-
clear_all_traces
|
29
|
-
|
30
32
|
response = nil
|
31
33
|
|
32
34
|
AppOpticsAPM::API.start_trace('httpclient_tests') do
|
@@ -55,8 +57,6 @@ unless defined?(JRUBY_VERSION)
|
|
55
57
|
end
|
56
58
|
|
57
59
|
def test_get_with_header_hash
|
58
|
-
clear_all_traces
|
59
|
-
|
60
60
|
response = nil
|
61
61
|
|
62
62
|
AppOpticsAPM::API.start_trace('httpclient_tests') do
|
@@ -85,8 +85,6 @@ unless defined?(JRUBY_VERSION)
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def test_get_with_header_array
|
88
|
-
clear_all_traces
|
89
|
-
|
90
88
|
response = nil
|
91
89
|
|
92
90
|
AppOpticsAPM::API.start_trace('httpclient_tests') do
|
@@ -115,8 +113,6 @@ unless defined?(JRUBY_VERSION)
|
|
115
113
|
end
|
116
114
|
|
117
115
|
def test_post_request
|
118
|
-
clear_all_traces
|
119
|
-
|
120
116
|
response = nil
|
121
117
|
|
122
118
|
AppOpticsAPM::API.start_trace('httpclient_tests') do
|
@@ -145,8 +141,6 @@ unless defined?(JRUBY_VERSION)
|
|
145
141
|
end
|
146
142
|
|
147
143
|
def test_async_get
|
148
|
-
clear_all_traces
|
149
|
-
|
150
144
|
conn = nil
|
151
145
|
|
152
146
|
AppOpticsAPM::API.start_trace('httpclient_tests') do
|
@@ -180,8 +174,6 @@ unless defined?(JRUBY_VERSION)
|
|
180
174
|
end
|
181
175
|
|
182
176
|
def test_cross_app_tracing
|
183
|
-
clear_all_traces
|
184
|
-
|
185
177
|
response = nil
|
186
178
|
|
187
179
|
AppOpticsAPM::API.start_trace('httpclient_tests') do
|
@@ -217,8 +209,6 @@ unless defined?(JRUBY_VERSION)
|
|
217
209
|
end
|
218
210
|
|
219
211
|
def test_requests_with_errors
|
220
|
-
clear_all_traces
|
221
|
-
|
222
212
|
result = nil
|
223
213
|
begin
|
224
214
|
AppOpticsAPM::API.start_trace('httpclient_tests') do
|
@@ -249,8 +239,6 @@ unless defined?(JRUBY_VERSION)
|
|
249
239
|
end
|
250
240
|
|
251
241
|
def test_log_args_when_true
|
252
|
-
clear_all_traces
|
253
|
-
|
254
242
|
@log_args = AppOpticsAPM::Config[:httpclient][:log_args]
|
255
243
|
AppOpticsAPM::Config[:httpclient][:log_args] = true
|
256
244
|
|
@@ -276,8 +264,6 @@ unless defined?(JRUBY_VERSION)
|
|
276
264
|
end
|
277
265
|
|
278
266
|
def test_log_args_when_false
|
279
|
-
clear_all_traces
|
280
|
-
|
281
267
|
@log_args = AppOpticsAPM::Config[:httpclient][:log_args]
|
282
268
|
AppOpticsAPM::Config[:httpclient][:log_args] = false
|
283
269
|
|
@@ -303,8 +289,6 @@ unless defined?(JRUBY_VERSION)
|
|
303
289
|
end
|
304
290
|
|
305
291
|
def test_without_tracing
|
306
|
-
clear_all_traces
|
307
|
-
|
308
292
|
clnt = HTTPClient.new
|
309
293
|
clnt.get('http://127.0.0.1:8101/', :query => { :keyword => 'ruby', :lang => 'en' })
|
310
294
|
|
@@ -344,7 +344,7 @@ if Gem.loaded_specs['mongo'].version.to_s < '2.0.0'
|
|
344
344
|
validate_event_keys(traces[1], @entry_kvs)
|
345
345
|
validate_event_keys(traces[2], @exit_kvs)
|
346
346
|
|
347
|
-
result.
|
347
|
+
result.must_be_nil
|
348
348
|
traces[1]['Collection'].must_equal "testCollection"
|
349
349
|
traces[1].has_key?('Backtrace').must_equal AppOpticsAPM::Config[:mongo][:collect_backtraces]
|
350
350
|
traces[1]['QueryOp'].must_equal "find"
|
@@ -139,7 +139,7 @@ class RackTestApp < Minitest::Test
|
|
139
139
|
|
140
140
|
get "/no/controller/here"
|
141
141
|
|
142
|
-
|
142
|
+
assert_nil test_action
|
143
143
|
assert_equal "http://example.org/no/controller/here", test_url
|
144
144
|
assert_equal 404, test_status
|
145
145
|
assert_equal "GET", test_method
|
@@ -344,7 +344,7 @@ if defined?(::Sequel) && !defined?(JRUBY_VERSION)
|
|
344
344
|
traces[1]['Query'].must_equal "select_by_name"
|
345
345
|
end
|
346
346
|
|
347
|
-
traces[1]['QueryArgs'].
|
347
|
+
traces[1]['QueryArgs'].must_be_nil
|
348
348
|
traces[1]['IsPreparedStatement'].must_equal "true"
|
349
349
|
traces[1].has_key?('Backtrace').must_equal AppOpticsAPM::Config[:sequel][:collect_backtraces]
|
350
350
|
validate_event_keys(traces[2], @exit_kvs)
|