appoptics_apm 4.2.2 → 4.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -3
  3. data/Gemfile +0 -2
  4. data/README.md +119 -95
  5. data/examples/SDK/01_basic_tracing.rb +67 -0
  6. data/ext/oboe_metal/src/VERSION +1 -1
  7. data/lib/appoptics_apm/api.rb +10 -9
  8. data/lib/appoptics_apm/api/layerinit.rb +1 -1
  9. data/lib/appoptics_apm/api/logging.rb +41 -14
  10. data/lib/appoptics_apm/api/metrics.rb +34 -0
  11. data/lib/appoptics_apm/api/profiling.rb +5 -0
  12. data/lib/appoptics_apm/api/tracing.rb +33 -148
  13. data/lib/appoptics_apm/api/util.rb +3 -9
  14. data/lib/appoptics_apm/base.rb +11 -12
  15. data/lib/appoptics_apm/frameworks/padrino/templates.rb +1 -1
  16. data/lib/appoptics_apm/frameworks/rails.rb +1 -1
  17. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +1 -1
  18. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +1 -1
  19. data/lib/appoptics_apm/inst/bunny-consumer.rb +1 -2
  20. data/lib/appoptics_apm/inst/delayed_job.rb +4 -4
  21. data/lib/appoptics_apm/inst/em-http-request.rb +1 -1
  22. data/lib/appoptics_apm/inst/rack.rb +1 -0
  23. data/lib/appoptics_apm/inst/redis.rb +1 -1
  24. data/lib/appoptics_apm/inst/resque.rb +1 -1
  25. data/lib/appoptics_apm/inst/sidekiq-worker.rb +1 -3
  26. data/lib/appoptics_apm/loading.rb +3 -4
  27. data/lib/appoptics_apm/logger.rb +1 -1
  28. data/lib/appoptics_apm/sdk.rb +317 -0
  29. data/lib/appoptics_apm/support.rb +0 -17
  30. data/lib/appoptics_apm/test.rb +1 -1
  31. data/lib/appoptics_apm/util.rb +4 -3
  32. data/lib/appoptics_apm/version.rb +1 -1
  33. data/lib/appoptics_apm/xtrace.rb +3 -3
  34. data/lib/joboe_metal.rb +2 -4
  35. data/lib/oboe_metal.rb +5 -5
  36. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +5 -4
  37. data/run_tests_docker.rb +6 -0
  38. metadata +5 -9
  39. data/examples/DNT.md +0 -35
  40. data/examples/instrumenting_metal_controller.rb +0 -8
  41. data/examples/puma_on_heroku_config.rb +0 -17
  42. data/examples/tracing_async_threads.rb +0 -124
  43. data/examples/tracing_background_jobs.rb +0 -53
  44. data/examples/tracing_forked_processes.rb +0 -99
  45. data/examples/unicorn_on_heroku_config.rb +0 -28
@@ -29,7 +29,7 @@ module AppOpticsAPM
29
29
  report_kvs[:File] = __FILE__
30
30
  report_kvs[:LineNumber] = __LINE__
31
31
  rescue StandardError => e
32
- ::AppOpticsAPM.logger.debug e.message
32
+ ::AppOpticsAPM.logger.debug "[appoptics_apm/padrino] #{e.message}"
33
33
  ::AppOpticsAPM.logger.debug e.backtrace.join(', ')
34
34
  end
35
35
 
@@ -51,7 +51,7 @@ module AppOpticsAPM
51
51
  require 'appoptics_apm/frameworks/rails/inst/action_view_30'
52
52
  require 'appoptics_apm/frameworks/rails/inst/active_record'
53
53
 
54
- AppOpticsAPM.logger.info "AppOpticsAPM gem #{AppOpticsAPM::Version::STRING} successfully loaded."
54
+ AppOpticsAPM.logger.info "[appoptics_apm/rails] AppOpticsAPM gem #{AppOpticsAPM::Version::STRING} successfully loaded."
55
55
  end
56
56
 
57
57
  def self.include_helpers
@@ -39,7 +39,7 @@ module AppOpticsAPM
39
39
  end
40
40
  end
41
41
  rescue StandardError => e
42
- AppOpticsAPM.logger.debug "Exception raised capturing ActiveRecord KVs: #{e.inspect}"
42
+ AppOpticsAPM.logger.debug "[appoptics_apm/rails] Exception raised capturing ActiveRecord KVs: #{e.inspect}"
43
43
  AppOpticsAPM.logger.debug e.backtrace.join('\n')
44
44
  ensure
45
45
  return opts
@@ -45,7 +45,7 @@ module AppOpticsAPM
45
45
  end
46
46
  end
47
47
  rescue StandardError => e
48
- AppOpticsAPM.logger.debug "Exception raised capturing ActiveRecord KVs: #{e.inspect}"
48
+ AppOpticsAPM.logger.debug "[appoptics_apm/rails] Exception raised capturing ActiveRecord KVs: #{e.inspect}"
49
49
  AppOpticsAPM.logger.debug e.backtrace.join('\n')
50
50
  end
51
51
 
@@ -77,10 +77,9 @@ module AppOpticsAPM
77
77
  headers.delete('SourceTrace')
78
78
  end
79
79
 
80
- result = AppOpticsAPM::API.start_trace(:'rabbitmq-consumer', nil, report_kvs) do
80
+ AppOpticsAPM::SDK.start_trace(:'rabbitmq-consumer', nil, report_kvs) do
81
81
  call_without_appoptics(*args)
82
82
  end
83
- result[0]
84
83
  end
85
84
  end
86
85
  end
@@ -73,11 +73,11 @@ if defined?(::Delayed)
73
73
  AppOpticsAPM.logger.warn "[appoptics_apm/warning] inst/delayed_job.rb: #{e.message}"
74
74
  end
75
75
 
76
- result = AppOpticsAPM::API.start_trace(:'delayed_job-worker', nil, report_kvs) do
77
- block.call(worker, job)
78
- AppOpticsAPM::API.log_exception(nil, job.error) if job.error
76
+ AppOpticsAPM::SDK.start_trace(:'delayed_job-worker', nil, report_kvs) do
77
+ result = block.call(worker, job)
78
+ AppOpticsAPM::API.log_exception(:'delayed_job-worker', job.error) if job.error
79
+ result
79
80
  end
80
- result[0]
81
81
  end
82
82
  end
83
83
  end
@@ -63,7 +63,7 @@ module AppOpticsAPM
63
63
  if task_id == AppOpticsAPM::XTrace.task_id(xtrace)
64
64
  AppOpticsAPM::Context.fromString(xtrace)
65
65
  else
66
- AppOpticsAPM.logger.debug "Mismatched returned X-Trace ID : #{xtrace}"
66
+ AppOpticsAPM.logger.debug "[appoptics_apm/em-http] Mismatched returned X-Trace ID : #{xtrace}"
67
67
  end
68
68
  end
69
69
 
@@ -109,6 +109,7 @@ if AppOpticsAPM.loaded
109
109
  AppOpticsAPM.transaction_name = nil
110
110
  req = ::Rack::Request.new(env)
111
111
  req_url = req.url # saving it here because rails3.2 overrides it when there is a 500 error
112
+ status = 500 # initialize with 500
112
113
 
113
114
  report_kvs = {}
114
115
  report_kvs[:URL] = AppOpticsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
@@ -169,7 +169,7 @@ module AppOpticsAPM
169
169
  end # case op
170
170
  end # if KV_COLLECT_MAP[op]
171
171
  rescue StandardError => e
172
- AppOpticsAPM.logger.debug "Error collecting redis KVs: #{e.message}"
172
+ AppOpticsAPM.logger.debug "[appoptics_apm/redis] Error collecting redis KVs: #{e.message}"
173
173
  AppOpticsAPM.logger.debug e.backtrace.join('\n')
174
174
  ensure
175
175
  return kvs
@@ -117,7 +117,7 @@ module AppOpticsAPM
117
117
  AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" if AppOpticsAPM::Config[:verbose]
118
118
  end
119
119
 
120
- AppOpticsAPM::API.start_trace(:'resque-worker', nil, report_kvs) do
120
+ AppOpticsAPM::SDK.start_trace(:'resque-worker', nil, report_kvs) do
121
121
  perform_without_appoptics(job)
122
122
  end
123
123
  end
@@ -46,11 +46,9 @@ module AppOpticsAPM
46
46
  report_kvs[:SourceTrace] = args[1]['SourceTrace']
47
47
  end
48
48
 
49
- result = AppOpticsAPM::API.start_trace(:'sidekiq-worker', nil, report_kvs) do
49
+ AppOpticsAPM::SDK.start_trace(:'sidekiq-worker', nil, report_kvs) do
50
50
  yield
51
51
  end
52
-
53
- result[0]
54
52
  end
55
53
  end
56
54
  end
@@ -28,8 +28,8 @@ module AppOpticsAPM
28
28
  end
29
29
 
30
30
  ##
31
- # This module houses all of the loading functionality for the appoptics_apm gem.
32
- #
31
+ # This module houses all of the loading functionality for the appoptics_apm em.
32
+
33
33
  # Note that this does not necessarily _have_ to include initialization routines
34
34
  # (although it can).
35
35
  #
@@ -46,10 +46,9 @@ module AppOpticsAPM
46
46
  Dir.glob(pattern) do |f|
47
47
  require f
48
48
  end
49
- require 'appoptics_apm/api'
50
49
 
51
50
  begin
52
- AppOpticsAPM::API.extend_with_tracing
51
+ require 'appoptics_apm/api'
53
52
  rescue LoadError => e
54
53
  AppOpticsAPM.logger.fatal "[appoptics_apm/error] Couldn't load api: #{e.message}"
55
54
  end
@@ -38,5 +38,5 @@ module AppOpticsAPM
38
38
  end
39
39
 
40
40
  AppOpticsAPM.logger = Logger.new(STDERR)
41
- # set log level to INFO to be consistent with oboe, DEBUG would be default
41
+ # set log level to INFO to be consistent with the c-lib, DEBUG would be default
42
42
  AppOpticsAPM.logger.level = Logger::INFO
@@ -0,0 +1,317 @@
1
+ #--
2
+ # Copyright (c) 2016 SolarWinds, LLC.
3
+ # All rights reserved.
4
+ #++
5
+
6
+ module AppOpticsAPM
7
+ module SDK
8
+
9
+ ##
10
+ # Traces are best created with an <tt>AppOpticsAPM::SDK.start_trace</tt> block and
11
+ # <tt>AppOpticsAPM::SDK.trace</tt> blocks around calls to be traced.
12
+ # These two methods guarantee proper nesting of traces, handling of the tracing context, as well as avoiding
13
+ # broken traces in case of exceptions.
14
+ #
15
+ # Some optional keys that can be used in the +opts+ hash:
16
+ # * +:TransactionName+ - this will show up in the transactions column in the traces dashboard
17
+ # * +:Controller+ - if present will be combined with +Action+ and show up as transaction in the traces dashboard
18
+ # * +:Action+ - if present will be combined with +Controller+ and show up as transaction in the traces dashboard
19
+ # * +:HTTP-Host+ - domain portion of URL
20
+ # * +:URL+ - request URI
21
+ # * +:Method+
22
+ #
23
+ # Invalid keys: +:Label+, +:Layer+, +:Edge+, +:Timestamp+, +:Timestamp_u+
24
+ #
25
+ # The methods are exposed as singleton methods for AppOpticsAPM::SDK.
26
+ #
27
+ # === Usage:
28
+ # * +AppOpticsAPM::SDK.appoptics_ready?+
29
+ # * +AppOpticsAPM::SDK.get_transaction_name+
30
+ # * +AppOpticsAPM::SDK.set_transaction_name+
31
+ # * +AppOpticsAPM::SDK.start_trace+
32
+ # * +AppOpticsAPM::SDK.start_trace_sith_target+
33
+ # * +AppOpticsAPM::SDK.trace+
34
+ # * +AppOpticsAPM::SDK.tracing?+
35
+ #
36
+ # === Example:
37
+ # class MonthlyCouponEmailJob
38
+ # def perform(*args)
39
+ #
40
+ # # KVs to report to the dashboard
41
+ # report_kvs = {}
42
+ # report_kvs[:Spec] = :job
43
+ # report_kvs[:Controller] = :MonthlyEmailJob
44
+ # report_kvs[:Action] = :CouponEmailer
45
+ #
46
+ # # Start tracing this job with start_trace
47
+ # AppOpticsAPM::SDK.start_trace('starling', nil, report_kvs) do
48
+ # monthly = MonthlyEmail.new(:CouponEmailer)
49
+ #
50
+ # # Trace a sub-component of this trace
51
+ # AppOpticsAPM::SDK.trace(self.class.name) do
52
+ #
53
+ # # The work to be done
54
+ # users = User.all
55
+ # users.each do |u|
56
+ # monthly.send(u.email)
57
+ # end
58
+ #
59
+ # end
60
+ # end
61
+ # end
62
+ # end
63
+ #
64
+ module Tracing
65
+
66
+ # Trace a given block of code.
67
+ #
68
+ # Also detects any exceptions thrown by the block and report errors.
69
+ #
70
+ # === Arguments:
71
+ # * +:span+ - The span the block of code belongs to.
72
+ # * +:opts+ - (optional) A hash containing key/value pairs that will be reported along with the first event of this span.
73
+ # * +:protect_op+ - (optional) The operation being traced. Used to avoid double tracing operations that call each other.
74
+ #
75
+ # === Example:
76
+ #
77
+ # def computation(n)
78
+ # fib(n)
79
+ # raise Exception.new
80
+ # end
81
+ #
82
+ # def computation_with_appoptics(n)
83
+ # trace('fib', { :number => n }, :fib) do
84
+ # computation(n)
85
+ # end
86
+ # end
87
+ #
88
+ # result = computation_with_appoptics(100)
89
+ #
90
+ # === Returns:
91
+ # * The result of the block.
92
+ #
93
+ def trace(span, opts = {}, protect_op = nil)
94
+ return yield if !AppOpticsAPM.loaded || !AppOpticsAPM.tracing? || (protect_op && AppOpticsAPM.layer_op == protect_op.to_sym)
95
+
96
+ AppOpticsAPM::API.log_entry(span, opts, protect_op)
97
+ begin
98
+ yield
99
+ rescue Exception => e
100
+ AppOpticsAPM::API.log_exception(span, e)
101
+ raise
102
+ ensure
103
+ AppOpticsAPM::API.log_exit(span, opts, protect_op)
104
+ end
105
+ end
106
+
107
+
108
+ # Collect metrics and start tracing a given block of code.
109
+ #
110
+ # This will start a trace depending on configuration and probability, detect any exceptions
111
+ # thrown by the block, and report errors.
112
+ #
113
+ # When start_trace returns control to the calling context, the trace will be
114
+ # completed and the tracing context will be cleared.
115
+ #
116
+ # === Arguments:
117
+ #
118
+ # * +span+ - Name for the span to be used as label in the trace view.
119
+ # * +xtrace+ - (optional) incoming X-Trace identifier to be continued.
120
+ # * +opts+ - (optional) hash containing key/value pairs that will be reported with this span.
121
+ #
122
+ # === Example:
123
+ #
124
+ # def handle_request(request, response)
125
+ # # ... code that processes request and response ...
126
+ # end
127
+ #
128
+ # def handle_request_with_appoptics(request, response)
129
+ # start_trace('custom_trace', nil, :TransactionName => 'handle_request') do
130
+ # handle_request(request, response)
131
+ # end
132
+ # end
133
+ #
134
+ # === Returns:
135
+ # * The result of the block.
136
+ #
137
+ def start_trace(span, xtrace = nil, opts = {})
138
+ return yield unless AppOpticsAPM.loaded
139
+
140
+ # in case it is not an entry span!
141
+ return trace(span, opts) { yield } if AppOpticsAPM::Context.isValid
142
+
143
+ AppOpticsAPM::API.log_start(span, xtrace, opts)
144
+
145
+ # send_metrics deals with the logic for setting AppOpticsAPM.transaction_name
146
+ # and ensures that metrics are sent
147
+ # log_end includes sending the transaction_name
148
+ result = AppOpticsAPM::API.send_metrics(span, opts) do
149
+ begin
150
+ yield
151
+ rescue Exception => e # rescue everything ok, since we are raising
152
+ AppOpticsAPM::API.log_exception(span, e)
153
+ e.instance_variable_set(:@xtrace, AppOpticsAPM::API.log_end(span))
154
+ raise
155
+ end
156
+ end
157
+ AppOpticsAPM::API.log_end(span)
158
+
159
+ result
160
+ end
161
+
162
+ # Collect metrics, trace a given block of code, and assign trace info to target.
163
+ #
164
+ # This will start a trace depending on configuration and probability, detect any exceptions
165
+ # thrown by the block, report errors, and assign an X-Trace to the target.
166
+ #
167
+ # The motivating use case for this is HTTP streaming in rails3. We need
168
+ # access to the exit event's trace id so we can set the header before any
169
+ # work is done, and before any headers are sent back to the client.
170
+ #
171
+ # === Arguments:
172
+ # * +span+ - The span the block of code belongs to.
173
+ # * +xtrace+ - (optional) incoming X-Trace identifier to be continued.
174
+ # * +target+ - (optional) has to respond to #[]=, The target object in which to place the trace information.
175
+ # * +opts+ - (optional) hash containing key/value pairs that will be reported with this span.
176
+ #
177
+ # === Example:
178
+ #
179
+ # def handle_request(request, response)
180
+ # # ... code that processes request and response ...
181
+ # end
182
+ #
183
+ # def handle_request_with_appoptics(request, response)
184
+ # start_trace_with_target('rails', request['X-Trace'], response) do
185
+ # handle_request(request, response)
186
+ # end
187
+ # end
188
+ #
189
+ # === Returns:
190
+ # * The result of the block.
191
+ #
192
+ def start_trace_with_target(span, xtrace, target, opts = {})
193
+ return yield unless AppOpticsAPM.loaded
194
+
195
+ if AppOpticsAPM::Context.isValid # not an entry span!
196
+ result = trace(span) { yield }
197
+ target['X-Trace'] = AppOpticsAPM::Context.toString
198
+ return result
199
+ end
200
+
201
+ AppOpticsAPM::API.log_start(span, xtrace, opts)
202
+ exit_evt = AppOpticsAPM::Context.createEvent
203
+ result = AppOpticsAPM::API.send_metrics(span, opts) do
204
+ begin
205
+ target['X-Trace'] = AppOpticsAPM::EventUtil.metadataString(exit_evt) if AppOpticsAPM.tracing?
206
+ yield
207
+ rescue Exception => e
208
+ AppOpticsAPM::API.log_exception(span, e)
209
+ exit_evt.addEdge(AppOpticsAPM::Context.get)
210
+ xtrace = AppOpticsAPM::API.log_end(span, opts, exit_evt)
211
+ e.instance_variable_set(:@xtrace, xtrace)
212
+ raise
213
+ end
214
+ end
215
+ exit_evt.addEdge(AppOpticsAPM::Context.get)
216
+ AppOpticsAPM::API.log_end(span, opts, exit_evt)
217
+
218
+ result
219
+ end
220
+
221
+ # Provide a custom transaction name
222
+ #
223
+ # The AppOpticsAPM gem tries to create meaningful transaction names from controller+action
224
+ # or something similar depending on the framework used. However, you may want to override the
225
+ # transaction name to better describe your instrumented operation.
226
+ #
227
+ # Take note that on the dashboard the transaction name is converted to lowercase, and might be
228
+ # truncated with invalid characters replaced. Method calls with an empty string or a non-string
229
+ # argument won't change the current transaction name.
230
+ #
231
+ # The configuration +AppOpticsAPM.Config+['transaction_name']+['prepend_domain']+ can be set to
232
+ # true to have the domain name prepended to the transaction name. This is a global setting.
233
+ #
234
+ # === Argument:
235
+ #
236
+ # * +name+ - A non-empty string with the custom transaction name
237
+ #
238
+ # === Example:
239
+ #
240
+ # class DogfoodsController < ApplicationController
241
+ #
242
+ # def create
243
+ # @dogfood = Dogfood.new(params.permit(:brand, :name))
244
+ # @dogfood.save
245
+ #
246
+ # AppOpticsAPM::SDK.set_transaction_name("dogfoodscontroller.create_for_#{params[:brand]}")
247
+ #
248
+ # redirect_to @dogfood
249
+ # end
250
+ #
251
+ # end
252
+ #
253
+ # === Returns:
254
+ # * (String or nil) the current transaction name
255
+ #
256
+ def set_transaction_name(name)
257
+ if name.is_a?(String) && name.strip != ''
258
+ AppOpticsAPM.transaction_name = name
259
+ else
260
+ AppOpticsAPM.logger.debug "[appoptics_apm/api] Could not set transaction name, provided name is empty or not a String."
261
+ end
262
+ AppOpticsAPM.transaction_name
263
+ end
264
+
265
+
266
+ # Get the currently set custom transaction name.
267
+ #
268
+ # This is provided for testing
269
+ #
270
+ # === Returns:
271
+ # * (String or nil) the current transaction name
272
+ #
273
+ def get_transaction_name
274
+ AppOpticsAPM.transaction_name
275
+ end
276
+
277
+ # Determine if this transaction is being traced.
278
+ #
279
+ # Tracing puts some extra load on a system, therefor not all transaction are traced.
280
+ # The `tracing?` method helps to determine this so that extra work can be avoided when not tracing.
281
+ #
282
+ # === Example:
283
+ #
284
+ # kvs = expensive_info_gathering_method if AppOpticsAPM::SDK.tracing?
285
+ # AppOpticsAPM::SDK.trace('some_span', kvs) do
286
+ # # this may not create a trace every time it runs
287
+ # db_request
288
+ # end
289
+ #
290
+ def tracing?
291
+ AppOpticsAPM.tracing?
292
+ end
293
+
294
+ # Wait for AppOptics to be ready to send traces.
295
+ #
296
+ # This may be useful in short lived background processes when it is important to capture
297
+ # information during the whole time the process is running. Usually AppOptics doesn't block an
298
+ # application while it is starting up.
299
+ #
300
+ # === Argument:
301
+ #
302
+ # * +wait_milliseconds+ (int, default 3000) the maximum time to wait in milliseconds
303
+ #
304
+ # === Example:
305
+ #
306
+ # unless AppopticsAPM::SDK.appoptics_ready?(10_000)
307
+ # Logger.info "AppOptics not ready after 10 seconds, no metrics will be sent"
308
+ # end
309
+ #
310
+ def appoptics_ready?(wait_milliseconds = 3000)
311
+ AppopticsAPM::Context.isReady(wait_milliseconds)
312
+ end
313
+ end
314
+
315
+ extend Tracing
316
+ end
317
+ end