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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +27 -6
- data/.travis.yml +22 -43
- data/.travis/bundle.sh +9 -0
- data/Gemfile +31 -22
- data/app/assets/config/manifest.js +1 -0
- data/appoptics_apm.gemspec +13 -12
- data/examples/sdk_examples.rb +142 -0
- data/ext/.vscode/launch.json +20 -0
- data/ext/oboe_metal/extconf.rb +6 -2
- data/ext/oboe_metal/src/VERSION +1 -1
- data/ext/oboe_metal/src/function_profiler.hpp +160 -0
- data/lib/appoptics_apm/api/logging.rb +6 -2
- data/lib/appoptics_apm/api/metrics.rb +3 -1
- data/lib/appoptics_apm/api/util.rb +5 -7
- data/lib/appoptics_apm/config.rb +10 -9
- data/lib/appoptics_apm/frameworks/grape.rb +3 -2
- data/lib/appoptics_apm/frameworks/padrino.rb +1 -1
- data/lib/appoptics_apm/frameworks/rails.rb +7 -1
- data/lib/appoptics_apm/frameworks/sinatra.rb +1 -1
- data/lib/appoptics_apm/inst/graphql.rb +240 -0
- data/lib/appoptics_apm/inst/grpc_client.rb +1 -1
- data/lib/appoptics_apm/inst/rack_cache.rb +35 -0
- data/lib/appoptics_apm/sdk/custom_metrics.rb +2 -2
- data/lib/appoptics_apm/sdk/logging.rb +1 -1
- data/lib/appoptics_apm/sdk/tracing.rb +4 -4
- data/lib/appoptics_apm/support/transaction_metrics.rb +1 -1
- data/lib/appoptics_apm/test.rb +2 -1
- data/lib/appoptics_apm/version.rb +2 -2
- data/lib/oboe_metal.rb +1 -1
- data/lib/rails/generators/appoptics_apm/install_generator.rb +23 -21
- data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +43 -19
- data/lib/rb_appoptics_apm.so +0 -0
- metadata +17 -68
- data/examples/SDK/01_basic_tracing.rb +0 -68
- data/examples/carrying_context.rb +0 -220
data/ext/oboe_metal/extconf.rb
CHANGED
@@ -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
|
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) <
|
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
|
|
data/ext/oboe_metal/src/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
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
|
-
|
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
|
-
#
|
28
|
-
#
|
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(
|
32
|
+
def backtrace(from = 0, to = -1)
|
34
33
|
bt = Kernel.caller
|
35
|
-
bt.
|
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
|
-
|
116
|
+
xtr && xtr.start_with?('2B')
|
119
117
|
end
|
120
118
|
end
|
121
119
|
end
|
data/lib/appoptics_apm/config.rb
CHANGED
@@ -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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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) &&
|
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) &&
|
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
|