appoptics_apm 4.2.2 → 4.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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