appoptics_apm_mnfst 4.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +5 -0
  3. data/.github/ISSUE_TEMPLATE/bug-or-feature-request.md +16 -0
  4. data/.gitignore +29 -0
  5. data/.rubocop.yml +8 -0
  6. data/.travis.yml +121 -0
  7. data/.yardopts +4 -0
  8. data/CHANGELOG.md +769 -0
  9. data/CONFIG.md +33 -0
  10. data/Gemfile +29 -0
  11. data/LICENSE +193 -0
  12. data/README.md +393 -0
  13. data/Rakefile +230 -0
  14. data/appoptics_apm.gemspec +61 -0
  15. data/bin/appoptics_apm_config +15 -0
  16. data/build_gem.sh +15 -0
  17. data/build_gem_upload_to_packagecloud.sh +20 -0
  18. data/examples/SDK/01_basic_tracing.rb +67 -0
  19. data/examples/carrying_context.rb +220 -0
  20. data/ext/oboe_metal/extconf.rb +114 -0
  21. data/ext/oboe_metal/lib/.keep +0 -0
  22. data/ext/oboe_metal/noop/noop.c +7 -0
  23. data/ext/oboe_metal/src/VERSION +1 -0
  24. data/init.rb +4 -0
  25. data/lib/appoptics_apm.rb +76 -0
  26. data/lib/appoptics_apm/api.rb +20 -0
  27. data/lib/appoptics_apm/api/layerinit.rb +41 -0
  28. data/lib/appoptics_apm/api/logging.rb +375 -0
  29. data/lib/appoptics_apm/api/memcache.rb +37 -0
  30. data/lib/appoptics_apm/api/metrics.rb +55 -0
  31. data/lib/appoptics_apm/api/profiling.rb +203 -0
  32. data/lib/appoptics_apm/api/tracing.rb +53 -0
  33. data/lib/appoptics_apm/api/util.rb +122 -0
  34. data/lib/appoptics_apm/base.rb +230 -0
  35. data/lib/appoptics_apm/config.rb +254 -0
  36. data/lib/appoptics_apm/frameworks/grape.rb +97 -0
  37. data/lib/appoptics_apm/frameworks/padrino.rb +108 -0
  38. data/lib/appoptics_apm/frameworks/rails.rb +94 -0
  39. data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +104 -0
  40. data/lib/appoptics_apm/frameworks/rails/inst/action_controller3.rb +55 -0
  41. data/lib/appoptics_apm/frameworks/rails/inst/action_controller4.rb +48 -0
  42. data/lib/appoptics_apm/frameworks/rails/inst/action_controller5.rb +50 -0
  43. data/lib/appoptics_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
  44. data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +58 -0
  45. data/lib/appoptics_apm/frameworks/rails/inst/action_view_30.rb +50 -0
  46. data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +27 -0
  47. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql.rb +43 -0
  48. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
  49. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +31 -0
  50. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +119 -0
  51. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +108 -0
  52. data/lib/appoptics_apm/frameworks/sinatra.rb +125 -0
  53. data/lib/appoptics_apm/inst/bunny-client.rb +148 -0
  54. data/lib/appoptics_apm/inst/bunny-consumer.rb +89 -0
  55. data/lib/appoptics_apm/inst/curb.rb +330 -0
  56. data/lib/appoptics_apm/inst/dalli.rb +85 -0
  57. data/lib/appoptics_apm/inst/delayed_job.rb +92 -0
  58. data/lib/appoptics_apm/inst/em-http-request.rb +101 -0
  59. data/lib/appoptics_apm/inst/excon.rb +125 -0
  60. data/lib/appoptics_apm/inst/faraday.rb +94 -0
  61. data/lib/appoptics_apm/inst/grpc_client.rb +162 -0
  62. data/lib/appoptics_apm/inst/grpc_server.rb +120 -0
  63. data/lib/appoptics_apm/inst/http.rb +73 -0
  64. data/lib/appoptics_apm/inst/httpclient.rb +174 -0
  65. data/lib/appoptics_apm/inst/memcached.rb +86 -0
  66. data/lib/appoptics_apm/inst/mongo.rb +246 -0
  67. data/lib/appoptics_apm/inst/mongo2.rb +225 -0
  68. data/lib/appoptics_apm/inst/moped.rb +466 -0
  69. data/lib/appoptics_apm/inst/rack.rb +199 -0
  70. data/lib/appoptics_apm/inst/redis.rb +275 -0
  71. data/lib/appoptics_apm/inst/resque.rb +151 -0
  72. data/lib/appoptics_apm/inst/rest-client.rb +48 -0
  73. data/lib/appoptics_apm/inst/sequel.rb +178 -0
  74. data/lib/appoptics_apm/inst/sidekiq-client.rb +55 -0
  75. data/lib/appoptics_apm/inst/sidekiq-worker.rb +65 -0
  76. data/lib/appoptics_apm/inst/twitter-cassandra.rb +294 -0
  77. data/lib/appoptics_apm/inst/typhoeus.rb +108 -0
  78. data/lib/appoptics_apm/instrumentation.rb +22 -0
  79. data/lib/appoptics_apm/legacy_method_profiling.rb +90 -0
  80. data/lib/appoptics_apm/loading.rb +65 -0
  81. data/lib/appoptics_apm/logger.rb +42 -0
  82. data/lib/appoptics_apm/method_profiling.rb +33 -0
  83. data/lib/appoptics_apm/noop/README.md +9 -0
  84. data/lib/appoptics_apm/noop/context.rb +26 -0
  85. data/lib/appoptics_apm/noop/metadata.rb +22 -0
  86. data/lib/appoptics_apm/ruby.rb +35 -0
  87. data/lib/appoptics_apm/sdk/custom_metrics.rb +92 -0
  88. data/lib/appoptics_apm/sdk/tracing.rb +315 -0
  89. data/lib/appoptics_apm/support.rb +119 -0
  90. data/lib/appoptics_apm/test.rb +94 -0
  91. data/lib/appoptics_apm/thread_local.rb +26 -0
  92. data/lib/appoptics_apm/util.rb +319 -0
  93. data/lib/appoptics_apm/version.rb +15 -0
  94. data/lib/appoptics_apm/xtrace.rb +103 -0
  95. data/lib/joboe_metal.rb +212 -0
  96. data/lib/oboe.rb +7 -0
  97. data/lib/oboe/README +2 -0
  98. data/lib/oboe/backward_compatibility.rb +80 -0
  99. data/lib/oboe/inst/rack.rb +11 -0
  100. data/lib/oboe_metal.rb +198 -0
  101. data/lib/rails/generators/appoptics_apm/install_generator.rb +45 -0
  102. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +265 -0
  103. data/yardoc_frontpage.md +26 -0
  104. metadata +266 -0
@@ -0,0 +1,65 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ require 'digest/sha1'
5
+
6
+ module AppOpticsAPM
7
+ module Util
8
+ ##
9
+ # This module was used solely for the deprecated RUM ID calculation
10
+ # but may be useful in the future.
11
+ #
12
+ module Base64URL
13
+ module_function
14
+
15
+ def encode(bin)
16
+ c = [bin].pack('m0').gsub(/\=+\Z/, '').tr('+/', '-_').rstrip
17
+ m = c.size % 4
18
+ c += '=' * (4 - m) if m != 0
19
+ c
20
+ end
21
+
22
+ def decode(bin)
23
+ m = bin.size % 4
24
+ bin += '=' * (4 - m) if m != 0
25
+ bin.tr('-_', '+/').unpack('m0').first
26
+ end
27
+ end
28
+ end
29
+
30
+ ##
31
+ # This module houses all of the loading functionality for the appoptics_apm em.
32
+
33
+ # Note that this does not necessarily _have_ to include initialization routines
34
+ # (although it can).
35
+ #
36
+ # Actual initialization is often separated out as it can be dependent on on the state
37
+ # of the stack boot process. e.g. code requiring that initializers, frameworks or
38
+ # instrumented libraries are already loaded...
39
+ #
40
+ module Loading
41
+ ##
42
+ # Load the appoptics_apm tracing API
43
+ #
44
+ def self.require_api
45
+ pattern = File.join(File.dirname(__FILE__), 'api', '*.rb')
46
+ Dir.glob(pattern) do |f|
47
+ require f
48
+ end
49
+
50
+ begin
51
+ require 'appoptics_apm/api'
52
+ rescue LoadError => e
53
+ AppOpticsAPM.logger.fatal "[appoptics_apm/error] Couldn't load api: #{e.message}"
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ AppOpticsAPM::Loading.require_api
60
+
61
+ # Auto-start the Reporter unless we are running Unicorn on Heroku
62
+ # In that case, we start the reporters after fork
63
+ unless AppOpticsAPM.heroku? && AppOpticsAPM.forking_webserver?
64
+ AppOpticsAPM::Reporter.start if AppOpticsAPM.loaded
65
+ end
@@ -0,0 +1,42 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ require 'logger'
5
+
6
+ module AppOpticsAPM
7
+ class << self
8
+ attr_accessor :logger
9
+ end
10
+
11
+ class Logger
12
+ # Fatal message
13
+ def fatal(string, exception = nil)
14
+ AppOpticsAPM.logger.fatal(string) if AppOpticsAPM.logger
15
+ end
16
+
17
+ # Error message
18
+ def error(msg, exception = nil)
19
+ AppOpticsAPM.logger.error(string) if AppOpticsAPM.logger
20
+ end
21
+
22
+ # Warn message
23
+ def warn(msg, exception = nil)
24
+ AppOpticsAPM.logger.warn(string) if AppOpticsAPM.logger
25
+ end
26
+
27
+ # Info message
28
+ def info(msg, exception = nil)
29
+ AppOpticsAPM.logger.info(string) if AppOpticsAPM.logger
30
+ end
31
+
32
+ # Debug message
33
+ def debug(msg, exception = nil)
34
+ AppOpticsAPM.logger.debug(string) if AppOpticsAPM.logger
35
+ end
36
+
37
+ end
38
+ end
39
+
40
+ AppOpticsAPM.logger = Logger.new(STDERR)
41
+ # set log level to INFO to be consistent with the c-lib, DEBUG would be default
42
+ AppOpticsAPM.logger.level = Logger::INFO
@@ -0,0 +1,33 @@
1
+
2
+ module AppOpticsAPM
3
+ module MethodProfiling
4
+ def profile_wrapper(method, report_kvs, opts, *args, &block)
5
+ report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace(2) if opts[:backtrace]
6
+ report_kvs[:Arguments] = args if opts[:arguments]
7
+
8
+ # if this is a rails controller we want to set the transaction for the outbound metrics
9
+ if defined?(request) && defined?(request.env)
10
+ report_kvs['Controller'] = self.class.name
11
+ report_kvs['Action'] = self.action_name
12
+ request.env['appoptics_apm.controller'] = report_kvs['Controller']
13
+ request.env['appoptics_apm.action'] = report_kvs['Action']
14
+ end
15
+
16
+ AppOpticsAPM::API.log(nil, :profile_entry, report_kvs)
17
+
18
+ begin
19
+ rv = self.send(method, *args, &block)
20
+ report_kvs[:ReturnValue] = rv if opts[:result]
21
+ rv
22
+ rescue => e
23
+ AppOpticsAPM::API.log_exception(nil, e)
24
+ raise
25
+ ensure
26
+ report_kvs.delete(:Backtrace)
27
+ report_kvs.delete(:Controller)
28
+ report_kvs.delete(:Action)
29
+ AppOpticsAPM::API.log(nil, :profile_exit, report_kvs)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ Here we can define modules and classes for noop mode.
2
+
3
+ Instead of polluting code with AppOpticsAPM.loaded conditionals
4
+
5
+ we load these classes when in noop mode and they expose noop behavior.
6
+
7
+ so far only one class is needed:
8
+
9
+ - AppOpticsAPM::Context and its toString() method from oboe
@@ -0,0 +1,26 @@
1
+ ####
2
+ # noop version of AppOpticsAPM::Context
3
+ #
4
+ #
5
+
6
+ module AppOpticsAPM
7
+ module Context
8
+
9
+ ##
10
+ # noop version of :toString
11
+ # toString would return the current context (xtrace) as string
12
+ #
13
+ # the noop version returns an empty string
14
+ #
15
+ def self.toString
16
+ ''
17
+ end
18
+
19
+ ##
20
+ # noop version of :clear
21
+ #
22
+ def self.clear
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ ####
2
+ # noop version of AppOpticsAPM::Metadata
3
+ #
4
+ #
5
+
6
+ module AppOpticsAPM
7
+ class Metadata
8
+
9
+ ##
10
+ # noop version of :makeRandom
11
+ #
12
+ # needs to return an object that responds to :isValid
13
+ #
14
+ def self.makeRandom
15
+ Metadata.new
16
+ end
17
+
18
+ def isValid
19
+ false
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module AppOpticsAPM
5
+ ##
6
+ # This module provides a method to manually initialize the
7
+ # Ruby instrumentation. Normally this is done by detecting
8
+ # frameworks at load time and inserting initialization hooks.
9
+ module Ruby
10
+ class << self
11
+ def initialize
12
+ load
13
+ end
14
+
15
+ ##
16
+ # The core method to load Ruby instrumentation. Call this
17
+ # from raw Ruby scripts or in Ruby applications where a
18
+ # supported framework isn't being used. Supported frameworks
19
+ # will instead be detected at load time and initialization is
20
+ # automatic.
21
+ def load
22
+ # In case some apps call this manually, make sure
23
+ # that the gem is fully loaded and not in no-op
24
+ # mode (e.g. on unsupported platforms etc.)
25
+ if AppOpticsAPM.loaded
26
+ AppOpticsAPM::Inst.load_instrumentation
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ if AppOpticsAPM.loaded && !AppOpticsAPM.framework?
34
+ AppOpticsAPM::Ruby.load
35
+ end
@@ -0,0 +1,92 @@
1
+ #--
2
+ # Copyright (c) 2016 SolarWinds, LLC.
3
+ # All rights reserved.
4
+ #++
5
+
6
+ module AppOpticsAPM
7
+ module SDK
8
+
9
+ module CustomMetrics
10
+
11
+ # Send counts
12
+ #
13
+ # Use this method to report the number of times an action occurs. The metric counts reported are summed and flushed every 60 seconds.
14
+ #
15
+ # === Arguments:
16
+ #
17
+ # * +name+ (String) Name to be used for the metric. Must be 255 or fewer characters and consist only of A-Za-z0-9.:-*
18
+ # * +count+ (Integer, optional, default = 1): Count of actions being reported
19
+ # * +with_hostname+ (Boolean, optional, default = false): Indicates if the host name should be included as a tag for the metric
20
+ # * +tags_kvs+ (Hash, optional): List of key/value pairs to describe the metric. The key must be <= 64 characters, the value must be <= 255 characters, allowed characters: A-Za-z0-9.:-_
21
+ #
22
+ # === Example:
23
+ #
24
+ # class WorkTracker
25
+ # def counting(name, tags = {})
26
+ # yield # yield to where work is done
27
+ # AppOpticsAPM::SDK.increment_metric(name, 1, false, tags)
28
+ # end
29
+ # end
30
+ #
31
+ # === Returns:
32
+ # * true on success, false on failure
33
+ #
34
+ def increment_metric(name, count = 1, with_hostname = false, tags_kvs = {})
35
+ with_hostname = with_hostname ? 1 : 0
36
+ tags, tags_count = make_tags(tags_kvs)
37
+ AppOpticsAPM::CustomMetrics.increment(name.to_s, count, with_hostname, nil, tags, tags_count) == 1
38
+ end
39
+
40
+ # Send values with counts
41
+ #
42
+ # Use this method to report a value for each or multiple counts. The metric values reported are aggregated and flushed every 60 seconds. The dashboard displays the average value per count.
43
+ #
44
+ # === Arguments:
45
+ #
46
+ # * +name+ (String) Name to be used for the metric. Must be 255 or fewer characters and consist only of A-Za-z0-9.:-*
47
+ # * +value+ (Numeric) Value to be added to the current sum
48
+ # * +count+ (Integer, optional, default = 1): Count of actions being reported
49
+ # * +with_hostname+ (Boolean, optional, default = false): Indicates if the host name should be included as a tag for the metric
50
+ # * +tags_kvs+ (Hash, optional): List of key/value pairs to describe the metric. The key must be <= 64 characters, the value must be <= 255 characters, allowed characters: A-Za-z0-9.:-_
51
+ #
52
+ # === Example:
53
+ #
54
+ # class WorkTracker
55
+ # def timing(name, tags = {})
56
+ # start = Time.now
57
+ # yield # yield to where work is done
58
+ # duration = Time.now - start
59
+ # AppOpticsAPM::SDK.summary_metric(name, duration, 1, false, tags)
60
+ # end
61
+ # end
62
+ #
63
+ # === Returns:
64
+ # * true on success, false on failure
65
+ #
66
+ def summary_metric(name, value, count = 1, with_hostname = false, tags_kvs = {})
67
+ with_hostname = with_hostname ? 1 : 0
68
+ tags, tags_count = make_tags(tags_kvs)
69
+ AppOpticsAPM::CustomMetrics.summary(name.to_s, value, count, with_hostname, nil, tags, tags_count) == 1
70
+ end
71
+
72
+ private
73
+
74
+ def make_tags(tags_kvs)
75
+ unless tags_kvs.is_a?(Hash)
76
+ AppOpticsAPM.logger.warn("[appoptics_apm/metrics] CustomMetrics received tags_kvs that are not a Hash (found #{tags_kvs.class}), setting tags_kvs = {}")
77
+ tags_kvs = {}
78
+ end
79
+ count = tags_kvs.size
80
+ tags = AppOpticsAPM::MetricTags.new(count)
81
+
82
+ tags_kvs.each_with_index do |(k, v), i|
83
+ tags.add(i, k.to_s, v.to_s)
84
+ end
85
+
86
+ [tags, count]
87
+ end
88
+ end
89
+
90
+ extend CustomMetrics
91
+ end
92
+ end
@@ -0,0 +1,315 @@
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
+ # * +:Controller+
17
+ # * +:Action+
18
+ # * +:HTTP-Host+
19
+ # * +:URL+
20
+ # * +:Method+
21
+ #
22
+ # as well as custom keys. The information will show up in the raw data view of a span.
23
+ #
24
+ # Invalid keys: +:Label+, +:Layer+, +:Edge+, +:Timestamp+, +:Timestamp_u+, +:TransactionName+ (allowed in start_trace)
25
+ #
26
+ # The methods are exposed as singleton methods for AppOpticsAPM::SDK.
27
+ #
28
+ # === Usage:
29
+ # * +AppOpticsAPM::SDK.appoptics_ready?+
30
+ # * +AppOpticsAPM::SDK.get_transaction_name+
31
+ # * +AppOpticsAPM::SDK.set_transaction_name+
32
+ # * +AppOpticsAPM::SDK.start_trace+
33
+ # * +AppOpticsAPM::SDK.start_trace_with_target+
34
+ # * +AppOpticsAPM::SDK.trace+
35
+ # * +AppOpticsAPM::SDK.tracing?+
36
+ #
37
+ # === Example:
38
+ # class MonthlyCouponEmailJob
39
+ # def perform(*args)
40
+ #
41
+ # # KVs to report to the dashboard
42
+ # report_kvs = {}
43
+ # report_kvs[:Spec] = :job
44
+ # report_kvs[:Controller] = :MonthlyEmailJob
45
+ # report_kvs[:Action] = :CouponEmailer
46
+ #
47
+ # # Start tracing this job with start_trace
48
+ # AppOpticsAPM::SDK.start_trace('starling', nil, report_kvs) do
49
+ # monthly = MonthlyEmail.new(:CouponEmailer)
50
+ #
51
+ # # Trace a sub-component of this trace
52
+ # AppOpticsAPM::SDK.trace(self.class.name) do
53
+ #
54
+ # # The work to be done
55
+ # users = User.all
56
+ # users.each do |u|
57
+ # monthly.send(u.email)
58
+ # end
59
+ #
60
+ # end
61
+ # end
62
+ # end
63
+ # end
64
+ #
65
+ module Tracing
66
+
67
+ # Trace a given block of code.
68
+ #
69
+ # Also detects any exceptions thrown by the block and report errors.
70
+ #
71
+ # === Arguments:
72
+ # * +:span+ - The span the block of code belongs to.
73
+ # * +:opts+ - (optional) A hash containing key/value pairs that will be reported along with the first event of this span.
74
+ # * +:protect_op+ - (optional) The operation being traced. Used to avoid double tracing operations that call each other.
75
+ #
76
+ # === Example:
77
+ #
78
+ # def computation_with_appoptics(n)
79
+ # AppOpticsAPM::SDK.trace('computation', { :number => n }, :computation) do
80
+ # return n if n == 0
81
+ # n + computation_with_appoptics(n-1)
82
+ # end
83
+ # end
84
+ #
85
+ # result = computation_with_appoptics(100)
86
+ #
87
+ # === Returns:
88
+ # * The result of the block.
89
+ #
90
+ def trace(span, opts = {}, protect_op = nil)
91
+ return yield if !AppOpticsAPM.loaded || !AppOpticsAPM.tracing? || AppOpticsAPM.tracing_layer_op?(protect_op)
92
+
93
+ opts.delete(:TransactionName)
94
+ opts.delete('TransactionName')
95
+
96
+ AppOpticsAPM::API.log_entry(span, opts, protect_op)
97
+ opts[:Backtrace] && opts.delete(:Backtrace) # to avoid sending backtrace twice (faster to check presence here)
98
+ begin
99
+ yield
100
+ rescue Exception => e
101
+ AppOpticsAPM::API.log_exception(span, e)
102
+ raise
103
+ ensure
104
+ AppOpticsAPM::API.log_exit(span, opts, protect_op)
105
+ end
106
+ end
107
+
108
+
109
+ # Collect metrics and start tracing a given block of code.
110
+ #
111
+ # This will start a trace depending on configuration and probability, detect any exceptions
112
+ # thrown by the block, and report errors.
113
+ #
114
+ # When start_trace returns control to the calling context, the trace will be
115
+ # completed and the tracing context will be cleared.
116
+ #
117
+ # === Arguments:
118
+ #
119
+ # * +span+ - Name for the span to be used as label in the trace view.
120
+ # * +xtrace+ - (optional) incoming X-Trace identifier to be continued.
121
+ # * +opts+ - (optional) hash containing key/value pairs that will be reported with this span.
122
+ # The value of :TransactionName will set the transaction_name.
123
+ #
124
+ # === Example:
125
+ #
126
+ # def handle_request(request, response)
127
+ # # ... code that processes request and response ...
128
+ # end
129
+ #
130
+ # def handle_request_with_appoptics(request, response)
131
+ # AppOpticsAPM::SDK.start_trace('custom_trace', nil, :TransactionName => 'handle_request') do
132
+ # handle_request(request, response)
133
+ # end
134
+ # end
135
+ #
136
+ # === Returns:
137
+ # * The result of the block.
138
+ #
139
+ def start_trace(span, xtrace = nil, opts = {})
140
+ start_trace_with_target(span, xtrace, {}, opts) { yield }
141
+ end
142
+
143
+ # Collect metrics, trace a given block of code, and assign trace info to target.
144
+ #
145
+ # This will start a trace depending on configuration and probability, detect any exceptions
146
+ # thrown by the block, report errors, and assign an X-Trace to the target.
147
+ #
148
+ # The motivating use case for this is HTTP streaming in rails3. We need
149
+ # access to the exit event's trace id so we can set the header before any
150
+ # work is done, and before any headers are sent back to the client.
151
+ #
152
+ # === Arguments:
153
+ # * +span+ - The span the block of code belongs to.
154
+ # * +xtrace+ - (optional) incoming X-Trace identifier to be continued.
155
+ # * +target+ - (optional) has to respond to #[]=, The target object in which to place the trace information.
156
+ # * +opts+ - (optional) hash containing key/value pairs that will be reported with this span.
157
+ #
158
+ # === Example:
159
+ #
160
+ # def handle_request(request, response)
161
+ # # ... code that processes request and response ...
162
+ # end
163
+ #
164
+ # def handle_request_with_appoptics(request, response)
165
+ # AppOpticsAPM::SDK.start_trace_with_target('rails', request['X-Trace'], response) do
166
+ # handle_request(request, response)
167
+ # end
168
+ # end
169
+ #
170
+ # === Returns:
171
+ # * The result of the block.
172
+ #
173
+ def start_trace_with_target(span, xtrace, target, opts = {})
174
+ return yield unless AppOpticsAPM.loaded
175
+
176
+ if AppOpticsAPM::Context.isValid # not an entry span!
177
+ result = trace(span, opts) { yield }
178
+ target['X-Trace'] = AppOpticsAPM::Context.toString
179
+ return result
180
+ end
181
+
182
+ # :TransactionName and 'TransactionName' need to be removed from opts
183
+ AppOpticsAPM.transaction_name = opts.delete('TransactionName') || opts.delete(:TransactionName)
184
+
185
+ AppOpticsAPM::API.log_start(span, xtrace, opts)
186
+ opts[:Backtrace] && opts.delete(:Backtrace) # to avoid sending backtrace twice (faster to check presence here)
187
+
188
+ # AppOpticsAPM::Event.startTrace creates an Event without an Edge
189
+ exit_evt = AppOpticsAPM::Event.startTrace(AppOpticsAPM::Context.get)
190
+ result = begin
191
+ AppOpticsAPM::API.send_metrics(span, opts) do
192
+ target['X-Trace'] = AppOpticsAPM::EventUtil.metadataString(exit_evt)
193
+ yield
194
+ end
195
+ rescue Exception => e
196
+ AppOpticsAPM::API.log_exception(span, e)
197
+ exit_evt.addEdge(AppOpticsAPM::Context.get)
198
+ xtrace = AppOpticsAPM::API.log_end(span, opts, exit_evt)
199
+ e.instance_variable_set(:@xtrace, xtrace)
200
+ raise
201
+ end
202
+
203
+ exit_evt.addEdge(AppOpticsAPM::Context.get)
204
+ AppOpticsAPM::API.log_end(span, opts, exit_evt)
205
+
206
+ result
207
+ end
208
+
209
+ # Provide a custom transaction name
210
+ #
211
+ # The AppOpticsAPM gem tries to create meaningful transaction names from controller+action
212
+ # or something similar depending on the framework used. However, you may want to override the
213
+ # transaction name to better describe your instrumented operation.
214
+ #
215
+ # Take note that on the dashboard the transaction name is converted to lowercase, and might be
216
+ # truncated with invalid characters replaced. Method calls with an empty string or a non-string
217
+ # argument won't change the current transaction name.
218
+ #
219
+ # The configuration +AppOpticsAPM.Config+['transaction_name']+['prepend_domain']+ can be set to
220
+ # true to have the domain name prepended to the transaction name when an event or a metric are
221
+ # logged. This is a global setting.
222
+ #
223
+ # === Argument:
224
+ #
225
+ # * +name+ - A non-empty string with the custom transaction name
226
+ #
227
+ # === Example:
228
+ #
229
+ # class DogfoodsController < ApplicationController
230
+ #
231
+ # def create
232
+ # @dogfood = Dogfood.new(params.permit(:brand, :name))
233
+ # @dogfood.save
234
+ #
235
+ # AppOpticsAPM::SDK.set_transaction_name("dogfoodscontroller.create_for_#{params[:brand]}")
236
+ #
237
+ # redirect_to @dogfood
238
+ # end
239
+ #
240
+ # end
241
+ #
242
+ # === Returns:
243
+ # * (String or nil) the current transaction name
244
+ #
245
+ def set_transaction_name(name)
246
+ if name.is_a?(String) && name.strip != ''
247
+ AppOpticsAPM.transaction_name = name
248
+ else
249
+ AppOpticsAPM.logger.debug "[appoptics_apm/api] Could not set transaction name, provided name is empty or not a String."
250
+ end
251
+ AppOpticsAPM.transaction_name
252
+ end
253
+
254
+
255
+ # Get the currently set custom transaction name.
256
+ #
257
+ # This is provided for testing
258
+ #
259
+ # === Returns:
260
+ # * (String or nil) the current transaction name (without domain prepended)
261
+ #
262
+ def get_transaction_name
263
+ AppOpticsAPM.transaction_name
264
+ end
265
+
266
+ # Determine if this transaction is being traced.
267
+ #
268
+ # Tracing puts some extra load on a system, therefor not all transaction are traced.
269
+ # The +tracing?+ method helps to determine this so that extra work can be avoided when not tracing.
270
+ #
271
+ # === Example:
272
+ #
273
+ # kvs = expensive_info_gathering_method if AppOpticsAPM::SDK.tracing?
274
+ # AppOpticsAPM::SDK.trace('some_span', kvs) do
275
+ # # this may not create a trace every time it runs
276
+ # db_request
277
+ # end
278
+ #
279
+ def tracing?
280
+ AppOpticsAPM.tracing?
281
+ end
282
+
283
+ # Wait for AppOptics to be ready to send traces.
284
+ #
285
+ # This may be useful in short lived background processes when it is important to capture
286
+ # information during the whole time the process is running. Usually AppOptics doesn't block an
287
+ # application while it is starting up.
288
+ #
289
+ # === Argument:
290
+ #
291
+ # * +wait_milliseconds+ (int, default 3000) the maximum time to wait in milliseconds
292
+ #
293
+ # === Example:
294
+ #
295
+ # unless AppopticsAPM::SDK.appoptics_ready?(10_000)
296
+ # Logger.info "AppOptics not ready after 10 seconds, no metrics will be sent"
297
+ # end
298
+ #
299
+ def appoptics_ready?(wait_milliseconds = 3000)
300
+ return false unless AppOpticsAPM.loaded
301
+ # These codes are returned by isReady:
302
+ # OBOE_SERVER_RESPONSE_UNKNOWN 0
303
+ # OBOE_SERVER_RESPONSE_OK 1
304
+ # OBOE_SERVER_RESPONSE_TRY_LATER 2
305
+ # OBOE_SERVER_RESPONSE_LIMIT_EXCEEDED 3
306
+ # OBOE_SERVER_RESPONSE_INVALID_API_KEY 4
307
+ # OBOE_SERVER_RESPONSE_CONNECT_ERROR 5
308
+ AppopticsAPM::Context.isReady(wait_milliseconds) == 1
309
+ end
310
+ end
311
+
312
+ extend Tracing
313
+
314
+ end
315
+ end