appoptics_apm 4.10.1 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +27 -6
  4. data/.travis.yml +22 -43
  5. data/.travis/bundle.sh +9 -0
  6. data/Gemfile +31 -22
  7. data/app/assets/config/manifest.js +1 -0
  8. data/appoptics_apm.gemspec +13 -12
  9. data/examples/sdk_examples.rb +142 -0
  10. data/ext/.vscode/launch.json +20 -0
  11. data/ext/oboe_metal/extconf.rb +6 -2
  12. data/ext/oboe_metal/src/VERSION +1 -1
  13. data/ext/oboe_metal/src/function_profiler.hpp +160 -0
  14. data/lib/appoptics_apm/api/logging.rb +6 -2
  15. data/lib/appoptics_apm/api/metrics.rb +3 -1
  16. data/lib/appoptics_apm/api/util.rb +5 -7
  17. data/lib/appoptics_apm/config.rb +10 -9
  18. data/lib/appoptics_apm/frameworks/grape.rb +3 -2
  19. data/lib/appoptics_apm/frameworks/padrino.rb +1 -1
  20. data/lib/appoptics_apm/frameworks/rails.rb +7 -1
  21. data/lib/appoptics_apm/frameworks/sinatra.rb +1 -1
  22. data/lib/appoptics_apm/inst/graphql.rb +240 -0
  23. data/lib/appoptics_apm/inst/grpc_client.rb +1 -1
  24. data/lib/appoptics_apm/inst/rack_cache.rb +35 -0
  25. data/lib/appoptics_apm/sdk/custom_metrics.rb +2 -2
  26. data/lib/appoptics_apm/sdk/logging.rb +1 -1
  27. data/lib/appoptics_apm/sdk/tracing.rb +4 -4
  28. data/lib/appoptics_apm/support/transaction_metrics.rb +1 -1
  29. data/lib/appoptics_apm/test.rb +2 -1
  30. data/lib/appoptics_apm/version.rb +2 -2
  31. data/lib/oboe_metal.rb +1 -1
  32. data/lib/rails/generators/appoptics_apm/install_generator.rb +23 -21
  33. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +43 -19
  34. data/lib/rb_appoptics_apm.so +0 -0
  35. metadata +17 -68
  36. data/examples/SDK/01_basic_tracing.rb +0 -68
  37. data/examples/carrying_context.rb +0 -220
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright (c) 2016 SolarWinds, LLC.
2
4
  # All rights reserved.
3
5
 
@@ -20,7 +22,7 @@ ao_include = File.join(ext_dir, 'src')
20
22
  version = File.read(File.join(ao_include, 'VERSION')).chomp
21
23
  if ENV['APPOPTICS_FROM_S3'].to_s.downcase == 'true'
22
24
  ao_path = File.join('https://s3-us-west-2.amazonaws.com/rc-files-t2/c-lib/', version)
23
- puts "Fetching c-lib from S3"
25
+ puts 'Fetching c-lib from S3'
24
26
  else
25
27
  ao_path = File.join('https://files.appoptics.com/c-lib', version)
26
28
  end
@@ -29,7 +31,7 @@ ao_arch = 'x86_64'
29
31
  if File.exist?('/etc/alpine-release')
30
32
  version = open('/etc/alpine-release').read.chomp
31
33
  ao_arch =
32
- if Gem::Version.new(version) < Gem::Version.new('3.9')
34
+ if Gem::Version.new(version) < Gem::Version.new('3.9')
33
35
  'alpine-libressl-x86_64'
34
36
  else # openssl
35
37
  'alpine-x86_64'
@@ -46,6 +48,8 @@ success = false
46
48
  while retries > 0
47
49
  begin
48
50
  # download
51
+ # TODO warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open
52
+ # ____ URI.open is not supported in 2.4.5, let's use open until we can deprecate 2.4.5
49
53
  download = open(ao_item, 'rb')
50
54
  IO.copy_stream(download, clib)
51
55
 
@@ -1 +1 @@
1
- 7.0.0
1
+ 10.0.0
@@ -0,0 +1,160 @@
1
+ // function_profiler C++11 per-thread function profling
2
+ // https://git.the-pluc.net/function_profiler.git/
3
+ // Version 0.3
4
+ //
5
+ // Copyright (C) 2015, 2016 Pierre-Luc Perrier <pluc@the-pluc.net>
6
+ //
7
+ // Licensed under the Apache License, Version 2.0 (the "License");
8
+ // you may not use this file except in compliance with the License.
9
+ // You may obtain a copy of the License at
10
+ //
11
+ // http://www.apache.org/licenses/LICENSE-2.0
12
+ //
13
+ // Unless required by applicable law or agreed to in writing, software
14
+ // distributed under the License is distributed on an "AS IS" BASIS,
15
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ // See the License for the specific language governing permissions and
17
+ // limitations under the License.
18
+
19
+ #ifndef function_profiler_h
20
+ #define function_profiler_h
21
+
22
+ #ifdef FP_ENABLE
23
+
24
+ #include <boost/chrono.hpp>
25
+
26
+ #include <cstdio> // std::printf
27
+ #include <ios> // std::hex
28
+ #include <string> // std::string
29
+ #include <thread> // std::this_thread::get_id
30
+ #include <iostream>
31
+ #include <iomanip>
32
+
33
+ namespace fp {
34
+
35
+ // Main structure used to collect data
36
+ struct collector {
37
+ // Clocks
38
+ using thread_clock = boost::chrono::thread_clock;
39
+ using thread_time_point = thread_clock::time_point;
40
+ using thread_accumulator = boost::chrono::duration<uint_least64_t, thread_clock::period>;
41
+
42
+ using steady_clock = boost::chrono::steady_clock;
43
+ using steady_time_point = steady_clock::time_point;
44
+ using steady_accumulator = boost::chrono::duration<uint_least64_t, steady_clock::period>;
45
+
46
+ // The duration we use for the report
47
+ using report_duration = boost::chrono::duration<double, boost::milli>;
48
+
49
+ collector() = delete;
50
+ collector(const std::string &name)
51
+ : m_thread_last{thread_clock::now()},
52
+ m_steady_last{steady_clock::now()},
53
+ m_count{0},
54
+ m_thread_accumulator{0},
55
+ m_steady_accumulator{0},
56
+ m_last_report{steady_clock::now()}
57
+ {
58
+ std::ostringstream os;
59
+ // os << "[FP] " << std::hex << std::this_thread::get_id() << " " << name << " #%-7lu %8.5F %8.5F %11.4F %11.4F\n";
60
+ os << std::setfill (' ') << std::setw(22) << name << " " << std::hex << std::this_thread::get_id() << "\t#%-7lu\t%4.5F\t%4.5F\t%6.4F\t%6.4F\n";
61
+ m_fmt = os.str();
62
+ }
63
+
64
+ // We also report upon destruction. Note: this is probably a bad
65
+ // idea.
66
+ ~collector() noexcept
67
+ {
68
+ report();
69
+ }
70
+
71
+ inline void start() noexcept
72
+ {
73
+ m_thread_last = thread_clock::now();
74
+ m_steady_last = steady_clock::now();
75
+ }
76
+
77
+ inline void stop()
78
+ {
79
+ ++m_count;
80
+
81
+ {
82
+ const auto now = thread_clock::now();
83
+ m_thread_accumulator += now - m_thread_last;
84
+ m_thread_last = std::move(now);
85
+ }
86
+ {
87
+ const auto now = steady_clock::now();
88
+ m_steady_accumulator += now - m_steady_last;
89
+ m_steady_last = std::move(now);
90
+ }
91
+
92
+ // Check if we need to report current stats
93
+ if (m_steady_last - m_last_report > report_interval) {
94
+ m_last_report = m_steady_last;
95
+ report();
96
+ }
97
+ }
98
+
99
+ private:
100
+ inline void report() const
101
+ {
102
+ if (m_count == 0) return;
103
+ const auto thread_ms = report_duration{m_thread_accumulator}.count();
104
+ const auto steady_ms = report_duration{m_steady_accumulator}.count();
105
+ std::printf(m_fmt.c_str(), m_count, steady_ms / m_count, thread_ms / m_count, steady_ms, thread_ms);
106
+ }
107
+
108
+ private:
109
+ static constexpr auto report_interval = boost::chrono::seconds{1};
110
+
111
+ thread_time_point m_thread_last; // Last thread clock time point
112
+ steady_time_point m_steady_last; // Last steady clock time point
113
+ uint_least64_t m_count; // Number of samples
114
+ thread_accumulator m_thread_accumulator; // Total thread clock duration
115
+ steady_accumulator m_steady_accumulator; // Total steady clock duration
116
+ steady_time_point m_last_report; // Last report
117
+ std::string m_fmt; // Format used for reporting
118
+ };
119
+
120
+ // RAII structure responsible to update a collector
121
+ struct scoped_profiler {
122
+ public:
123
+ scoped_profiler(collector &collector_) : m_collector{collector_}
124
+ {
125
+ m_collector.start();
126
+ }
127
+
128
+ ~scoped_profiler()
129
+ {
130
+ m_collector.stop();
131
+ }
132
+
133
+ scoped_profiler() = delete;
134
+ scoped_profiler(const scoped_profiler &) = delete;
135
+ scoped_profiler(scoped_profiler &&) = delete;
136
+
137
+ scoped_profiler &operator=(const scoped_profiler &) = delete;
138
+ scoped_profiler &operator=(scoped_profiler &&) = delete;
139
+
140
+ private:
141
+ collector &m_collector;
142
+ };
143
+
144
+ // constexpr boost::chrono::seconds collector::report_interval;
145
+
146
+ } // namespace fp
147
+
148
+ // Note: __func__ is not a preprocessor macro, it's a 'function-local
149
+ // predefined variable' (char *), hence we don't expand it.
150
+ #define PROFILE_FUNCTION() \
151
+ thread_local fp::collector fp_info_fp{__func__}; \
152
+ fp::scoped_profiler fp_scoped_profiler_fp{fp_info_fp};
153
+
154
+ #else // !FB_ENABLE
155
+
156
+ #define PROFILE_FUNCTION() static_cast<void>(0);
157
+
158
+ #endif // FB_ENABLE
159
+
160
+ #endif // function_profiler_h
@@ -55,7 +55,7 @@ module AppOpticsAPM
55
55
  # ==== Arguments
56
56
  #
57
57
  # * +layer+ - The layer the reported event belongs to
58
- # * +exception+ - The exception to report
58
+ # * +exception+ - The exception to report, responds to :message and :backtrace(optional)
59
59
  # * +opts+ - Custom params if you want to log extra information
60
60
  #
61
61
  # ==== Example
@@ -80,7 +80,10 @@ module AppOpticsAPM
80
80
  opts.merge!(:Spec => 'error',
81
81
  :ErrorClass => exception.class.name,
82
82
  :ErrorMsg => exception.message)
83
- opts.merge!(:Backtrace => exception.backtrace.join("\r\n")) if exception.backtrace
83
+
84
+ if exception.respond_to?(:backtrace) && exception.backtrace
85
+ opts.merge!(:Backtrace => exception.backtrace.join("\r\n"))
86
+ end
84
87
 
85
88
  exception.instance_variable_set(:@exn_logged, true)
86
89
  log(layer, :error, opts)
@@ -205,6 +208,7 @@ module AppOpticsAPM
205
208
  def log_info(layer, opts = {})
206
209
  return AppOpticsAPM::Context.toString unless AppOpticsAPM.tracing?
207
210
 
211
+ opts[:Spec] = 'info'
208
212
  log_event(layer, :info, AppOpticsAPM::Context.createEvent, opts)
209
213
  end
210
214
 
@@ -45,6 +45,8 @@ module AppOpticsAPM
45
45
  AppOpticsAPM.transaction_name
46
46
  elsif kvs['Controller'] && kvs['Action']
47
47
  [kvs['Controller'], kvs['Action']].join('.')
48
+ elsif kvs[:Controller] && kvs[:Action]
49
+ [kvs[:Controller], kvs[:Action]].join('.')
48
50
  else
49
51
  "custom-#{span}"
50
52
  end
@@ -52,4 +54,4 @@ module AppOpticsAPM
52
54
 
53
55
  end
54
56
  end
55
- end
57
+ end
@@ -24,16 +24,14 @@ module AppOpticsAPM
24
24
 
25
25
  # Internal: Get the current backtrace.
26
26
  #
27
- # ignore - Number of frames to ignore at the top of the backtrace. Use
28
- # when you know how many layers deep in the key call is being
29
- # made.
27
+ # from - int, from position in array of backtraces
28
+ # to - int, end position in array of backtraces, can be negative to count from the end
30
29
  #
31
30
  # Returns a string with each frame of the backtrace separated by '\r\n'.
32
31
  #
33
- def backtrace(ignore = 0)
32
+ def backtrace(from = 0, to = -1)
34
33
  bt = Kernel.caller
35
- bt.slice!(0, ignore)
36
- trim_backtrace(bt).join("\r\n")
34
+ trim_backtrace(bt[from..to]).join("\r\n")
37
35
  end
38
36
 
39
37
  # Internal: Trim a backtrace to a manageable size
@@ -115,7 +113,7 @@ module AppOpticsAPM
115
113
  end
116
114
 
117
115
  def xtrace_v2?(xtr)
118
- return xtr && xtr.start_with?('2B')
116
+ xtr && xtr.start_with?('2B')
119
117
  end
120
118
  end
121
119
  end
@@ -16,7 +16,7 @@ module AppOpticsAPM
16
16
  @@instrumentation = [:action_controller, :action_controller_api, :action_view,
17
17
  :active_record, :bunnyclient, :bunnyconsumer, :cassandra, :curb,
18
18
  :dalli, :delayed_jobclient, :delayed_jobworker,
19
- :excon, :faraday, :grpc_client, :grpc_server, :grape,
19
+ :excon, :faraday, :graphql, :grpc_client, :grpc_server, :grape,
20
20
  :httpclient, :nethttp, :memcached, :mongo, :moped, :padrino, :rack, :redis,
21
21
  :resqueclient, :resqueworker, :rest_client,
22
22
  :sequel, :sidekiqclient, :sidekiqworker, :sinatra, :typhoeus]
@@ -65,15 +65,16 @@ module AppOpticsAPM
65
65
  config_file = File.join(Dir.pwd, 'appoptics_apm_config.rb')
66
66
  config_files << config_file if File.exist?(config_file)
67
67
 
68
- return if config_files.empty? # we use the defaults from the template in this case
69
-
70
- if config_files.size > 1
71
- AppOpticsAPM.logger.warn [
72
- '[appoptics_apm/config] Multiple configuration files configured, using the first one listed: ',
73
- config_files.join(', ')
74
- ].join(' ')
68
+ unless config_files.empty? # we use the defaults from the template if there are no config files
69
+ if config_files.size > 1
70
+ AppOpticsAPM.logger.warn [
71
+ '[appoptics_apm/config] Multiple configuration files configured, using the first one listed: ',
72
+ config_files.join(', ')
73
+ ].join(' ')
74
+ end
75
+ load(config_files[0])
75
76
  end
76
- load(config_files[0])
77
+
77
78
  # sets AppOpticsAPM::Config[:debug_level], AppOpticsAPM.logger.level
78
79
  set_log_level
79
80
 
@@ -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
@@ -58,7 +58,7 @@ module AppOpticsAPM
58
58
  end
59
59
  end
60
60
 
61
- if defined?(Padrino) && AppopticsAPM::Config[:padrino][:enabled]
61
+ if defined?(Padrino) && AppOpticsAPM::Config[:padrino][:enabled]
62
62
  # This instrumentation is a superset of the Sinatra instrumentation similar
63
63
  # to how Padrino is a superset of Sinatra itself.
64
64
  AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting Padrino' if AppOpticsAPM::Config[:verbose]
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) 2016 SolarWinds, LLC.
2
2
  # All rights reserved.
3
3
 
4
+ require_relative '../../../lib/appoptics_apm/inst/logger_formatter'
5
+
4
6
  module AppOpticsAPM
5
7
  module Rails
6
8
  module Helpers
@@ -75,6 +77,10 @@ if defined?(::Rails)
75
77
  AppOpticsAPM::Rails.include_helpers
76
78
  end
77
79
 
80
+ initializer 'appoptics_apm.controller', before: 'wicked_pdf.register' do
81
+ AppOpticsAPM::Rails.load_instrumentation
82
+ end
83
+
78
84
  initializer 'appoptics_apm.rack' do |app|
79
85
  AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting rack' if AppOpticsAPM::Config[:verbose]
80
86
  app.config.middleware.insert 0, AppOpticsAPM::Rack
@@ -84,7 +90,7 @@ if defined?(::Rails)
84
90
  AppOpticsAPM.logger = ::Rails.logger if ::Rails.logger && !ENV.key?('APPOPTICS_GEM_TEST')
85
91
 
86
92
  AppOpticsAPM::Inst.load_instrumentation
87
- AppOpticsAPM::Rails.load_instrumentation
93
+ # AppOpticsAPM::Rails.load_instrumentation
88
94
 
89
95
  # Report __Init after fork when in Heroku
90
96
  AppOpticsAPM::API.report_init unless AppOpticsAPM.heroku?
@@ -73,7 +73,7 @@ module AppOpticsAPM
73
73
  end
74
74
  end
75
75
 
76
- if defined?(Sinatra) && AppopticsAPM::Config[:sinatra][:enabled]
76
+ if defined?(Sinatra) && AppOpticsAPM::Config[:sinatra][:enabled]
77
77
  require 'appoptics_apm/inst/rack'
78
78
 
79
79
  AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting Sinatra' if AppOpticsAPM::Config[:verbose]
@@ -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