solarwinds_apm 5.0.0
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 +112 -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 +155 -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 +161 -0
- data/.gitignore +39 -0
- data/.rubocop.yml +29 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +769 -0
- data/CONFIG.md +31 -0
- data/Gemfile +14 -0
- data/LICENSE +202 -0
- data/README.md +383 -0
- data/bin/solarwinds_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 +141 -0
- data/ext/oboe_metal/extconf_local.rb +75 -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 +247 -0
- data/ext/oboe_metal/src/frames.h +40 -0
- data/ext/oboe_metal/src/init_solarwinds_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 +1169 -0
- data/ext/oboe_metal/src/oboe_api.cpp +658 -0
- data/ext/oboe_metal/src/oboe_api.hpp +433 -0
- data/ext/oboe_metal/src/oboe_debug.h +59 -0
- data/ext/oboe_metal/src/oboe_swig_wrap.cc +7562 -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/oboe.rb +7 -0
- data/lib/oboe_metal.rb +172 -0
- data/lib/rails/generators/solarwinds_apm/install_generator.rb +47 -0
- data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +424 -0
- data/lib/solarwinds_apm/api/layerinit.rb +41 -0
- data/lib/solarwinds_apm/api/logging.rb +356 -0
- data/lib/solarwinds_apm/api/memcache.rb +37 -0
- data/lib/solarwinds_apm/api/metrics.rb +63 -0
- data/lib/solarwinds_apm/api/util.rb +98 -0
- data/lib/solarwinds_apm/api.rb +21 -0
- data/lib/solarwinds_apm/base.rb +160 -0
- data/lib/solarwinds_apm/config.rb +301 -0
- data/lib/solarwinds_apm/frameworks/grape.rb +96 -0
- data/lib/solarwinds_apm/frameworks/padrino.rb +78 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_controller.rb +100 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_controller5.rb +50 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_view.rb +88 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/active_record.rb +26 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +22 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +103 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/logger_formatters.rb +14 -0
- data/lib/solarwinds_apm/frameworks/rails.rb +100 -0
- data/lib/solarwinds_apm/frameworks/sinatra.rb +96 -0
- data/lib/solarwinds_apm/inst/bunny-client.rb +157 -0
- data/lib/solarwinds_apm/inst/bunny-consumer.rb +102 -0
- data/lib/solarwinds_apm/inst/curb.rb +288 -0
- data/lib/solarwinds_apm/inst/dalli.rb +89 -0
- data/lib/solarwinds_apm/inst/delayed_job.rb +100 -0
- data/lib/solarwinds_apm/inst/excon.rb +113 -0
- data/lib/solarwinds_apm/inst/faraday.rb +96 -0
- data/lib/solarwinds_apm/inst/graphql.rb +206 -0
- data/lib/solarwinds_apm/inst/grpc_client.rb +147 -0
- data/lib/solarwinds_apm/inst/grpc_server.rb +119 -0
- data/lib/solarwinds_apm/inst/httpclient.rb +181 -0
- data/lib/solarwinds_apm/inst/logger_formatter.rb +46 -0
- data/lib/solarwinds_apm/inst/logging_log_event.rb +24 -0
- data/lib/solarwinds_apm/inst/lumberjack_formatter.rb +9 -0
- data/lib/solarwinds_apm/inst/memcached.rb +86 -0
- data/lib/solarwinds_apm/inst/mongo.rb +246 -0
- data/lib/solarwinds_apm/inst/mongo2.rb +225 -0
- data/lib/solarwinds_apm/inst/moped.rb +466 -0
- data/lib/solarwinds_apm/inst/net_http.rb +60 -0
- data/lib/solarwinds_apm/inst/rack.rb +217 -0
- data/lib/solarwinds_apm/inst/rack_cache.rb +35 -0
- data/lib/solarwinds_apm/inst/redis.rb +273 -0
- data/lib/solarwinds_apm/inst/resque.rb +129 -0
- data/lib/solarwinds_apm/inst/rest-client.rb +43 -0
- data/lib/solarwinds_apm/inst/sequel.rb +241 -0
- data/lib/solarwinds_apm/inst/sidekiq-client.rb +63 -0
- data/lib/solarwinds_apm/inst/sidekiq-worker.rb +64 -0
- data/lib/solarwinds_apm/inst/typhoeus.rb +90 -0
- data/lib/solarwinds_apm/instrumentation.rb +22 -0
- data/lib/solarwinds_apm/loading.rb +65 -0
- data/lib/solarwinds_apm/logger.rb +14 -0
- data/lib/solarwinds_apm/noop/README.md +9 -0
- data/lib/solarwinds_apm/noop/context.rb +26 -0
- data/lib/solarwinds_apm/noop/metadata.rb +25 -0
- data/lib/solarwinds_apm/noop/profiling.rb +21 -0
- data/lib/solarwinds_apm/oboe_init_options.rb +191 -0
- data/lib/solarwinds_apm/ruby.rb +35 -0
- data/lib/solarwinds_apm/sdk/current_trace_info.rb +123 -0
- data/lib/solarwinds_apm/sdk/custom_metrics.rb +94 -0
- data/lib/solarwinds_apm/sdk/logging.rb +37 -0
- data/lib/solarwinds_apm/sdk/trace_context_headers.rb +69 -0
- data/lib/solarwinds_apm/sdk/tracing.rb +432 -0
- data/lib/solarwinds_apm/support/profiling.rb +22 -0
- data/lib/solarwinds_apm/support/trace_context.rb +53 -0
- data/lib/solarwinds_apm/support/trace_state.rb +69 -0
- data/lib/solarwinds_apm/support/trace_string.rb +89 -0
- data/lib/solarwinds_apm/support/transaction_metrics.rb +67 -0
- data/lib/solarwinds_apm/support/transaction_settings.rb +233 -0
- data/lib/solarwinds_apm/support/x_trace_options.rb +113 -0
- data/lib/solarwinds_apm/support.rb +12 -0
- data/lib/solarwinds_apm/support_report.rb +113 -0
- data/lib/solarwinds_apm/test.rb +165 -0
- data/lib/solarwinds_apm/thread_local.rb +26 -0
- data/lib/solarwinds_apm/util.rb +334 -0
- data/lib/solarwinds_apm/version.rb +17 -0
- data/lib/solarwinds_apm.rb +72 -0
- data/log/.keep +0 -0
- data/log/postgresql/.keep +0 -0
- data/solarwinds_apm.gemspec +52 -0
- data/yardoc_frontpage.md +24 -0
- metadata +228 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
if defined?(Delayed)
|
|
5
|
+
module SolarWindsAPM
|
|
6
|
+
module Inst
|
|
7
|
+
module DelayedJob
|
|
8
|
+
##
|
|
9
|
+
# ForkHandler
|
|
10
|
+
#
|
|
11
|
+
# Since delayed job doesn't offer a hook into `after_fork`, we alias the method
|
|
12
|
+
# here to do our magic after a fork happens.
|
|
13
|
+
#
|
|
14
|
+
module ForkHandler
|
|
15
|
+
def self.extended(klass)
|
|
16
|
+
SolarWindsAPM::Util.class_method_alias(klass, :after_fork, ::Delayed::Worker)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def after_fork_with_sw_apm
|
|
20
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/delayed_job] Detected fork. Restarting SolarWindsAPM reporter.' if SolarWindsAPM::Config[:verbose]
|
|
21
|
+
SolarWindsAPM::Reporter.restart unless ENV.key?('SW_APM_GEM_TEST')
|
|
22
|
+
|
|
23
|
+
after_fork_without_sw_apm
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# SolarWindsAPM::Inst::DelayedJob::Plugin
|
|
29
|
+
#
|
|
30
|
+
# The SolarWindsAPM DelayedJob plugin. Here we wrap `enqueue` and
|
|
31
|
+
# `perform` to capture the timing of the bits we're interested in.
|
|
32
|
+
#
|
|
33
|
+
# Traces from the client are not continued in the consumer for a number
|
|
34
|
+
# of reasons:
|
|
35
|
+
# - no context propagation for delayed_job in OTEL
|
|
36
|
+
# - there is no reliable way to for the job to carry trace information. It
|
|
37
|
+
# is an instance of a shared class they share,
|
|
38
|
+
# often: Delayed::Backend::ActiveRecord::Job, but could be something else
|
|
39
|
+
# - It can also be too asynchronous for tracing to make sense. The worker can be
|
|
40
|
+
# delayed by seconds/minutes/hours and the trace processing completed already
|
|
41
|
+
#
|
|
42
|
+
class Plugin < Delayed::Plugin
|
|
43
|
+
callbacks do |lifecycle|
|
|
44
|
+
|
|
45
|
+
# enqueue
|
|
46
|
+
if SolarWindsAPM::Config[:delayed_jobclient][:enabled]
|
|
47
|
+
lifecycle.around(:enqueue) do |job, &block|
|
|
48
|
+
begin
|
|
49
|
+
report_kvs = {}
|
|
50
|
+
report_kvs[:Spec] = :pushq
|
|
51
|
+
report_kvs[:Flavor] = :DelayedJob
|
|
52
|
+
report_kvs[:JobName] = job.name
|
|
53
|
+
report_kvs[:MsgID] = job.id
|
|
54
|
+
report_kvs[:Queue] = job.queue if job.queue
|
|
55
|
+
report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:delayed_jobclient][:collect_backtraces]
|
|
56
|
+
|
|
57
|
+
SolarWindsAPM::SDK.trace(:'delayed_job-client', kvs: report_kvs) do
|
|
58
|
+
block.call(job)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# invoke_job
|
|
65
|
+
if SolarWindsAPM::Config[:delayed_jobworker][:enabled]
|
|
66
|
+
lifecycle.around(:perform) do |worker, job, &block|
|
|
67
|
+
begin
|
|
68
|
+
report_kvs = {}
|
|
69
|
+
report_kvs[:Spec] = :job
|
|
70
|
+
report_kvs[:Flavor] = :DelayedJob
|
|
71
|
+
report_kvs[:JobName] = job.name
|
|
72
|
+
report_kvs[:MsgID] = job.id
|
|
73
|
+
report_kvs[:Queue] = job.queue if job.queue
|
|
74
|
+
report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:delayed_jobworker][:collect_backtraces]
|
|
75
|
+
|
|
76
|
+
# DelayedJob Specific KVs
|
|
77
|
+
report_kvs[:priority] = job.priority
|
|
78
|
+
report_kvs[:attempts] = job.attempts
|
|
79
|
+
report_kvs[:WorkerName] = job.locked_by
|
|
80
|
+
rescue => e
|
|
81
|
+
SolarWindsAPM.logger.warn "[solarwinds_apm/warning] inst/delayed_job.rb: #{e.message}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
SolarWindsAPM::SDK.start_trace(:'delayed_job-worker', kvs: report_kvs) do
|
|
85
|
+
result = block.call(worker, job)
|
|
86
|
+
SolarWindsAPM::API.log_exception(:'delayed_job-worker', job.error) if job.error
|
|
87
|
+
result
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting delayed_job' if SolarWindsAPM::Config[:verbose]
|
|
98
|
+
SolarWindsAPM::Util.send_extend(::Delayed::Worker, SolarWindsAPM::Inst::DelayedJob::ForkHandler)
|
|
99
|
+
Delayed::Worker.plugins << SolarWindsAPM::Inst::DelayedJob::Plugin
|
|
100
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
module SolarWindsAPM
|
|
5
|
+
module Inst
|
|
6
|
+
module ExconConnection
|
|
7
|
+
include SolarWindsAPM::SDK::TraceContextHeaders
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def sw_apm_collect(params)
|
|
12
|
+
kvs = {}
|
|
13
|
+
kvs[:Spec] = 'rsc'
|
|
14
|
+
kvs[:IsService] = 1
|
|
15
|
+
|
|
16
|
+
# Conditionally log query args
|
|
17
|
+
if SolarWindsAPM::Config[:excon][:log_args] && @data[:query]
|
|
18
|
+
if @data[:query].is_a?(Hash)
|
|
19
|
+
service_arg = "#{@data[:path]}?#{URI.encode_www_form(@data[:query])}"
|
|
20
|
+
else
|
|
21
|
+
service_arg = "#{@data[:path]}?#{@data[:query]}"
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
service_arg = @data[:path]
|
|
25
|
+
end
|
|
26
|
+
kvs[:RemoteURL] = "#{@data[:scheme]}://#{@data[:host]}:#{@data[:port]}#{service_arg}"
|
|
27
|
+
|
|
28
|
+
# In the case of HTTP pipelining, params could be an array of
|
|
29
|
+
# request hashes.
|
|
30
|
+
if params.is_a?(Array)
|
|
31
|
+
methods = []
|
|
32
|
+
params.each do |p|
|
|
33
|
+
methods << SolarWindsAPM::Util.upcase(p[:method])
|
|
34
|
+
end
|
|
35
|
+
kvs[:HTTPMethods] = methods.join(',')[0..1024]
|
|
36
|
+
kvs[:Pipeline] = true
|
|
37
|
+
else
|
|
38
|
+
kvs[:HTTPMethod] = SolarWindsAPM::Util.upcase(params[:method])
|
|
39
|
+
end
|
|
40
|
+
kvs
|
|
41
|
+
rescue => e
|
|
42
|
+
SolarWindsAPM.logger.debug "[solarwinds_apm/debug] Error capturing excon KVs: #{e.message}"
|
|
43
|
+
SolarWindsAPM.logger.debug e.backtrace.join('\n') if SolarWindsAPM::Config[:verbose]
|
|
44
|
+
ensure
|
|
45
|
+
return kvs
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
public
|
|
49
|
+
|
|
50
|
+
def requests(pipeline_params)
|
|
51
|
+
responses = nil
|
|
52
|
+
kvs = sw_apm_collect(pipeline_params)
|
|
53
|
+
SolarWindsAPM::SDK.trace(:excon, kvs: kvs) do
|
|
54
|
+
kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:excon][:collect_backtraces]
|
|
55
|
+
responses = super(pipeline_params)
|
|
56
|
+
kvs[:HTTPStatuses] = responses.map { |r| r.status }.join(',')
|
|
57
|
+
end
|
|
58
|
+
responses
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def request(params = {}, &block)
|
|
62
|
+
# If we're not tracing, just do a fast return.
|
|
63
|
+
# If making HTTP pipeline requests (ordered batched)
|
|
64
|
+
# then just return as we're tracing from parent
|
|
65
|
+
# <tt>requests</tt>
|
|
66
|
+
if !SolarWindsAPM.tracing? || params[:pipeline]
|
|
67
|
+
add_tracecontext_headers(@data[:headers])
|
|
68
|
+
return super(params, &block)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
response_context = nil
|
|
73
|
+
|
|
74
|
+
kvs = sw_apm_collect(params)
|
|
75
|
+
|
|
76
|
+
SolarWindsAPM::API.log_entry(:excon, kvs)
|
|
77
|
+
kvs.clear
|
|
78
|
+
|
|
79
|
+
req_context = SolarWindsAPM::Context.toString
|
|
80
|
+
|
|
81
|
+
# The core excon call
|
|
82
|
+
add_tracecontext_headers(@data[:headers])
|
|
83
|
+
response = super(params, &block)
|
|
84
|
+
|
|
85
|
+
# excon only passes back a hash (datum) for HTTP pipelining...
|
|
86
|
+
# In that case, we should never arrive here but for the OCD, double check
|
|
87
|
+
# the datatype before trying to extract pertinent info
|
|
88
|
+
if response.is_a?(Excon::Response)
|
|
89
|
+
kvs[:HTTPStatus] = response.status
|
|
90
|
+
|
|
91
|
+
# If we get a redirect, report the location header
|
|
92
|
+
if ((300..308).to_a.include? response.status.to_i) && response.headers.key?('Location')
|
|
93
|
+
kvs[:Location] = response.headers['Location']
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
response
|
|
98
|
+
rescue => e
|
|
99
|
+
SolarWindsAPM::API.log_exception(:excon, e)
|
|
100
|
+
raise e
|
|
101
|
+
ensure
|
|
102
|
+
kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:excon][:collect_backtraces]
|
|
103
|
+
SolarWindsAPM::API.log_exit(:excon, kvs) unless params[:pipeline]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if SolarWindsAPM::Config[:excon][:enabled] && defined?(Excon)
|
|
111
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting excon' if SolarWindsAPM::Config[:verbose]
|
|
112
|
+
Excon::Connection.prepend(SolarWindsAPM::Inst::ExconConnection)
|
|
113
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
#####################################################
|
|
5
|
+
# FYI:
|
|
6
|
+
# Faraday only adds tracing when it is
|
|
7
|
+
# not using an adapter that is instrumented
|
|
8
|
+
#
|
|
9
|
+
# otherwise we would get two spans for the same call
|
|
10
|
+
#####################################################
|
|
11
|
+
|
|
12
|
+
module SolarWindsAPM
|
|
13
|
+
module Inst
|
|
14
|
+
module FaradayConnection
|
|
15
|
+
include SolarWindsAPM::SDK::TraceContextHeaders
|
|
16
|
+
|
|
17
|
+
def run_request(method, url, body, headers, &block)
|
|
18
|
+
remote_call = remote_call?
|
|
19
|
+
unless SolarWindsAPM.tracing?
|
|
20
|
+
if remote_call
|
|
21
|
+
add_tracecontext_headers(@headers)
|
|
22
|
+
end
|
|
23
|
+
return super(method, url, body, headers, &block)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
SolarWindsAPM::API.log_entry(:faraday)
|
|
28
|
+
if remote_call # nothing else is instrumented that could add the w3c context
|
|
29
|
+
add_tracecontext_headers(@headers)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
result = super(method, url, body, headers, &block)
|
|
33
|
+
|
|
34
|
+
kvs = {}
|
|
35
|
+
|
|
36
|
+
# this seems the safer condition than trying to identify the
|
|
37
|
+
# faraday version when adapter started to work without arg
|
|
38
|
+
# and handlers don't include the adapter anymore
|
|
39
|
+
if @builder.method(:adapter).parameters.find { |ele| ele[0] == :req }
|
|
40
|
+
kvs[:Middleware] = @builder.handlers
|
|
41
|
+
else
|
|
42
|
+
kvs[:Middleware] = [@builder.adapter] + @builder.handlers
|
|
43
|
+
end
|
|
44
|
+
kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:faraday][:collect_backtraces]
|
|
45
|
+
|
|
46
|
+
# Only send service KVs if we're not using an adapter
|
|
47
|
+
# Otherwise, the adapter instrumentation will send the service KVs
|
|
48
|
+
if remote_call
|
|
49
|
+
kvs.merge!(rsc_kvs(url, method, result))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
result
|
|
53
|
+
rescue => e
|
|
54
|
+
SolarWindsAPM::API.log_exception(:faraday, e)
|
|
55
|
+
raise e
|
|
56
|
+
ensure
|
|
57
|
+
SolarWindsAPM::API.log_exit(:faraday, kvs)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# This is only considered a remote service call if the middleware/adapter is not instrumented
|
|
64
|
+
def remote_call?
|
|
65
|
+
if @builder.method(:adapter).parameters.find { |ele| ele[0] == :req }
|
|
66
|
+
(@builder.handlers.map(&:name) & SW_APM_INSTR_ADAPTERS).count == 0
|
|
67
|
+
else
|
|
68
|
+
((@builder.handlers.map(&:name) << @builder.adapter.name) & SW_APM_INSTR_ADAPTERS).count == 0
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def rsc_kvs(_url, method, result)
|
|
73
|
+
kvs = { :Spec => 'rsc',
|
|
74
|
+
:IsService => 1,
|
|
75
|
+
:HTTPMethod => method.upcase,
|
|
76
|
+
:HTTPStatus => result.status, }
|
|
77
|
+
kvs[:RemoteURL] = result.env.to_hash[:url].to_s
|
|
78
|
+
kvs[:RemoteURL].split('?').first unless SolarWindsAPM::Config[:faraday][:log_args]
|
|
79
|
+
|
|
80
|
+
kvs
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if defined?(Faraday) && SolarWindsAPM::Config[:faraday][:enabled]
|
|
87
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting faraday' if SolarWindsAPM::Config[:verbose]
|
|
88
|
+
Faraday::Connection.prepend(SolarWindsAPM::Inst::FaradayConnection)
|
|
89
|
+
|
|
90
|
+
SW_APM_INSTR_ADAPTERS = [] # ["Faraday::Adapter::NetHttp", "Faraday::Adapter::Excon", "Faraday::Adapter::Typhoeus"]
|
|
91
|
+
|
|
92
|
+
SW_APM_INSTR_ADAPTERS << "Faraday::Adapter::Typhoeus" if defined? Faraday::Adapter::Typhoeus
|
|
93
|
+
SW_APM_INSTR_ADAPTERS << "Faraday::Adapter::NetHttp" if defined? Faraday::Adapter::NetHttp
|
|
94
|
+
SW_APM_INSTR_ADAPTERS << "Faraday::Adapter::Excon" if defined? Faraday::Adapter::Excon
|
|
95
|
+
SW_APM_INSTR_ADAPTERS << "Faraday::Adapter::HTTPClient" if defined? Faraday::Adapter::HTTPClient
|
|
96
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# Copyright (c) 2019 SolarWinds, LLC.
|
|
5
|
+
# All rights reserved.
|
|
6
|
+
#++
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Tracing for the graphql gem
|
|
11
|
+
#
|
|
12
|
+
# This instrumentation is autoloaded when a class inherits GraphQL::Schema
|
|
13
|
+
# no need to call `tracer` or `use`
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if defined?(GraphQL::Tracing) && !(SolarWindsAPM::Config[:graphql][:enabled] == false)
|
|
17
|
+
module GraphQL
|
|
18
|
+
module Tracing
|
|
19
|
+
|
|
20
|
+
class SolarWindsAPMTracing < GraphQL::Tracing::PlatformTracing
|
|
21
|
+
# These GraphQL events will show up as 'graphql.prep' spans
|
|
22
|
+
PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze
|
|
23
|
+
EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
|
|
24
|
+
|
|
25
|
+
self.platform_keys = {
|
|
26
|
+
'lex' => 'lex',
|
|
27
|
+
'parse' => 'parse',
|
|
28
|
+
'validate' => 'validate',
|
|
29
|
+
'analyze_query' => 'analyze_query',
|
|
30
|
+
'analyze_multiplex' => 'analyze_multiplex',
|
|
31
|
+
'execute_multiplex' => 'execute_multiplex',
|
|
32
|
+
'execute_query' => 'execute_query',
|
|
33
|
+
'execute_query_lazy' => 'execute_query_lazy'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def platform_trace(platform_key, _key, data)
|
|
37
|
+
return yield if gql_config[:enabled] == false
|
|
38
|
+
|
|
39
|
+
layer = span_name(platform_key)
|
|
40
|
+
kvs = metadata(data, layer)
|
|
41
|
+
kvs[:Key] = platform_key if (PREP_KEYS + EXEC_KEYS).include?(platform_key)
|
|
42
|
+
|
|
43
|
+
transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute'
|
|
44
|
+
|
|
45
|
+
::SolarWindsAPM::SDK.trace(layer, kvs: kvs) do
|
|
46
|
+
kvs.clear # we don't have to send them twice
|
|
47
|
+
yield
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def platform_field_key(type, field)
|
|
52
|
+
"graphql.#{type.graphql_name}.#{field.name}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def platform_authorized_key(type)
|
|
56
|
+
"graphql.#{type.graphql_name}.authorized"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def platform_resolve_type_key(type)
|
|
60
|
+
"graphql.#{type.graphql_name}.resolve_type"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def gql_config
|
|
66
|
+
::SolarWindsAPM::Config[:graphql] ||= {}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def transaction_name(query)
|
|
70
|
+
return if gql_config[:transaction_name] == false ||
|
|
71
|
+
::SolarWindsAPM::SDK.get_transaction_name
|
|
72
|
+
|
|
73
|
+
split_query = query.strip.split(/\W+/, 3)
|
|
74
|
+
split_query[0] = 'query' if split_query[0].empty?
|
|
75
|
+
name = "graphql.#{split_query[0..1].join('.')}"
|
|
76
|
+
|
|
77
|
+
::SolarWindsAPM::SDK.set_transaction_name(name)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def multiplex_transaction_name(names)
|
|
81
|
+
return if gql_config[:transaction_name] == false ||
|
|
82
|
+
::SolarWindsAPM::SDK.get_transaction_name
|
|
83
|
+
|
|
84
|
+
name = "graphql.multiplex.#{names.join('.')}"
|
|
85
|
+
name = "#{name[0..251]}..." if name.length > 254
|
|
86
|
+
|
|
87
|
+
::SolarWindsAPM::SDK.set_transaction_name(name)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def span_name(key)
|
|
91
|
+
return 'graphql.prep' if PREP_KEYS.include?(key)
|
|
92
|
+
return 'graphql.execute' if EXEC_KEYS.include?(key)
|
|
93
|
+
|
|
94
|
+
key[/^graphql\./] ? key : "graphql.#{key}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
98
|
+
def metadata(data, layer)
|
|
99
|
+
kvs = data.keys.map do |key|
|
|
100
|
+
case key
|
|
101
|
+
when :context
|
|
102
|
+
graphql_context(data[:context], layer)
|
|
103
|
+
when :query
|
|
104
|
+
graphql_query(data[:query])
|
|
105
|
+
when :query_string
|
|
106
|
+
graphql_query_string(data[:query_string])
|
|
107
|
+
when :multiplex
|
|
108
|
+
graphql_multiplex(data[:multiplex])
|
|
109
|
+
else
|
|
110
|
+
[key, data[key]] unless key == :path # we get the path from context
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
kvs.compact.flatten.each_slice(2).to_h.merge(Spec: 'graphql')
|
|
115
|
+
end
|
|
116
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
117
|
+
|
|
118
|
+
def graphql_context(context, layer)
|
|
119
|
+
context.errors && context.errors.each do |err|
|
|
120
|
+
SolarWindsAPM::API.log_exception(layer, err)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
[[:Path, context.path.join('.')]]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def graphql_query(query)
|
|
127
|
+
return [] unless query
|
|
128
|
+
|
|
129
|
+
query_string = query.query_string
|
|
130
|
+
query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
|
|
131
|
+
query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
|
|
132
|
+
|
|
133
|
+
[[:InboundQuery, query_string],
|
|
134
|
+
[:Operation, query.selected_operation_name]]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def graphql_query_string(query_string)
|
|
138
|
+
query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
|
|
139
|
+
query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
|
|
140
|
+
|
|
141
|
+
[:InboundQuery, query_string]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def graphql_multiplex(data)
|
|
145
|
+
names = data.queries.map(&:operations).map(&:keys).flatten.compact
|
|
146
|
+
multiplex_transaction_name(names) if names.size > 1
|
|
147
|
+
|
|
148
|
+
[:Operations, names.join(', ')]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def sanitize(query)
|
|
152
|
+
return unless query
|
|
153
|
+
|
|
154
|
+
# remove arguments
|
|
155
|
+
query.gsub(/"[^"]*"/, '"?"') # strings
|
|
156
|
+
.gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats
|
|
157
|
+
.gsub(/\[[^\]]*\]/, '[?]') # arrays
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def remove_comments(query)
|
|
161
|
+
return unless query
|
|
162
|
+
|
|
163
|
+
query.gsub(/#[^\n\r]*/, '')
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
module SolarWindsAPM
|
|
170
|
+
module GraphQLSchemaPrepend
|
|
171
|
+
|
|
172
|
+
# Graphql doesn't check if a plugin is added twice
|
|
173
|
+
# we would get double traces
|
|
174
|
+
def use(plugin, **options)
|
|
175
|
+
super unless self.plugins.find { |pl| pl[0].to_s == plugin.to_s }
|
|
176
|
+
|
|
177
|
+
self.plugins
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def inherited(subclass)
|
|
181
|
+
subclass.use(GraphQL::Tracing::SolarWindsAPMTracing)
|
|
182
|
+
super
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
module GraphQLErrorPrepend
|
|
187
|
+
def initialize(*args)
|
|
188
|
+
super
|
|
189
|
+
bt = SolarWindsAPM::API.backtrace(1)
|
|
190
|
+
set_backtrace(bt) unless self.backtrace
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if Gem.loaded_specs['graphql'] && Gem.loaded_specs['graphql'].version >= Gem::Version.new('1.8.0')
|
|
197
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting GraphQL' if SolarWindsAPM::Config[:verbose]
|
|
198
|
+
if defined?(GraphQL::Schema)
|
|
199
|
+
GraphQL::Schema.singleton_class.prepend(SolarWindsAPM::GraphQLSchemaPrepend)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
if defined?(GraphQL::Error)
|
|
203
|
+
GraphQL::Error.prepend(SolarWindsAPM::GraphQLErrorPrepend)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright (c) 2018 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
module SolarWindsAPM
|
|
5
|
+
module GRPC
|
|
6
|
+
|
|
7
|
+
module ActiveCall
|
|
8
|
+
include SolarWindsAPM::SDK::TraceContextHeaders
|
|
9
|
+
|
|
10
|
+
if defined? ::GRPC
|
|
11
|
+
StatusCodes = {}
|
|
12
|
+
::GRPC::Core::StatusCodes.constants.each { |code| StatusCodes[::GRPC::Core::StatusCodes.const_get(code)] = code }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.included(klass)
|
|
16
|
+
SolarWindsAPM::Util.method_alias(klass, :request_response, ::GRPC::ActiveCall)
|
|
17
|
+
SolarWindsAPM::Util.method_alias(klass, :client_streamer, ::GRPC::ActiveCall)
|
|
18
|
+
SolarWindsAPM::Util.method_alias(klass, :server_streamer, ::GRPC::ActiveCall)
|
|
19
|
+
SolarWindsAPM::Util.method_alias(klass, :bidi_streamer, ::GRPC::ActiveCall)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def grpc_tags(method_type, method)
|
|
23
|
+
tags = { 'Spec' => 'rsc',
|
|
24
|
+
'RemoteURL' => "grpc://#{peer}#{method}",
|
|
25
|
+
'GRPCMethodType' => method_type,
|
|
26
|
+
'IsService' => 'True'
|
|
27
|
+
}
|
|
28
|
+
tags
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def request_response_with_sw_apm(req, metadata: {})
|
|
32
|
+
unary_response(req, type: 'UNARY', metadata: metadata, without: :request_response_without_sw_apm)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def client_streamer_with_sw_apm(req, metadata: {})
|
|
36
|
+
unary_response(req, type: 'CLIENT_STREAMING', metadata: metadata, without: :client_streamer_without_sw_apm)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def server_streamer_with_sw_apm(req, metadata: {}, &blk)
|
|
40
|
+
@tags = grpc_tags('SERVER_STREAMING', metadata['method'] || metadata_to_send['method'])
|
|
41
|
+
SolarWindsAPM::API.log_entry('grpc-client', @tags)
|
|
42
|
+
add_tracecontext_headers(metadata)
|
|
43
|
+
|
|
44
|
+
patch_receive_and_check_status # need to patch this so that log_exit can be called after the enum is consumed
|
|
45
|
+
|
|
46
|
+
response = server_streamer_without_sw_apm(req, metadata: metadata)
|
|
47
|
+
block_given? ? response.each { |r| yield r } : response
|
|
48
|
+
rescue => e
|
|
49
|
+
# this check is needed because the exception may have been logged in patch_receive_and_check_status
|
|
50
|
+
unless e.instance_variable_get(:@exn_logged)
|
|
51
|
+
SolarWindsAPM::API.log_exception('grpc-client', e)
|
|
52
|
+
SolarWindsAPM::API.log_exit('grpc-client', exit_tags(@tags))
|
|
53
|
+
end
|
|
54
|
+
raise e
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def bidi_streamer_with_sw_apm(req, metadata: {}, &blk)
|
|
58
|
+
@tags = grpc_tags('BIDI_STREAMING', metadata['method'] || metadata_to_send['method'])
|
|
59
|
+
SolarWindsAPM::API.log_entry('grpc-client', @tags)
|
|
60
|
+
add_tracecontext_headers(metadata)
|
|
61
|
+
|
|
62
|
+
patch_set_input_stream_done
|
|
63
|
+
|
|
64
|
+
response = bidi_streamer_without_sw_apm(req, metadata: metadata)
|
|
65
|
+
block_given? ? response.each { |r| yield r } : response
|
|
66
|
+
rescue => e
|
|
67
|
+
unless e.instance_variable_get(:@exn_logged)
|
|
68
|
+
SolarWindsAPM::API.log_exception('grpc-client', e)
|
|
69
|
+
SolarWindsAPM::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
|
+
SolarWindsAPM::SDK.trace('grpc-client', kvs: tags) do
|
|
79
|
+
add_tracecontext_headers(metadata)
|
|
80
|
+
begin
|
|
81
|
+
send(without, req, metadata: metadata)
|
|
82
|
+
ensure
|
|
83
|
+
exit_tags(tags)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def patch_receive_and_check_status
|
|
89
|
+
def self.receive_and_check_status # need to patch this so that log_exit can be called after the enum is consumed
|
|
90
|
+
super
|
|
91
|
+
rescue => e
|
|
92
|
+
SolarWindsAPM::API.log_exception('grpc-client', e)
|
|
93
|
+
raise e
|
|
94
|
+
ensure
|
|
95
|
+
SolarWindsAPM::API.log_exit('grpc-client', exit_tags(@tags))
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def patch_set_input_stream_done
|
|
100
|
+
# need to patch this instance method so that log_exit can be called after the enum is consumed
|
|
101
|
+
def self.set_input_stream_done
|
|
102
|
+
return if status.nil?
|
|
103
|
+
if status.code > 0
|
|
104
|
+
SolarWindsAPM::API.log_exception('grpc-client', $!)
|
|
105
|
+
end
|
|
106
|
+
SolarWindsAPM::API.log_exit('grpc-client', exit_tags(@tags))
|
|
107
|
+
super
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def exit_tags(tags)
|
|
112
|
+
# we need to translate the status.code, it is not the status.details we want, they are not matching 1:1
|
|
113
|
+
tags['GRPCStatus'] ||= @call.status ? StatusCodes[@call.status.code].to_s : 'UNKNOWN'
|
|
114
|
+
tags['Backtrace'] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:grpc_client][:collect_backtraces]
|
|
115
|
+
tags
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if defined?(GRPC) && SolarWindsAPM::Config[:grpc_client][:enabled]
|
|
123
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting GRPC' if SolarWindsAPM::Config[:verbose]
|
|
124
|
+
|
|
125
|
+
# Client side is instrumented in ActiveCall and ClientStub
|
|
126
|
+
SolarWindsAPM::Util.send_include(GRPC::ActiveCall, SolarWindsAPM::GRPC::ActiveCall)
|
|
127
|
+
|
|
128
|
+
GRPC_ClientStub_ops = [:request_response, :client_streamer, :server_streamer, :bidi_streamer]
|
|
129
|
+
module GRPC
|
|
130
|
+
class ClientStub
|
|
131
|
+
GRPC_ClientStub_ops.reject { |m| !method_defined?(m) }.each do |m|
|
|
132
|
+
define_method("#{m}_with_sw_apm") do |method, req, marshal, unmarshal, deadline: nil,
|
|
133
|
+
return_op: false, parent: nil,
|
|
134
|
+
credentials: nil, metadata: {}, &blk|
|
|
135
|
+
|
|
136
|
+
metadata['method'] = method
|
|
137
|
+
return send("#{m}_without_sw_apm", method, req, marshal, unmarshal, deadline: deadline,
|
|
138
|
+
return_op: return_op, parent: parent,
|
|
139
|
+
credentials: credentials, metadata: metadata, &blk)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
SolarWindsAPM::Util.method_alias(GRPC::ClientStub, m)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|