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,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