appoptics_apm_mnfst 4.5.2

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 (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,101 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module AppOpticsAPM
5
+ module Inst
6
+ module EventMachine
7
+ module HttpConnection
8
+ def setup_request_with_appoptics(*args, &block)
9
+ context = AppOpticsAPM::Context.toString
10
+ blacklisted = AppOpticsAPM::API.blacklisted?(@uri)
11
+
12
+ if AppOpticsAPM.tracing?
13
+ report_kvs = {}
14
+
15
+ begin
16
+ report_kvs[:Spec] = 'rsc'
17
+ report_kvs[:IsService] = 1
18
+ report_kvs[:RemoteURL] = @uri
19
+ report_kvs[:HTTPMethod] = args[0]
20
+ report_kvs[:Blacklisted] = true if blacklisted
21
+ report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:em_http_request][:collect_backtraces]
22
+ rescue => e
23
+ AppOpticsAPM.logger.debug "[appoptics_apm/debug] em-http-request KV error: #{e.inspect}"
24
+ end
25
+
26
+ context = AppOpticsAPM::API.log_entry('em-http-request', report_kvs)
27
+ end
28
+ client = setup_request_without_appoptics(*args, &block)
29
+ client.req.headers['X-Trace'] = context unless blacklisted
30
+ client
31
+ end
32
+ end
33
+
34
+ module HttpClient
35
+ def parse_response_header_with_appoptics(*args, &block)
36
+ report_kvs = {}
37
+ xtrace = nil
38
+ blacklisted = AppOpticsAPM::API.blacklisted?(@uri)
39
+
40
+ begin
41
+ report_kvs[:HTTPStatus] = args[2]
42
+ report_kvs[:Async] = 1
43
+ rescue => e
44
+ AppOpticsAPM.logger.debug "[appoptics_apm/debug] em-http-request KV error: #{e.inspect}"
45
+ end
46
+
47
+ parse_response_header_without_appoptics(*args, &block)
48
+
49
+ unless blacklisted
50
+ headers = args[0]
51
+ context = AppOpticsAPM::Context.toString
52
+ task_id = AppOpticsAPM::XTrace.task_id(context)
53
+
54
+ if headers.is_a?(Hash) && headers.key?('X-Trace')
55
+ xtrace = headers['X-Trace']
56
+ end
57
+
58
+ if AppOpticsAPM::XTrace.valid?(xtrace) && AppOpticsAPM.tracing?
59
+
60
+ # Assure that we received back a valid X-Trace with the same task_id
61
+ if task_id == AppOpticsAPM::XTrace.task_id(xtrace)
62
+ AppOpticsAPM::Context.fromString(xtrace)
63
+ else
64
+ AppOpticsAPM.logger.debug "[appoptics_apm/em-http] Mismatched returned X-Trace ID : #{xtrace}"
65
+ end
66
+ end
67
+
68
+ end
69
+ ensure
70
+ AppOpticsAPM::API.log_exit(:'em-http-request', report_kvs)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ if defined?(EventMachine::HttpConnection) && defined?(EventMachine::HttpClient) && AppOpticsAPM::Config[:em_http_request][:enabled]
78
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting em-http-request' if AppOpticsAPM::Config[:verbose]
79
+
80
+ class EventMachine::HttpConnection
81
+ include AppOpticsAPM::Inst::EventMachine::HttpConnection
82
+
83
+ if method_defined?(:setup_request)
84
+ class_eval 'alias :setup_request_without_appoptics :setup_request'
85
+ class_eval 'alias :setup_request :setup_request_with_appoptics'
86
+ else
87
+ AppOpticsAPM.logger.warn '[appoptics_apm/loading] Couldn\'t properly instrument em-http-request (:setup_request). Partial traces may occur.'
88
+ end
89
+ end
90
+
91
+ class EventMachine::HttpClient
92
+ include AppOpticsAPM::Inst::EventMachine::HttpClient
93
+
94
+ if method_defined?(:parse_response_header)
95
+ class_eval 'alias :parse_response_header_without_appoptics :parse_response_header'
96
+ class_eval 'alias :parse_response_header :parse_response_header_with_appoptics'
97
+ else
98
+ AppOpticsAPM.logger.warn '[appoptics_apm/loading] Couldn\'t properly instrument em-http-request (:parse_response_header). Partial traces may occur.'
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,125 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module AppOpticsAPM
5
+ module Inst
6
+ module ExconConnection
7
+ def self.included(klass)
8
+ AppOpticsAPM::Util.method_alias(klass, :request, ::Excon::Connection)
9
+ AppOpticsAPM::Util.method_alias(klass, :requests, ::Excon::Connection)
10
+ end
11
+
12
+ private
13
+
14
+ def appoptics_collect(params)
15
+ kvs = {}
16
+ kvs[:Spec] = 'rsc'
17
+ kvs[:IsService] = 1
18
+
19
+ # Conditionally log query args
20
+ if AppOpticsAPM::Config[:excon][:log_args] && @data[:query]
21
+ if @data[:query].is_a?(Hash)
22
+ service_arg = "#{@data[:path]}?#{URI.encode_www_form(@data[:query])}"
23
+ else
24
+ service_arg = "#{@data[:path]}?#{@data[:query]}"
25
+ end
26
+ else
27
+ service_arg = @data[:path]
28
+ end
29
+ kvs[:RemoteURL] = "#{@data[:scheme]}://#{@data[:host]}:#{@data[:port]}#{service_arg}"
30
+
31
+ # In the case of HTTP pipelining, params could be an array of
32
+ # request hashes.
33
+ if params.is_a?(Array)
34
+ methods = []
35
+ params.each do |p|
36
+ methods << AppOpticsAPM::Util.upcase(p[:method])
37
+ end
38
+ kvs[:HTTPMethods] = methods.join(',')[0..1024]
39
+ kvs[:Pipeline] = true
40
+ else
41
+ kvs[:HTTPMethod] = AppOpticsAPM::Util.upcase(params[:method])
42
+ end
43
+ kvs
44
+ rescue => e
45
+ AppOpticsAPM.logger.debug "[appoptics_apm/debug] Error capturing excon KVs: #{e.message}"
46
+ AppOpticsAPM.logger.debug e.backtrace.join('\n') if AppOpticsAPM::Config[:verbose]
47
+ ensure
48
+ return kvs
49
+ end
50
+
51
+ public
52
+
53
+ def requests_with_appoptics(pipeline_params)
54
+ responses = nil
55
+ kvs = appoptics_collect(pipeline_params)
56
+ AppOpticsAPM::API.trace(:excon, kvs) do
57
+ kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:excon][:collect_backtraces]
58
+ responses = requests_without_appoptics(pipeline_params)
59
+ kvs[:HTTPStatuses] = responses.map { |r| r.status }.join(',')
60
+ end
61
+ responses
62
+ end
63
+
64
+ def request_with_appoptics(params={}, &block)
65
+ # Avoid cross host tracing for blacklisted domains
66
+ blacklisted = AppOpticsAPM::API.blacklisted?(@data[:hostname] || @data[:host])
67
+
68
+ # If we're not tracing, just do a fast return.
69
+ # If making HTTP pipeline requests (ordered batched)
70
+ # then just return as we're tracing from parent
71
+ # <tt>requests</tt>
72
+ if !AppOpticsAPM.tracing? || params[:pipeline]
73
+ @data[:headers]['X-Trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::Context.isValid && !blacklisted
74
+ return request_without_appoptics(params, &block)
75
+ end
76
+
77
+ begin
78
+ response_context = nil
79
+
80
+ kvs = appoptics_collect(params)
81
+ kvs[:Blacklisted] = true if blacklisted
82
+
83
+ AppOpticsAPM::API.log_entry(:excon, kvs)
84
+ kvs.clear
85
+
86
+ req_context = AppOpticsAPM::Context.toString
87
+ @data[:headers]['X-Trace'] = req_context unless blacklisted
88
+
89
+ # The core excon call
90
+ response = request_without_appoptics(params, &block)
91
+
92
+ # excon only passes back a hash (datum) for HTTP pipelining...
93
+ # In that case, we should never arrive here but for the OCD, double check
94
+ # the datatype before trying to extract pertinent info
95
+ if response.is_a?(Excon::Response)
96
+ response_context = response.headers['X-Trace']
97
+ kvs[:HTTPStatus] = response.status
98
+
99
+ # If we get a redirect, report the location header
100
+ if ((300..308).to_a.include? response.status.to_i) && response.headers.key?('Location')
101
+ kvs[:Location] = response.headers['Location']
102
+ end
103
+
104
+ if response_context && !blacklisted
105
+ AppOpticsAPM::XTrace.continue_service_context(req_context, response_context)
106
+ end
107
+ end
108
+
109
+ response
110
+ rescue => e
111
+ AppOpticsAPM::API.log_exception(:excon, e)
112
+ raise e
113
+ ensure
114
+ kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:excon][:collect_backtraces]
115
+ AppOpticsAPM::API.log_exit(:excon, kvs) unless params[:pipeline]
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ if AppOpticsAPM::Config[:excon][:enabled] && defined?(Excon)
123
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting excon' if AppOpticsAPM::Config[:verbose]
124
+ AppOpticsAPM::Util.send_include(Excon::Connection, AppOpticsAPM::Inst::ExconConnection)
125
+ end
@@ -0,0 +1,94 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module AppOpticsAPM
5
+ module Inst
6
+ module FaradayConnection
7
+ def self.included(klass)
8
+ AppOpticsAPM::Util.method_alias(klass, :run_request, ::Faraday::Connection)
9
+ end
10
+
11
+ def run_request_with_appoptics(method, url, body, headers, &block)
12
+ blacklisted = url_blacklisted?
13
+ remote_call = remote_call?
14
+
15
+ unless AppOpticsAPM.tracing?
16
+ if remote_call && !blacklisted
17
+ xtrace = AppOpticsAPM::Context.toString
18
+ @headers['X-Trace'] = xtrace if AppOpticsAPM::XTrace.valid?(xtrace)
19
+ end
20
+ return run_request_without_appoptics(method, url, body, headers, &block)
21
+ end
22
+
23
+ begin
24
+ AppOpticsAPM::API.log_entry(:faraday)
25
+ xtrace = nil
26
+ if remote_call && !blacklisted
27
+ xtrace = AppOpticsAPM::Context.toString
28
+ @headers['X-Trace'] = xtrace if AppOpticsAPM::XTrace.valid?(xtrace)
29
+ end
30
+
31
+ result = run_request_without_appoptics(method, url, body, headers, &block)
32
+
33
+ # Re-attach edge unless it's blacklisted
34
+ # or if we don't have a valid X-Trace header
35
+ if remote_call && !blacklisted
36
+ xtrace_new = result.headers['X-Trace']
37
+ AppOpticsAPM::XTrace.continue_service_context(xtrace, xtrace_new)
38
+ end
39
+ kvs = {}
40
+ kvs[:Middleware] = @builder.handlers
41
+ kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:faraday][:collect_backtraces]
42
+
43
+ # Only send service KVs if we're not using an adapter
44
+ # Otherwise, the adapter instrumentation will send the service KVs
45
+ if remote_call
46
+ kvs.merge!(rsc_kvs(url, method, result))
47
+ unless blacklisted
48
+ xtrace_new = result.headers['X-Trace']
49
+ AppOpticsAPM::XTrace.continue_service_context(xtrace, xtrace_new)
50
+ end
51
+ end
52
+
53
+ result
54
+ rescue => e
55
+ AppOpticsAPM::API.log_exception(:faraday, e)
56
+ raise e
57
+ ensure
58
+ AppOpticsAPM::API.log_exit(:faraday, kvs)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def url_blacklisted?
65
+ url = @url_prefix ? @url_prefix.to_s : @host
66
+ AppOpticsAPM::API.blacklisted?(url)
67
+ end
68
+
69
+ # This is only considered a remote service call if the middleware/adapter is not instrumented
70
+ def remote_call?
71
+ (@builder.handlers.map(&:name) & APPOPTICS_INSTR_ADAPTERS).count == 0
72
+ end
73
+
74
+ def rsc_kvs(url, method, result)
75
+ kvs = { :Spec => 'rsc',
76
+ :IsService => 1,
77
+ :HTTPMethod => method.upcase,
78
+ :HTTPStatus => result.status, }
79
+ kvs[:Blacklisted] = true if url_blacklisted?
80
+ kvs[:RemoteURL] = result.to_hash[:url].to_s
81
+ kvs[:RemoteURL].split('?').first unless AppOpticsAPM::Config[:faraday][:log_args]
82
+
83
+ kvs
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ if defined?(Faraday) && AppOpticsAPM::Config[:faraday][:enabled]
90
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting faraday' if AppOpticsAPM::Config[:verbose]
91
+ AppOpticsAPM::Util.send_include(Faraday::Connection, AppOpticsAPM::Inst::FaradayConnection)
92
+ APPOPTICS_INSTR_ADAPTERS = ["Faraday::Adapter::NetHttp", "Faraday::Adapter::Excon", "Faraday::Adapter::Typhoeus"]
93
+ APPOPTICS_INSTR_ADAPTERS << "Faraday::Adapter::HTTPClient" if defined?Faraday::Adapter::HTTPClient
94
+ end
@@ -0,0 +1,162 @@
1
+ # Copyright (c) 2018 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module AppOpticsAPM
5
+ module GRPC
6
+
7
+ module ActiveCall
8
+ if defined? ::GRPC
9
+ StatusCodes = {}
10
+ ::GRPC::Core::StatusCodes.constants.each { |code| StatusCodes[::GRPC::Core::StatusCodes.const_get(code)] = code }
11
+ end
12
+
13
+ def self.included(klass)
14
+ AppOpticsAPM::Util.method_alias(klass, :request_response, ::GRPC::ActiveCall)
15
+ AppOpticsAPM::Util.method_alias(klass, :client_streamer, ::GRPC::ActiveCall)
16
+ AppOpticsAPM::Util.method_alias(klass, :server_streamer, ::GRPC::ActiveCall)
17
+ AppOpticsAPM::Util.method_alias(klass, :bidi_streamer, ::GRPC::ActiveCall)
18
+ end
19
+
20
+ def grpc_tags(method_type, method)
21
+ tags = { 'Spec' => 'rsc',
22
+ 'RemoteURL' => "grpc://#{peer}#{method}",
23
+ 'GRPCMethodType' => method_type,
24
+ 'IsService' => 'True'
25
+ }
26
+ tags
27
+ end
28
+
29
+ def request_response_with_appoptics(req, metadata: {})
30
+ unary_response(req, type: 'UNARY', metadata: metadata, without: :request_response_without_appoptics)
31
+ end
32
+
33
+ def client_streamer_with_appoptics(req, metadata: {})
34
+ unary_response(req, type: 'CLIENT_STREAMING', metadata: metadata, without: :client_streamer_without_appoptics)
35
+ end
36
+
37
+ def server_streamer_with_appoptics(req, metadata: {}, &blk)
38
+ @tags = grpc_tags('SERVER_STREAMING', metadata[:method] || metadata_to_send[:method])
39
+ AppOpticsAPM::API.log_entry('grpc-client', @tags)
40
+ metadata['x-trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::Context.isValid
41
+ AppOpticsAPM::SDK.set_transaction_name(metadata[:method]) if AppOpticsAPM.transaction_name.nil?
42
+
43
+ patch_receive_and_check_status # need to patch this so that log_exit can be called after the enum is consumed
44
+
45
+ response = server_streamer_without_appoptics(req, metadata: metadata)
46
+ block_given? ? response.each { |r| yield r } : response
47
+ rescue => e
48
+ # this check is needed because the exception may have been logged in patch_receive_and_check_status
49
+ unless e.instance_variable_get(:@exn_logged)
50
+ context_from_incoming
51
+ AppOpticsAPM::API.log_exception('grpc-client', e)
52
+ AppOpticsAPM::API.log_exit('grpc-client', exit_tags(@tags))
53
+ end
54
+ raise e
55
+ end
56
+
57
+ def bidi_streamer_with_appoptics(req, metadata: {}, &blk)
58
+ @tags = grpc_tags('BIDI_STREAMING', metadata[:method] || metadata_to_send[:method])
59
+ AppOpticsAPM::API.log_entry('grpc-client', @tags)
60
+ metadata['x-trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::Context.isValid
61
+ AppOpticsAPM::SDK.set_transaction_name(metadata[:method]) if AppOpticsAPM.transaction_name.nil?
62
+
63
+ patch_set_input_stream_done
64
+
65
+ response = bidi_streamer_without_appoptics(req, metadata: metadata)
66
+ block_given? ? response.each { |r| yield r } : response
67
+ rescue => e
68
+ unless e.instance_variable_get(:@exn_logged)
69
+ context_from_incoming
70
+ AppOpticsAPM::API.log_exception('grpc-client', e)
71
+ AppOpticsAPM::API.log_exit('grpc-client', exit_tags(@tags))
72
+ end
73
+ raise e
74
+ end
75
+
76
+ private
77
+
78
+ def unary_response(req, type: , metadata: , without:)
79
+ tags = grpc_tags(type, metadata[:method] || metadata_to_send[:method])
80
+ AppOpticsAPM::SDK.trace('grpc-client', tags) do
81
+ metadata['x-trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::Context.isValid
82
+ AppOpticsAPM::SDK.set_transaction_name(metadata[:method]) if AppOpticsAPM.transaction_name.nil?
83
+ begin
84
+ send(without, req, metadata: metadata)
85
+ ensure
86
+ exit_tags(tags)
87
+ context_from_incoming
88
+ end
89
+ end
90
+ end
91
+
92
+ def patch_receive_and_check_status
93
+ def self.receive_and_check_status # need to patch this so that log_exit can be called after the enum is consumed
94
+ super
95
+ context_from_incoming
96
+ rescue => e
97
+ context_from_incoming
98
+ AppOpticsAPM::API.log_exception('grpc-client', e)
99
+ raise e
100
+ ensure
101
+ AppOpticsAPM::API.log_exit('grpc-client', exit_tags(@tags))
102
+ end
103
+ end
104
+
105
+ def patch_set_input_stream_done
106
+ # need to patch this instance method so that log_exit can be called after the enum is consumed
107
+ def self.set_input_stream_done
108
+ return if status.nil?
109
+ context_from_incoming
110
+ if status.code > 0
111
+ AppOpticsAPM::API.log_exception('grpc-client', $!)
112
+ end
113
+ AppOpticsAPM::API.log_exit('grpc-client', exit_tags(@tags))
114
+ super
115
+ end
116
+ end
117
+
118
+ def context_from_incoming
119
+ if AppOpticsAPM::Context.isValid
120
+ xtrace ||= @call.trailing_metadata['x-trace'] if @call.trailing_metadata && @call.trailing_metadata['x-trace']
121
+ xtrace ||= @call.metadata['x-trace'] if @call.metadata && @call.metadata['x-trace']
122
+ AppOpticsAPM::Context.fromString(xtrace) if xtrace
123
+ end
124
+ end
125
+
126
+ def exit_tags(tags)
127
+ # we need to translate the status.code, it is not the status.details we want, they are not matching 1:1
128
+ tags['GRPCStatus'] ||= @call.status ? StatusCodes[@call.status.code].to_s : 'UNKNOWN'
129
+ tags['Backtrace'] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:grpc_client][:collect_backtraces]
130
+ tags
131
+ end
132
+ end
133
+
134
+ end
135
+ end
136
+
137
+ if defined?(GRPC) && AppOpticsAPM::Config[:grpc_client][:enabled]
138
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting GRPC' if AppOpticsAPM::Config[:verbose]
139
+
140
+ # Client side is instrumented in ActiveCall and ClientStub
141
+ AppOpticsAPM::Util.send_include(GRPC::ActiveCall, AppOpticsAPM::GRPC::ActiveCall)
142
+
143
+ GRPC_ClientStub_ops = [:request_response, :client_streamer, :server_streamer, :bidi_streamer]
144
+ module GRPC
145
+ class ClientStub
146
+ GRPC_ClientStub_ops.reject { |m| !method_defined?(m) }.each do |m|
147
+ define_method("#{m}_with_appoptics") do |method, req, marshal, unmarshal, deadline: nil,
148
+ return_op: false, parent: nil,
149
+ credentials: nil, metadata: {}, &blk|
150
+
151
+ metadata[:method] = method
152
+ return send("#{m}_without_appoptics", method, req, marshal, unmarshal, deadline: deadline,
153
+ return_op: return_op, parent: parent,
154
+ credentials: credentials, metadata: metadata, &blk)
155
+ end
156
+
157
+ AppOpticsAPM::Util.method_alias(GRPC::ClientStub, m)
158
+ end
159
+
160
+ end
161
+ end
162
+ end