appoptics_apm-zearn 4.13.1

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 (145) 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/.github/workflows/build_and_release_gem.yml +103 -0
  5. data/.github/workflows/build_for_packagecloud.yml +70 -0
  6. data/.github/workflows/docker-images.yml +47 -0
  7. data/.github/workflows/run_cpluplus_tests.yml +73 -0
  8. data/.github/workflows/run_tests.yml +168 -0
  9. data/.github/workflows/scripts/test_install.rb +23 -0
  10. data/.github/workflows/swig/swig-v4.0.2.tar.gz +0 -0
  11. data/.github/workflows/test_on_4_linux.yml +159 -0
  12. data/.gitignore +36 -0
  13. data/.rubocop.yml +29 -0
  14. data/.travis.yml +130 -0
  15. data/.yardopts +6 -0
  16. data/CHANGELOG.md +769 -0
  17. data/CONFIG.md +33 -0
  18. data/Gemfile +14 -0
  19. data/LICENSE +202 -0
  20. data/README.md +393 -0
  21. data/appoptics_apm.gemspec +70 -0
  22. data/bin/appoptics_apm_config +15 -0
  23. data/examples/prepend.rb +13 -0
  24. data/examples/sdk_examples.rb +158 -0
  25. data/ext/oboe_metal/README.md +69 -0
  26. data/ext/oboe_metal/extconf.rb +151 -0
  27. data/ext/oboe_metal/lib/.keep +0 -0
  28. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.0.0.0.sha256 +1 -0
  29. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.0.0.0.sha256 +1 -0
  30. data/ext/oboe_metal/noop/noop.c +8 -0
  31. data/ext/oboe_metal/src/README.md +6 -0
  32. data/ext/oboe_metal/src/VERSION +2 -0
  33. data/ext/oboe_metal/src/bson/bson.h +220 -0
  34. data/ext/oboe_metal/src/bson/platform_hacks.h +91 -0
  35. data/ext/oboe_metal/src/frames.cc +246 -0
  36. data/ext/oboe_metal/src/frames.h +40 -0
  37. data/ext/oboe_metal/src/init_appoptics_apm.cc +21 -0
  38. data/ext/oboe_metal/src/logging.cc +95 -0
  39. data/ext/oboe_metal/src/logging.h +35 -0
  40. data/ext/oboe_metal/src/oboe.h +1156 -0
  41. data/ext/oboe_metal/src/oboe_api.cpp +652 -0
  42. data/ext/oboe_metal/src/oboe_api.hpp +431 -0
  43. data/ext/oboe_metal/src/oboe_debug.h +59 -0
  44. data/ext/oboe_metal/src/oboe_swig_wrap.cc +7329 -0
  45. data/ext/oboe_metal/src/profiling.cc +435 -0
  46. data/ext/oboe_metal/src/profiling.h +78 -0
  47. data/ext/oboe_metal/test/CMakeLists.txt +53 -0
  48. data/ext/oboe_metal/test/FindGMock.cmake +43 -0
  49. data/ext/oboe_metal/test/README.md +56 -0
  50. data/ext/oboe_metal/test/frames_test.cc +164 -0
  51. data/ext/oboe_metal/test/profiling_test.cc +93 -0
  52. data/ext/oboe_metal/test/ruby_inc_dir.rb +8 -0
  53. data/ext/oboe_metal/test/ruby_prefix.rb +8 -0
  54. data/ext/oboe_metal/test/ruby_test_helper.rb +67 -0
  55. data/ext/oboe_metal/test/test.h +11 -0
  56. data/ext/oboe_metal/test/test_main.cc +32 -0
  57. data/init.rb +4 -0
  58. data/lib/appoptics_apm/api/layerinit.rb +41 -0
  59. data/lib/appoptics_apm/api/logging.rb +381 -0
  60. data/lib/appoptics_apm/api/memcache.rb +37 -0
  61. data/lib/appoptics_apm/api/metrics.rb +63 -0
  62. data/lib/appoptics_apm/api/tracing.rb +57 -0
  63. data/lib/appoptics_apm/api/util.rb +120 -0
  64. data/lib/appoptics_apm/api.rb +21 -0
  65. data/lib/appoptics_apm/base.rb +231 -0
  66. data/lib/appoptics_apm/config.rb +299 -0
  67. data/lib/appoptics_apm/frameworks/grape.rb +98 -0
  68. data/lib/appoptics_apm/frameworks/padrino.rb +78 -0
  69. data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +104 -0
  70. data/lib/appoptics_apm/frameworks/rails/inst/action_controller4.rb +48 -0
  71. data/lib/appoptics_apm/frameworks/rails/inst/action_controller5.rb +50 -0
  72. data/lib/appoptics_apm/frameworks/rails/inst/action_controller6.rb +50 -0
  73. data/lib/appoptics_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
  74. data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +88 -0
  75. data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +27 -0
  76. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql.rb +43 -0
  77. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
  78. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +31 -0
  79. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +119 -0
  80. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +114 -0
  81. data/lib/appoptics_apm/frameworks/rails/inst/logger_formatters.rb +27 -0
  82. data/lib/appoptics_apm/frameworks/rails.rb +100 -0
  83. data/lib/appoptics_apm/frameworks/sinatra.rb +96 -0
  84. data/lib/appoptics_apm/inst/bunny-client.rb +148 -0
  85. data/lib/appoptics_apm/inst/bunny-consumer.rb +89 -0
  86. data/lib/appoptics_apm/inst/curb.rb +332 -0
  87. data/lib/appoptics_apm/inst/dalli.rb +85 -0
  88. data/lib/appoptics_apm/inst/delayed_job.rb +92 -0
  89. data/lib/appoptics_apm/inst/em-http-request.rb +101 -0
  90. data/lib/appoptics_apm/inst/excon.rb +125 -0
  91. data/lib/appoptics_apm/inst/faraday.rb +106 -0
  92. data/lib/appoptics_apm/inst/graphql.rb +240 -0
  93. data/lib/appoptics_apm/inst/grpc_client.rb +159 -0
  94. data/lib/appoptics_apm/inst/grpc_server.rb +120 -0
  95. data/lib/appoptics_apm/inst/http.rb +81 -0
  96. data/lib/appoptics_apm/inst/httpclient.rb +174 -0
  97. data/lib/appoptics_apm/inst/logger_formatter.rb +50 -0
  98. data/lib/appoptics_apm/inst/logging_log_event.rb +28 -0
  99. data/lib/appoptics_apm/inst/lumberjack_formatter.rb +13 -0
  100. data/lib/appoptics_apm/inst/memcached.rb +86 -0
  101. data/lib/appoptics_apm/inst/mongo.rb +246 -0
  102. data/lib/appoptics_apm/inst/mongo2.rb +225 -0
  103. data/lib/appoptics_apm/inst/moped.rb +466 -0
  104. data/lib/appoptics_apm/inst/rack.rb +182 -0
  105. data/lib/appoptics_apm/inst/rack_cache.rb +35 -0
  106. data/lib/appoptics_apm/inst/redis.rb +274 -0
  107. data/lib/appoptics_apm/inst/resque.rb +151 -0
  108. data/lib/appoptics_apm/inst/rest-client.rb +48 -0
  109. data/lib/appoptics_apm/inst/sequel.rb +178 -0
  110. data/lib/appoptics_apm/inst/sidekiq-client.rb +55 -0
  111. data/lib/appoptics_apm/inst/sidekiq-worker.rb +66 -0
  112. data/lib/appoptics_apm/inst/twitter-cassandra.rb +294 -0
  113. data/lib/appoptics_apm/inst/typhoeus.rb +108 -0
  114. data/lib/appoptics_apm/instrumentation.rb +22 -0
  115. data/lib/appoptics_apm/loading.rb +65 -0
  116. data/lib/appoptics_apm/logger.rb +14 -0
  117. data/lib/appoptics_apm/noop/README.md +9 -0
  118. data/lib/appoptics_apm/noop/context.rb +27 -0
  119. data/lib/appoptics_apm/noop/metadata.rb +25 -0
  120. data/lib/appoptics_apm/noop/profiling.rb +21 -0
  121. data/lib/appoptics_apm/oboe_init_options.rb +211 -0
  122. data/lib/appoptics_apm/ruby.rb +35 -0
  123. data/lib/appoptics_apm/sdk/current_trace.rb +77 -0
  124. data/lib/appoptics_apm/sdk/custom_metrics.rb +94 -0
  125. data/lib/appoptics_apm/sdk/logging.rb +37 -0
  126. data/lib/appoptics_apm/sdk/tracing.rb +434 -0
  127. data/lib/appoptics_apm/support/profiling.rb +18 -0
  128. data/lib/appoptics_apm/support/transaction_metrics.rb +67 -0
  129. data/lib/appoptics_apm/support/transaction_settings.rb +219 -0
  130. data/lib/appoptics_apm/support/x_trace_options.rb +110 -0
  131. data/lib/appoptics_apm/support_report.rb +119 -0
  132. data/lib/appoptics_apm/test.rb +95 -0
  133. data/lib/appoptics_apm/thread_local.rb +26 -0
  134. data/lib/appoptics_apm/util.rb +326 -0
  135. data/lib/appoptics_apm/version.rb +16 -0
  136. data/lib/appoptics_apm/xtrace.rb +115 -0
  137. data/lib/appoptics_apm.rb +77 -0
  138. data/lib/joboe_metal.rb +212 -0
  139. data/lib/oboe.rb +7 -0
  140. data/lib/oboe_metal.rb +172 -0
  141. data/lib/rails/generators/appoptics_apm/install_generator.rb +47 -0
  142. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +425 -0
  143. data/log/.keep +0 -0
  144. data/yardoc_frontpage.md +26 -0
  145. metadata +231 -0
@@ -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,106 @@
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
+
41
+ # this seems the safer condition than trying to identify the
42
+ # faraday version when adapter started to work without arg
43
+ # and handlers don't include the adapter anymore
44
+ if @builder.method(:adapter).parameters.find { |ele| ele[0] == :req }
45
+ kvs[:Middleware] = @builder.handlers
46
+ else
47
+ kvs[:Middleware] = [@builder.adapter] + @builder.handlers
48
+ end
49
+ kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:faraday][:collect_backtraces]
50
+
51
+ # Only send service KVs if we're not using an adapter
52
+ # Otherwise, the adapter instrumentation will send the service KVs
53
+ if remote_call
54
+ kvs.merge!(rsc_kvs(url, method, result))
55
+ unless blacklisted
56
+ xtrace_new = result.headers['X-Trace']
57
+ AppOpticsAPM::XTrace.continue_service_context(xtrace, xtrace_new)
58
+ end
59
+ end
60
+
61
+ result
62
+ rescue => e
63
+ AppOpticsAPM::API.log_exception(:faraday, e)
64
+ raise e
65
+ ensure
66
+ AppOpticsAPM::API.log_exit(:faraday, kvs)
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def url_blacklisted?
73
+ url = @url_prefix ? @url_prefix.to_s : @host
74
+ AppOpticsAPM::API.blacklisted?(url)
75
+ end
76
+
77
+ # This is only considered a remote service call if the middleware/adapter is not instrumented
78
+ def remote_call?
79
+ if @builder.method(:adapter).parameters.find { |ele| ele[0] == :req }
80
+ (@builder.handlers.map(&:name) & APPOPTICS_INSTR_ADAPTERS).count == 0
81
+ else
82
+ ((@builder.handlers.map(&:name) << @builder.adapter.name) & APPOPTICS_INSTR_ADAPTERS).count == 0
83
+ end
84
+ end
85
+
86
+ def rsc_kvs(_url, method, result)
87
+ kvs = { :Spec => 'rsc',
88
+ :IsService => 1,
89
+ :HTTPMethod => method.upcase,
90
+ :HTTPStatus => result.status, }
91
+ kvs[:Blacklisted] = true if url_blacklisted?
92
+ kvs[:RemoteURL] = result.env.to_hash[:url].to_s
93
+ kvs[:RemoteURL].split('?').first unless AppOpticsAPM::Config[:faraday][:log_args]
94
+
95
+ kvs
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ if defined?(Faraday) && AppOpticsAPM::Config[:faraday][:enabled]
102
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting faraday' if AppOpticsAPM::Config[:verbose]
103
+ AppOpticsAPM::Util.send_include(Faraday::Connection, AppOpticsAPM::Inst::FaradayConnection)
104
+ APPOPTICS_INSTR_ADAPTERS = ["Faraday::Adapter::NetHttp", "Faraday::Adapter::Excon", "Faraday::Adapter::Typhoeus"]
105
+ APPOPTICS_INSTR_ADAPTERS << "Faraday::Adapter::HTTPClient" if defined?Faraday::Adapter::HTTPClient
106
+ end
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # Copyright (c) 2019 SolarWinds, LLC.
5
+ # All rights reserved.
6
+ #++
7
+
8
+ # initial version of this instrumentation:
9
+ # https://github.com/librato/api/blob/master/graph/tracing/appoptics.rb
10
+ #
11
+
12
+ # TODO: make sure this stays up to date with
13
+ # ____ what is in the graphql gem and vice-versa
14
+
15
+
16
+ if defined?(GraphQL::Tracing) && !(AppOpticsAPM::Config[:graphql][:enabled] == false)
17
+ module GraphQL
18
+ module Tracing
19
+ # AppOpticsTracing in the graphql gem may be a different version than the
20
+ # one defined here, we want to use the newer one
21
+ redefine = true
22
+ this_version = Gem::Version.new('1.0.0')
23
+
24
+ if defined?(GraphQL::Tracing::AppOpticsTracing)
25
+ if this_version > GraphQL::Tracing::AppOpticsTracing.version
26
+ send(:remove_const, :AppOpticsTracing)
27
+ else
28
+ redefine = false
29
+ end
30
+ end
31
+
32
+ if redefine
33
+ #-----------------------------------------------------------------------------#
34
+ #----- this class is duplicated in the graphql gem ---------------------------#
35
+ #-----------------------------------------------------------------------------#
36
+ class AppOpticsTracing < GraphQL::Tracing::PlatformTracing
37
+ # These GraphQL events will show up as 'graphql.prep' spans
38
+ PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze
39
+ EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
40
+
41
+ # During auto-instrumentation this version of AppOpticsTracing is compared
42
+ # with the version provided in the graphql gem, so that the newer
43
+ # version of the class can be used
44
+
45
+ def self.version
46
+ Gem::Version.new('1.0.0')
47
+ end
48
+
49
+ self.platform_keys = {
50
+ 'lex' => 'lex',
51
+ 'parse' => 'parse',
52
+ 'validate' => 'validate',
53
+ 'analyze_query' => 'analyze_query',
54
+ 'analyze_multiplex' => 'analyze_multiplex',
55
+ 'execute_multiplex' => 'execute_multiplex',
56
+ 'execute_query' => 'execute_query',
57
+ 'execute_query_lazy' => 'execute_query_lazy'
58
+ }
59
+
60
+ def platform_trace(platform_key, _key, data)
61
+ return yield if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
62
+
63
+ layer = span_name(platform_key)
64
+ kvs = metadata(data, layer)
65
+ kvs[:Key] = platform_key if (PREP_KEYS + EXEC_KEYS).include?(platform_key)
66
+
67
+ transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute'
68
+
69
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
70
+ kvs.clear # we don't have to send them twice
71
+ yield
72
+ end
73
+ end
74
+
75
+ def platform_field_key(type, field)
76
+ "graphql.#{type.name}.#{field.name}"
77
+ end
78
+
79
+ private
80
+
81
+ def gql_config
82
+ ::AppOpticsAPM::Config[:graphql] ||= {}
83
+ end
84
+
85
+ def transaction_name(query)
86
+ return if gql_config[:transaction_name] == false ||
87
+ ::AppOpticsAPM::SDK.get_transaction_name
88
+
89
+ split_query = query.strip.split(/\W+/, 3)
90
+ split_query[0] = 'query' if split_query[0].empty?
91
+ name = "graphql.#{split_query[0..1].join('.')}"
92
+
93
+ ::AppOpticsAPM::SDK.set_transaction_name(name)
94
+ end
95
+
96
+ def multiplex_transaction_name(names)
97
+ return if gql_config[:transaction_name] == false ||
98
+ ::AppOpticsAPM::SDK.get_transaction_name
99
+
100
+ name = "graphql.multiplex.#{names.join('.')}"
101
+ name = "#{name[0..251]}..." if name.length > 254
102
+
103
+ ::AppOpticsAPM::SDK.set_transaction_name(name)
104
+ end
105
+
106
+ def span_name(key)
107
+ return 'graphql.prep' if PREP_KEYS.include?(key)
108
+ return 'graphql.execute' if EXEC_KEYS.include?(key)
109
+
110
+ key[/^graphql\./] ? key : "graphql.#{key}"
111
+ end
112
+
113
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
114
+ def metadata(data, layer)
115
+ data.keys.map do |key|
116
+ case key
117
+ when :context
118
+ graphql_context(data[key], layer)
119
+ when :query
120
+ graphql_query(data[key])
121
+ when :query_string
122
+ graphql_query_string(data[key])
123
+ when :multiplex
124
+ graphql_multiplex(data[key])
125
+ else
126
+ [key, data[key]]
127
+ end
128
+ end.flatten.each_slice(2).to_h.merge(Spec: 'graphql')
129
+ end
130
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
131
+
132
+ def graphql_context(context, layer)
133
+ context.errors && context.errors.each do |err|
134
+ AppOpticsAPM::API.log_exception(layer, err)
135
+ end
136
+
137
+ [[:Path, context.path.join('.')]]
138
+ end
139
+
140
+ def graphql_query(query)
141
+ return [] unless query
142
+
143
+ query_string = query.query_string
144
+ query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
145
+ query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
146
+
147
+ [[:InboundQuery, query_string],
148
+ [:Operation, query.selected_operation_name]]
149
+ end
150
+
151
+ def graphql_query_string(query_string)
152
+ query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
153
+ query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
154
+
155
+ [:InboundQuery, query_string]
156
+ end
157
+
158
+ def graphql_multiplex(data)
159
+ names = data.queries.map(&:operations).map(&:keys).flatten.compact
160
+ multiplex_transaction_name(names) if names.size > 1
161
+
162
+ [:Operations, names.join(', ')]
163
+ end
164
+
165
+ def sanitize(query)
166
+ return unless query
167
+
168
+ # remove arguments
169
+ query.gsub(/"[^"]*"/, '"?"') # strings
170
+ .gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats
171
+ .gsub(/\[[^\]]*\]/, '[?]') # arrays
172
+ end
173
+
174
+ def remove_comments(query)
175
+ return unless query
176
+
177
+ query.gsub(/#[^\n\r]*/, '')
178
+ end
179
+ end
180
+ #-----------------------------------------------------------------------------#
181
+ end
182
+ end
183
+ end
184
+
185
+ module AppOpticsAPM
186
+ module GraphQLSchemaPrepend
187
+ def use(plugin, **options)
188
+ # super unless GraphQL::Schema.plugins.find { |pl| pl[0].to_s == plugin.to_s }
189
+ super unless self.plugins.find { |pl| pl[0].to_s == plugin.to_s }
190
+
191
+ self.plugins
192
+ end
193
+
194
+ def inherited(subclass)
195
+ subclass.use(GraphQL::Tracing::AppOpticsTracing)
196
+ super
197
+ end
198
+ end
199
+
200
+ # rubocop:disable Style/RedundantSelf
201
+ module GraphQLErrorPrepend
202
+ def initialize(*args)
203
+ super
204
+ bt = AppOpticsAPM::API.backtrace(1)
205
+ set_backtrace(bt) unless self.backtrace
206
+ end
207
+ end
208
+ # rubocop:enable Style/RedundantSelf
209
+
210
+ # a different way of autoinstrumenting for graphql 1.7.4 - < 1.8.0
211
+ module GraphQLSchemaPrepend17
212
+ def initialize
213
+ super
214
+ unless tracers.find { |tr| tr.is_a? GraphQL::Tracing::AppOpticsTracing }
215
+ tracer = GraphQL::Tracing::AppOpticsTracing.new
216
+ tracers.push(tracer)
217
+ instrumenters[:field] << tracer
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ if Gem.loaded_specs['graphql'] && Gem.loaded_specs['graphql'].version >= Gem::Version.new('1.8.0')
224
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting GraphQL' if AppOpticsAPM::Config[:verbose]
225
+ if defined?(GraphQL::Schema)
226
+ GraphQL::Schema.singleton_class.prepend(AppOpticsAPM::GraphQLSchemaPrepend)
227
+ end
228
+
229
+ # rubocop:disable Style/IfUnlessModifier
230
+ if defined?(GraphQL::Error)
231
+ GraphQL::Error.prepend(AppOpticsAPM::GraphQLErrorPrepend)
232
+ end
233
+ # rubocop:enable Style/IfUnlessModifier
234
+ elsif Gem.loaded_specs['graphql'] && Gem.loaded_specs['graphql'].version >= Gem::Version.new('1.7.4')
235
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting GraphQL' if AppOpticsAPM::Config[:verbose]
236
+ if defined?(GraphQL::Schema)
237
+ GraphQL::Schema.prepend(AppOpticsAPM::GraphQLSchemaPrepend17)
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,159 @@
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
+
42
+ patch_receive_and_check_status # need to patch this so that log_exit can be called after the enum is consumed
43
+
44
+ response = server_streamer_without_appoptics(req, metadata: metadata)
45
+ block_given? ? response.each { |r| yield r } : response
46
+ rescue => e
47
+ # this check is needed because the exception may have been logged in patch_receive_and_check_status
48
+ unless e.instance_variable_get(:@exn_logged)
49
+ context_from_incoming
50
+ AppOpticsAPM::API.log_exception('grpc-client', e)
51
+ AppOpticsAPM::API.log_exit('grpc-client', exit_tags(@tags))
52
+ end
53
+ raise e
54
+ end
55
+
56
+ def bidi_streamer_with_appoptics(req, metadata: {}, &blk)
57
+ @tags = grpc_tags('BIDI_STREAMING', metadata[:method] || metadata_to_send[:method])
58
+ AppOpticsAPM::API.log_entry('grpc-client', @tags)
59
+ metadata['x-trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::Context.isValid
60
+
61
+ patch_set_input_stream_done
62
+
63
+ response = bidi_streamer_without_appoptics(req, metadata: metadata)
64
+ block_given? ? response.each { |r| yield r } : response
65
+ rescue => e
66
+ unless e.instance_variable_get(:@exn_logged)
67
+ context_from_incoming
68
+ AppOpticsAPM::API.log_exception('grpc-client', e)
69
+ AppOpticsAPM::API.log_exit('grpc-client', exit_tags(@tags))
70
+ end
71
+ raise e
72
+ end
73
+
74
+ private
75
+
76
+ def unary_response(req, type: , metadata: , without:)
77
+ tags = grpc_tags(type, metadata[:method] || metadata_to_send[:method])
78
+ AppOpticsAPM::SDK.trace('grpc-client', tags) do
79
+ metadata['x-trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::Context.isValid
80
+ begin
81
+ send(without, req, metadata: metadata)
82
+ ensure
83
+ exit_tags(tags)
84
+ context_from_incoming
85
+ end
86
+ end
87
+ end
88
+
89
+ def patch_receive_and_check_status
90
+ def self.receive_and_check_status # need to patch this so that log_exit can be called after the enum is consumed
91
+ super
92
+ context_from_incoming
93
+ rescue => e
94
+ context_from_incoming
95
+ AppOpticsAPM::API.log_exception('grpc-client', e)
96
+ raise e
97
+ ensure
98
+ AppOpticsAPM::API.log_exit('grpc-client', exit_tags(@tags))
99
+ end
100
+ end
101
+
102
+ def patch_set_input_stream_done
103
+ # need to patch this instance method so that log_exit can be called after the enum is consumed
104
+ def self.set_input_stream_done
105
+ return if status.nil?
106
+ context_from_incoming
107
+ if status.code > 0
108
+ AppOpticsAPM::API.log_exception('grpc-client', $!)
109
+ end
110
+ AppOpticsAPM::API.log_exit('grpc-client', exit_tags(@tags))
111
+ super
112
+ end
113
+ end
114
+
115
+ def context_from_incoming
116
+ if AppOpticsAPM::Context.isValid
117
+ xtrace ||= @call.trailing_metadata['x-trace'] if @call.trailing_metadata && @call.trailing_metadata['x-trace']
118
+ xtrace ||= @call.metadata['x-trace'] if @call.metadata && @call.metadata['x-trace']
119
+ AppOpticsAPM::Context.fromString(xtrace) if xtrace
120
+ end
121
+ end
122
+
123
+ def exit_tags(tags)
124
+ # we need to translate the status.code, it is not the status.details we want, they are not matching 1:1
125
+ tags['GRPCStatus'] ||= @call.status ? StatusCodes[@call.status.code].to_s : 'UNKNOWN'
126
+ tags['Backtrace'] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:grpc_client][:collect_backtraces]
127
+ tags
128
+ end
129
+ end
130
+
131
+ end
132
+ end
133
+
134
+ if defined?(GRPC) && AppOpticsAPM::Config[:grpc_client][:enabled]
135
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting GRPC' if AppOpticsAPM::Config[:verbose]
136
+
137
+ # Client side is instrumented in ActiveCall and ClientStub
138
+ AppOpticsAPM::Util.send_include(GRPC::ActiveCall, AppOpticsAPM::GRPC::ActiveCall)
139
+
140
+ GRPC_ClientStub_ops = [:request_response, :client_streamer, :server_streamer, :bidi_streamer]
141
+ module GRPC
142
+ class ClientStub
143
+ GRPC_ClientStub_ops.reject { |m| !method_defined?(m) }.each do |m|
144
+ define_method("#{m}_with_appoptics") do |method, req, marshal, unmarshal, deadline: nil,
145
+ return_op: false, parent: nil,
146
+ credentials: nil, metadata: {}, &blk|
147
+
148
+ metadata[:method] = method
149
+ return send("#{m}_without_appoptics", method, req, marshal, unmarshal, deadline: deadline,
150
+ return_op: return_op, parent: parent,
151
+ credentials: credentials, metadata: metadata, &blk)
152
+ end
153
+
154
+ AppOpticsAPM::Util.method_alias(GRPC::ClientStub, m)
155
+ end
156
+
157
+ end
158
+ end
159
+ end