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.
- checksums.yaml +7 -0
- data/.dockerignore +5 -0
- data/.github/ISSUE_TEMPLATE/bug-or-feature-request.md +16 -0
- data/.github/workflows/build_and_release_gem.yml +103 -0
- data/.github/workflows/build_for_packagecloud.yml +70 -0
- data/.github/workflows/docker-images.yml +47 -0
- data/.github/workflows/run_cpluplus_tests.yml +73 -0
- data/.github/workflows/run_tests.yml +168 -0
- data/.github/workflows/scripts/test_install.rb +23 -0
- data/.github/workflows/swig/swig-v4.0.2.tar.gz +0 -0
- data/.github/workflows/test_on_4_linux.yml +159 -0
- data/.gitignore +36 -0
- data/.rubocop.yml +29 -0
- data/.travis.yml +130 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +769 -0
- data/CONFIG.md +33 -0
- data/Gemfile +14 -0
- data/LICENSE +202 -0
- data/README.md +393 -0
- data/appoptics_apm.gemspec +70 -0
- data/bin/appoptics_apm_config +15 -0
- data/examples/prepend.rb +13 -0
- data/examples/sdk_examples.rb +158 -0
- data/ext/oboe_metal/README.md +69 -0
- data/ext/oboe_metal/extconf.rb +151 -0
- data/ext/oboe_metal/lib/.keep +0 -0
- data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.0.0.0.sha256 +1 -0
- data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.0.0.0.sha256 +1 -0
- data/ext/oboe_metal/noop/noop.c +8 -0
- data/ext/oboe_metal/src/README.md +6 -0
- data/ext/oboe_metal/src/VERSION +2 -0
- data/ext/oboe_metal/src/bson/bson.h +220 -0
- data/ext/oboe_metal/src/bson/platform_hacks.h +91 -0
- data/ext/oboe_metal/src/frames.cc +246 -0
- data/ext/oboe_metal/src/frames.h +40 -0
- data/ext/oboe_metal/src/init_appoptics_apm.cc +21 -0
- data/ext/oboe_metal/src/logging.cc +95 -0
- data/ext/oboe_metal/src/logging.h +35 -0
- data/ext/oboe_metal/src/oboe.h +1156 -0
- data/ext/oboe_metal/src/oboe_api.cpp +652 -0
- data/ext/oboe_metal/src/oboe_api.hpp +431 -0
- data/ext/oboe_metal/src/oboe_debug.h +59 -0
- data/ext/oboe_metal/src/oboe_swig_wrap.cc +7329 -0
- data/ext/oboe_metal/src/profiling.cc +435 -0
- data/ext/oboe_metal/src/profiling.h +78 -0
- data/ext/oboe_metal/test/CMakeLists.txt +53 -0
- data/ext/oboe_metal/test/FindGMock.cmake +43 -0
- data/ext/oboe_metal/test/README.md +56 -0
- data/ext/oboe_metal/test/frames_test.cc +164 -0
- data/ext/oboe_metal/test/profiling_test.cc +93 -0
- data/ext/oboe_metal/test/ruby_inc_dir.rb +8 -0
- data/ext/oboe_metal/test/ruby_prefix.rb +8 -0
- data/ext/oboe_metal/test/ruby_test_helper.rb +67 -0
- data/ext/oboe_metal/test/test.h +11 -0
- data/ext/oboe_metal/test/test_main.cc +32 -0
- data/init.rb +4 -0
- data/lib/appoptics_apm/api/layerinit.rb +41 -0
- data/lib/appoptics_apm/api/logging.rb +381 -0
- data/lib/appoptics_apm/api/memcache.rb +37 -0
- data/lib/appoptics_apm/api/metrics.rb +63 -0
- data/lib/appoptics_apm/api/tracing.rb +57 -0
- data/lib/appoptics_apm/api/util.rb +120 -0
- data/lib/appoptics_apm/api.rb +21 -0
- data/lib/appoptics_apm/base.rb +231 -0
- data/lib/appoptics_apm/config.rb +299 -0
- data/lib/appoptics_apm/frameworks/grape.rb +98 -0
- data/lib/appoptics_apm/frameworks/padrino.rb +78 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +104 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller4.rb +48 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller5.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller6.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +88 -0
- data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +27 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql.rb +43 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +31 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +119 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +114 -0
- data/lib/appoptics_apm/frameworks/rails/inst/logger_formatters.rb +27 -0
- data/lib/appoptics_apm/frameworks/rails.rb +100 -0
- data/lib/appoptics_apm/frameworks/sinatra.rb +96 -0
- data/lib/appoptics_apm/inst/bunny-client.rb +148 -0
- data/lib/appoptics_apm/inst/bunny-consumer.rb +89 -0
- data/lib/appoptics_apm/inst/curb.rb +332 -0
- data/lib/appoptics_apm/inst/dalli.rb +85 -0
- data/lib/appoptics_apm/inst/delayed_job.rb +92 -0
- data/lib/appoptics_apm/inst/em-http-request.rb +101 -0
- data/lib/appoptics_apm/inst/excon.rb +125 -0
- data/lib/appoptics_apm/inst/faraday.rb +106 -0
- data/lib/appoptics_apm/inst/graphql.rb +240 -0
- data/lib/appoptics_apm/inst/grpc_client.rb +159 -0
- data/lib/appoptics_apm/inst/grpc_server.rb +120 -0
- data/lib/appoptics_apm/inst/http.rb +81 -0
- data/lib/appoptics_apm/inst/httpclient.rb +174 -0
- data/lib/appoptics_apm/inst/logger_formatter.rb +50 -0
- data/lib/appoptics_apm/inst/logging_log_event.rb +28 -0
- data/lib/appoptics_apm/inst/lumberjack_formatter.rb +13 -0
- data/lib/appoptics_apm/inst/memcached.rb +86 -0
- data/lib/appoptics_apm/inst/mongo.rb +246 -0
- data/lib/appoptics_apm/inst/mongo2.rb +225 -0
- data/lib/appoptics_apm/inst/moped.rb +466 -0
- data/lib/appoptics_apm/inst/rack.rb +182 -0
- data/lib/appoptics_apm/inst/rack_cache.rb +35 -0
- data/lib/appoptics_apm/inst/redis.rb +274 -0
- data/lib/appoptics_apm/inst/resque.rb +151 -0
- data/lib/appoptics_apm/inst/rest-client.rb +48 -0
- data/lib/appoptics_apm/inst/sequel.rb +178 -0
- data/lib/appoptics_apm/inst/sidekiq-client.rb +55 -0
- data/lib/appoptics_apm/inst/sidekiq-worker.rb +66 -0
- data/lib/appoptics_apm/inst/twitter-cassandra.rb +294 -0
- data/lib/appoptics_apm/inst/typhoeus.rb +108 -0
- data/lib/appoptics_apm/instrumentation.rb +22 -0
- data/lib/appoptics_apm/loading.rb +65 -0
- data/lib/appoptics_apm/logger.rb +14 -0
- data/lib/appoptics_apm/noop/README.md +9 -0
- data/lib/appoptics_apm/noop/context.rb +27 -0
- data/lib/appoptics_apm/noop/metadata.rb +25 -0
- data/lib/appoptics_apm/noop/profiling.rb +21 -0
- data/lib/appoptics_apm/oboe_init_options.rb +211 -0
- data/lib/appoptics_apm/ruby.rb +35 -0
- data/lib/appoptics_apm/sdk/current_trace.rb +77 -0
- data/lib/appoptics_apm/sdk/custom_metrics.rb +94 -0
- data/lib/appoptics_apm/sdk/logging.rb +37 -0
- data/lib/appoptics_apm/sdk/tracing.rb +434 -0
- data/lib/appoptics_apm/support/profiling.rb +18 -0
- data/lib/appoptics_apm/support/transaction_metrics.rb +67 -0
- data/lib/appoptics_apm/support/transaction_settings.rb +219 -0
- data/lib/appoptics_apm/support/x_trace_options.rb +110 -0
- data/lib/appoptics_apm/support_report.rb +119 -0
- data/lib/appoptics_apm/test.rb +95 -0
- data/lib/appoptics_apm/thread_local.rb +26 -0
- data/lib/appoptics_apm/util.rb +326 -0
- data/lib/appoptics_apm/version.rb +16 -0
- data/lib/appoptics_apm/xtrace.rb +115 -0
- data/lib/appoptics_apm.rb +77 -0
- data/lib/joboe_metal.rb +212 -0
- data/lib/oboe.rb +7 -0
- data/lib/oboe_metal.rb +172 -0
- data/lib/rails/generators/appoptics_apm/install_generator.rb +47 -0
- data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +425 -0
- data/log/.keep +0 -0
- data/yardoc_frontpage.md +26 -0
- 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
|