appoptics_apm 4.8.4 → 4.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +7 -1
  3. data/.rubocop.yml +27 -6
  4. data/.travis.yml +24 -37
  5. data/.travis/bundle.sh +9 -0
  6. data/CONFIG.md +1 -1
  7. data/Gemfile +6 -6
  8. data/appoptics_apm.gemspec +6 -2
  9. data/examples/SDK/01_basic_tracing.rb +1 -1
  10. data/ext/oboe_metal/extconf.rb +6 -2
  11. data/ext/oboe_metal/noop/noop.c +2 -1
  12. data/ext/oboe_metal/src/VERSION +1 -1
  13. data/lib/appoptics_apm.rb +1 -3
  14. data/lib/appoptics_apm/api.rb +0 -1
  15. data/lib/appoptics_apm/api/logging.rb +6 -2
  16. data/lib/appoptics_apm/api/tracing.rb +4 -0
  17. data/lib/appoptics_apm/api/util.rb +5 -7
  18. data/lib/appoptics_apm/config.rb +16 -5
  19. data/lib/appoptics_apm/frameworks/grape.rb +3 -2
  20. data/lib/appoptics_apm/frameworks/padrino.rb +7 -37
  21. data/lib/appoptics_apm/frameworks/rails.rb +0 -1
  22. data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +1 -1
  23. data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +12 -25
  24. data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +1 -1
  25. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +1 -1
  26. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +1 -1
  27. data/lib/appoptics_apm/frameworks/sinatra.rb +4 -33
  28. data/lib/appoptics_apm/inst/curb.rb +6 -6
  29. data/lib/appoptics_apm/inst/faraday.rb +16 -4
  30. data/lib/appoptics_apm/inst/graphql.rb +240 -0
  31. data/lib/appoptics_apm/inst/grpc_client.rb +1 -1
  32. data/lib/appoptics_apm/inst/rack.rb +11 -11
  33. data/lib/appoptics_apm/oboe_init_options.rb +13 -3
  34. data/lib/appoptics_apm/sdk/custom_metrics.rb +2 -0
  35. data/lib/appoptics_apm/sdk/logging.rb +1 -1
  36. data/lib/appoptics_apm/sdk/tracing.rb +120 -2
  37. data/lib/appoptics_apm/support/transaction_metrics.rb +2 -1
  38. data/lib/appoptics_apm/support/transaction_settings.rb +40 -15
  39. data/lib/appoptics_apm/support/x_trace_options.rb +110 -0
  40. data/lib/appoptics_apm/version.rb +2 -2
  41. data/lib/appoptics_apm/xtrace.rb +7 -7
  42. data/lib/oboe_metal.rb +1 -1
  43. data/lib/rails/generators/appoptics_apm/install_generator.rb +23 -21
  44. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +68 -30
  45. metadata +40 -21
  46. data/Rakefile +0 -234
  47. data/build_gem.sh +0 -15
  48. data/build_gem_upload_to_packagecloud.sh +0 -15
  49. data/lib/appoptics_apm/api/profiling.rb +0 -203
  50. data/lib/appoptics_apm/frameworks/rails/inst/action_controller3.rb +0 -55
  51. data/lib/appoptics_apm/frameworks/rails/inst/action_view_30.rb +0 -50
  52. data/lib/appoptics_apm/legacy_method_profiling.rb +0 -90
  53. data/lib/appoptics_apm/method_profiling.rb +0 -33
  54. data/lib/oboe/README +0 -2
  55. data/lib/oboe/backward_compatibility.rb +0 -80
  56. data/lib/oboe/inst/rack.rb +0 -11
@@ -53,7 +53,8 @@ module AppOpticsAPM
53
53
  end
54
54
 
55
55
  def error_response_with_appoptics(error = {})
56
- status, headers, body = error_response_without_appoptics(error)
56
+ response = error_response_without_appoptics(error)
57
+ status, headers, _body = response.finish
57
58
 
58
59
  xtrace = AppOpticsAPM::Context.toString
59
60
 
@@ -77,7 +78,7 @@ module AppOpticsAPM
77
78
  end
78
79
  end
79
80
 
80
- [status, headers, body]
81
+ response
81
82
  end
82
83
  end
83
84
  end
@@ -38,47 +38,17 @@ module AppOpticsAPM
38
38
  end
39
39
 
40
40
  # TODO add test coverage, currently there are no tests for this
41
+ # ____ I'm not sure this gets ever called, Padrino uses Sinatra's render method
41
42
  def render_with_appoptics(engine, data = nil, options = {}, locals = {}, &block)
42
43
  if AppOpticsAPM.tracing?
43
44
  report_kvs = {}
44
45
 
45
- if data
46
- report_kvs[:engine] = engine
47
- report_kvs[:template] = data
48
- else
49
- report_kvs[:template] = engine
50
- end
51
-
52
- if AppOpticsAPM.tracing_layer_op?(:render)
53
- # For recursive calls to :render (for sub-partials and layouts),
54
- # use method profiling.
55
- begin
56
- report_kvs[:FunctionName] = :render
57
- report_kvs[:Class] = :Rendering
58
- report_kvs[:Module] = :Padrino
59
- report_kvs[:File] = __FILE__
60
- report_kvs[:LineNumber] = __LINE__
61
- rescue StandardError => e
62
- AppOpticsAPM.logger.debug "[appoptics_apm/padrino] #{e.message}"
63
- AppOpticsAPM.logger.debug e.backtrace.join(', ')
64
- end
65
-
66
- AppOpticsAPM::API.profile(report_kvs[:template], report_kvs, false) do
67
- render_without_appoptics(engine, data, options, locals, &block)
68
- end
69
- else
70
- # Fall back to the raw tracing API so we can pass KVs
71
- # back on exit (a limitation of the AppOpticsAPM::API.trace
72
- # block method) This removes the need for an info
73
- # event to send additonal KVs
74
- AppOpticsAPM::API.log_entry(:render, {}, :render)
46
+ report_kvs[:engine] = engine
47
+ report_kvs[:template] = data
75
48
 
76
- begin
77
- render_without_appoptics(engine, data, options, locals, &block)
78
- ensure
79
- report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:padrino][:collect_backtraces]
80
- AppOpticsAPM::API.log_exit(:render, report_kvs)
81
- end
49
+ AppOpticsAPM::SDK.trace(:padrino_render, report_kvs, :padrino_render) do
50
+ report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:padrino][:collect_backtraces]
51
+ render_without_appoptics(engine, data, options, locals, &block)
82
52
  end
83
53
  else
84
54
  render_without_appoptics(engine, data, options, locals, &block)
@@ -88,7 +58,7 @@ module AppOpticsAPM
88
58
  end
89
59
  end
90
60
 
91
- if defined?(Padrino) && AppopticsAPM::Config[:padrino][:enabled]
61
+ if defined?(Padrino) && AppOpticsAPM::Config[:padrino][:enabled]
92
62
  # This instrumentation is a superset of the Sinatra instrumentation similar
93
63
  # to how Padrino is a superset of Sinatra itself.
94
64
  AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting Padrino' if AppOpticsAPM::Config[:verbose]
@@ -48,7 +48,6 @@ module AppOpticsAPM
48
48
  # Load the Rails specific instrumentation
49
49
  require 'appoptics_apm/frameworks/rails/inst/action_controller'
50
50
  require 'appoptics_apm/frameworks/rails/inst/action_view'
51
- require 'appoptics_apm/frameworks/rails/inst/action_view_30'
52
51
  require 'appoptics_apm/frameworks/rails/inst/active_record'
53
52
  require 'appoptics_apm/frameworks/rails/inst/logger_formatters'
54
53
 
@@ -35,7 +35,7 @@ module AppOpticsAPM
35
35
  #
36
36
  # log_rails_error?
37
37
  #
38
- # Determins whether we should log a raised exception to the
38
+ # Determines whether we should log a raised exception to the
39
39
  # AppOptics dashboard. This is determined by whether the exception
40
40
  # has a rescue handler setup and the value of
41
41
  # AppOpticsAPM::Config[:report_rescued_errors]
@@ -1,53 +1,40 @@
1
1
  # Copyright (c) 2016 SolarWinds, LLC.
2
2
  # All rights reserved.
3
3
 
4
- if defined?(ActionView::Base) && AppOpticsAPM::Config[:action_view][:enabled] && Rails::VERSION::MAJOR < 6
4
+ if defined?(ActionView::Base) && AppOpticsAPM::Config[:action_view][:enabled]
5
5
 
6
- ##
7
- # ActionView Instrumentation is version dependent. ActionView 2.x is separate
8
- # and ActionView 3.0 is a special case.
9
- # Everything else goes here. (ActionView 3.1 - 4.0 as of this writing)
10
- #
11
- if (Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR > 0) || Rails::VERSION::MAJOR >= 4
6
+ if Rails::VERSION::MAJOR >= 4
12
7
 
13
8
  AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting actionview' if AppOpticsAPM::Config[:verbose]
14
9
 
15
10
  ActionView::PartialRenderer.class_eval do
16
11
  alias :render_partial_without_appoptics :render_partial
17
- def render_partial
12
+ def render_partial(*args)
18
13
  entry_kvs = {}
19
14
  begin
20
- name = AppOpticsAPM::Util.prettify(@options[:partial]) if @options.is_a?(Hash)
21
- entry_kvs[:FunctionName] = :render_partial
22
- entry_kvs[:Class] = :PartialRenderer
23
- entry_kvs[:Module] = :ActionView
24
- entry_kvs[:File] = __FILE__
25
- entry_kvs[:LineNumber] = __LINE__
15
+ entry_kvs[:Partial] = AppOpticsAPM::Util.prettify(@options[:partial]) if @options.is_a?(Hash)
26
16
  rescue => e
27
17
  AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" if AppOpticsAPM::Config[:verbose]
28
18
  end
29
19
 
30
- AppOpticsAPM::API.profile(name, entry_kvs, AppOpticsAPM::Config[:action_view][:collect_backtraces]) do
31
- render_partial_without_appoptics
20
+ AppOpticsAPM::SDK.trace('partial', entry_kvs) do
21
+ entry_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:action_view][:collect_backtraces]
22
+ render_partial_without_appoptics(*args)
32
23
  end
33
24
  end
34
25
 
35
26
  alias :render_collection_without_appoptics :render_collection
36
- def render_collection
27
+ def render_collection(*args)
37
28
  entry_kvs = {}
38
29
  begin
39
- name = AppOpticsAPM::Util.prettify(@path)
40
- entry_kvs[:FunctionName] = :render_collection
41
- entry_kvs[:Class] = :PartialRenderer
42
- entry_kvs[:Module] = :ActionView
43
- entry_kvs[:File] = __FILE__
44
- entry_kvs[:LineNumber] = __LINE__
30
+ entry_kvs[:Partial] = AppOpticsAPM::Util.prettify(@path)
45
31
  rescue => e
46
32
  AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" if AppOpticsAPM::Config[:verbose]
47
33
  end
48
34
 
49
- AppOpticsAPM::API.profile(name, entry_kvs, AppOpticsAPM::Config[:action_view][:collect_backtraces]) do
50
- render_collection_without_appoptics
35
+ AppOpticsAPM::SDK.trace('collection', entry_kvs) do
36
+ entry_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:action_view][:collect_backtraces]
37
+ render_collection_without_appoptics(*args)
51
38
  end
52
39
  end
53
40
 
@@ -17,7 +17,7 @@ if AppOpticsAPM::Config[:active_record][:enabled] && !defined?(JRUBY_VERSION) &&
17
17
 
18
18
  AppOpticsAPM::Inst::ConnectionAdapters::FlavorInitializers.mysql if adapter == 'mysql'
19
19
  AppOpticsAPM::Inst::ConnectionAdapters::FlavorInitializers.mysql2 if adapter == 'mysql2'
20
- AppOpticsAPM::Inst::ConnectionAdapters::FlavorInitializers.postgresql if adapter == 'postgresql'
20
+ AppOpticsAPM::Inst::ConnectionAdapters::FlavorInitializers.postgresql if adapter =~ /postgresql|postgis/i
21
21
 
22
22
  rescue StandardError => e
23
23
  AppOpticsAPM.logger.error "[appoptics_apm/error] AppOpticsAPM/ActiveRecord error: #{e.inspect}"
@@ -29,7 +29,7 @@ module AppOpticsAPM
29
29
  case adapter_name
30
30
  when /mysql/i
31
31
  opts[:Flavor] = 'mysql'
32
- when /postgres/i
32
+ when /^postgres|^postgis/i
33
33
  opts[:Flavor] = 'postgresql'
34
34
  end
35
35
  end
@@ -35,7 +35,7 @@ module AppOpticsAPM
35
35
  case adapter_name
36
36
  when /mysql/i
37
37
  opts[:Flavor] = 'mysql'
38
- when /postgres/i
38
+ when /^postgres|^postgis/i
39
39
  opts[:Flavor] = 'postgresql'
40
40
  end
41
41
  end
@@ -61,38 +61,9 @@ module AppOpticsAPM
61
61
  report_kvs[:engine] = engine
62
62
  report_kvs[:template] = data
63
63
 
64
- if AppOpticsAPM.tracing_layer_op?(:render)
65
- # For recursive calls to :render (for sub-partials and layouts),
66
- # use method profiling.
67
- begin
68
- name = data
69
- report_kvs[:FunctionName] = :render
70
- report_kvs[:Class] = :Templates
71
- report_kvs[:Module] = :'Sinatra::Templates'
72
- report_kvs[:File] = __FILE__
73
- report_kvs[:LineNumber] = __LINE__
74
- rescue StandardError => e
75
- AppOpticsAPM.logger.debug e.message
76
- AppOpticsAPM.logger.debug e.backtrace.join(', ')
77
- end
78
-
79
- AppOpticsAPM::API.profile(name, report_kvs, false) do
80
- render_without_appoptics(engine, data, options, locals, &block)
81
- end
82
-
83
- else
84
- # Fall back to the raw tracing API so we can pass KVs
85
- # back on exit (a limitation of the AppOpticsAPM::API.trace
86
- # block method) This removes the need for an info
87
- # event to send additonal KVs
88
- AppOpticsAPM::API.log_entry(:render, {}, :render)
89
-
90
- begin
91
- render_without_appoptics(engine, data, options, locals, &block)
92
- ensure
93
- report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:sinatra][:collect_backtraces]
94
- AppOpticsAPM::API.log_exit(:render, report_kvs, :render)
95
- end
64
+ AppOpticsAPM::SDK.trace(:sinatra_render, report_kvs, :sinatra_render) do
65
+ report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:sinatra][:collect_backtraces]
66
+ render_without_appoptics(engine, data, options, locals, &block)
96
67
  end
97
68
  else
98
69
  render_without_appoptics(engine, data, options, locals, &block)
@@ -102,7 +73,7 @@ module AppOpticsAPM
102
73
  end
103
74
  end
104
75
 
105
- if defined?(Sinatra) && AppopticsAPM::Config[:sinatra][:enabled]
76
+ if defined?(Sinatra) && AppOpticsAPM::Config[:sinatra][:enabled]
106
77
  require 'appoptics_apm/inst/rack'
107
78
 
108
79
  AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting Sinatra' if AppOpticsAPM::Config[:verbose]
@@ -46,12 +46,12 @@ module AppOpticsAPM
46
46
  end
47
47
 
48
48
  ##
49
- # profile_curb_method
49
+ # trace_curb_method
50
50
  #
51
51
  # An agnostic method that will profile any Curl::Easy method (and optional args and block)
52
52
  # that you throw at it.
53
53
  #
54
- def profile_curb_method(kvs, method, args, &block)
54
+ def trace_curb_method(kvs, method, args, &block)
55
55
  # If we're not tracing, just do a fast return.
56
56
  unless AppOpticsAPM.tracing?
57
57
  unless AppOpticsAPM::API.blacklisted?(URI(url).hostname)
@@ -129,7 +129,7 @@ module AppOpticsAPM
129
129
  kvs = {}
130
130
  kvs[:HTTPMethod] = :POST
131
131
 
132
- profile_curb_method(kvs, :http_post_without_appoptics, args, &block)
132
+ trace_curb_method(kvs, :http_post_without_appoptics, args, &block)
133
133
  end
134
134
 
135
135
  ##
@@ -149,7 +149,7 @@ module AppOpticsAPM
149
149
  kvs = {}
150
150
  kvs[:HTTPMethod] = :PUT
151
151
 
152
- profile_curb_method(kvs, :http_put_without_appoptics, args, &block)
152
+ trace_curb_method(kvs, :http_put_without_appoptics, args, &block)
153
153
  end
154
154
 
155
155
  ##
@@ -178,7 +178,7 @@ module AppOpticsAPM
178
178
  kvs[:HTTPMethod] = :GET
179
179
  end
180
180
 
181
- profile_curb_method(kvs, :perform_without_appoptics, nil, &block)
181
+ trace_curb_method(kvs, :perform_without_appoptics, nil, &block)
182
182
  end
183
183
 
184
184
  ##
@@ -198,7 +198,7 @@ module AppOpticsAPM
198
198
  kvs = {}
199
199
  kvs[:HTTPMethod] = verb
200
200
 
201
- profile_curb_method(kvs, :http_without_appoptics, [verb], &block)
201
+ trace_curb_method(kvs, :http_without_appoptics, [verb], &block)
202
202
  end
203
203
  end
204
204
 
@@ -37,7 +37,15 @@ module AppOpticsAPM
37
37
  AppOpticsAPM::XTrace.continue_service_context(xtrace, xtrace_new)
38
38
  end
39
39
  kvs = {}
40
- kvs[:Middleware] = @builder.handlers
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
41
49
  kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:faraday][:collect_backtraces]
42
50
 
43
51
  # Only send service KVs if we're not using an adapter
@@ -68,16 +76,20 @@ module AppOpticsAPM
68
76
 
69
77
  # This is only considered a remote service call if the middleware/adapter is not instrumented
70
78
  def remote_call?
71
- (@builder.handlers.map(&:name) & APPOPTICS_INSTR_ADAPTERS).count == 0
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
72
84
  end
73
85
 
74
- def rsc_kvs(url, method, result)
86
+ def rsc_kvs(_url, method, result)
75
87
  kvs = { :Spec => 'rsc',
76
88
  :IsService => 1,
77
89
  :HTTPMethod => method.upcase,
78
90
  :HTTPStatus => result.status, }
79
91
  kvs[:Blacklisted] = true if url_blacklisted?
80
- kvs[:RemoteURL] = result.to_hash[:url].to_s
92
+ kvs[:RemoteURL] = result.env.to_hash[:url].to_s
81
93
  kvs[:RemoteURL].split('?').first unless AppOpticsAPM::Config[:faraday][:log_args]
82
94
 
83
95
  kvs
@@ -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