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,217 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'cgi'
|
|
6
|
+
|
|
7
|
+
if SolarWindsAPM.loaded
|
|
8
|
+
module SolarWindsAPM
|
|
9
|
+
##
|
|
10
|
+
# SolarWindsAPM::Rack
|
|
11
|
+
#
|
|
12
|
+
# The SolarWindsAPM::Rack middleware used to sample a subset of incoming
|
|
13
|
+
# requests for instrumentation and reporting. Tracing context can
|
|
14
|
+
# be received here (via the X-Trace HTTP header) or initiated here
|
|
15
|
+
# based on configured tracing mode.
|
|
16
|
+
#
|
|
17
|
+
# After the rack layer passes on to the following layers (Rails, Sinatra,
|
|
18
|
+
# Padrino, Grape), then the instrumentation downstream will
|
|
19
|
+
# automatically detect whether this is a sampled request or not
|
|
20
|
+
# and act accordingly.
|
|
21
|
+
#
|
|
22
|
+
class Rack
|
|
23
|
+
include SolarWindsAPM::SDK::TraceContextHeaders
|
|
24
|
+
|
|
25
|
+
attr_reader :app
|
|
26
|
+
|
|
27
|
+
def initialize(app)
|
|
28
|
+
@app = app
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def call(env)
|
|
32
|
+
|
|
33
|
+
# In the case of nested Ruby apps such as Grape inside of Rails
|
|
34
|
+
# or Grape inside of Grape, each app has it's own instance
|
|
35
|
+
# of rack middleware. We want to avoid tracing rack more than once
|
|
36
|
+
return @app.call(env) if SolarWindsAPM.tracing? && SolarWindsAPM.layer == :rack
|
|
37
|
+
|
|
38
|
+
# there may be a legit existing context that we need to continue
|
|
39
|
+
# we need to check if we marked a start in env
|
|
40
|
+
# env is specific per request
|
|
41
|
+
existing_context = false
|
|
42
|
+
if SolarWindsAPM::Context.isValid
|
|
43
|
+
existing_context = env['SW_APM_TRACE_STARTED'] == 'true'
|
|
44
|
+
|
|
45
|
+
if existing_context
|
|
46
|
+
# include or override tracecontext in env, so that the current context gets continued
|
|
47
|
+
add_tracecontext_headers(env)
|
|
48
|
+
else
|
|
49
|
+
SolarWindsAPM::Context.clear
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
SolarWindsAPM.transaction_name = nil
|
|
54
|
+
|
|
55
|
+
url = env['PATH_INFO']
|
|
56
|
+
options = SolarWindsAPM::XTraceOptions.new(env['HTTP_X_TRACE_OPTIONS'], env['HTTP_X_TRACE_OPTIONS_SIGNATURE'])
|
|
57
|
+
|
|
58
|
+
# store incoming information in a thread local variable
|
|
59
|
+
settings = SolarWindsAPM::TransactionSettings.new(url, env, options)
|
|
60
|
+
|
|
61
|
+
profile_spans = SolarWindsAPM::Config['profiling'] == :enabled ? 1 : -1
|
|
62
|
+
|
|
63
|
+
response =
|
|
64
|
+
propagate_tracecontext(env, settings) do
|
|
65
|
+
sample(env, settings, options, profile_spans) do
|
|
66
|
+
SolarWindsAPM::Profiling.run do
|
|
67
|
+
SolarWindsAPM::TransactionMetrics.metrics(env, settings) do
|
|
68
|
+
@app.call(env)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end || [500, {}, nil]
|
|
73
|
+
options.add_response_header(response[1], settings)
|
|
74
|
+
|
|
75
|
+
unless existing_context
|
|
76
|
+
SolarWindsAPM::Context.clear
|
|
77
|
+
SolarWindsAPM.trace_context = nil
|
|
78
|
+
end
|
|
79
|
+
response
|
|
80
|
+
rescue
|
|
81
|
+
unless existing_context
|
|
82
|
+
SolarWindsAPM::Context.clear
|
|
83
|
+
SolarWindsAPM.trace_context = nil
|
|
84
|
+
end
|
|
85
|
+
raise
|
|
86
|
+
# can't use ensure for Context.clearing, because the Grape middleware
|
|
87
|
+
# needs the context in case of an error, it is somewhat convoluted ...
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.noop?
|
|
91
|
+
false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def collect(env)
|
|
97
|
+
req = ::Rack::Request.new(env)
|
|
98
|
+
report_kvs = {}
|
|
99
|
+
|
|
100
|
+
begin
|
|
101
|
+
report_kvs[:'HTTP-Host'] = req.host
|
|
102
|
+
report_kvs[:Port] = req.port
|
|
103
|
+
report_kvs[:Proto] = req.scheme
|
|
104
|
+
report_kvs[:Method] = req.request_method
|
|
105
|
+
report_kvs[:AJAX] = true if req.xhr?
|
|
106
|
+
report_kvs[:ClientIP] = req.ip
|
|
107
|
+
|
|
108
|
+
if SolarWindsAPM::Config[:rack][:log_args]
|
|
109
|
+
report_kvs[:'Query-String'] = ::CGI.unescape(req.query_string) unless req.query_string.empty?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
report_kvs[:URL] = SolarWindsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
|
|
113
|
+
report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:rack][:collect_backtraces]
|
|
114
|
+
|
|
115
|
+
# Report any request queue'ing headers. Report as 'Request-Start' or the summed Queue-Time
|
|
116
|
+
report_kvs[:'Request-Start'] = env['HTTP_X_REQUEST_START'] if env.key?('HTTP_X_REQUEST_START')
|
|
117
|
+
report_kvs[:'Request-Start'] = env['HTTP_X_QUEUE_START'] if env.key?('HTTP_X_QUEUE_START')
|
|
118
|
+
report_kvs[:'Queue-Time'] = env['HTTP_X_QUEUE_TIME'] if env.key?('HTTP_X_QUEUE_TIME')
|
|
119
|
+
|
|
120
|
+
report_kvs[:'Forwarded-For'] = env['HTTP_X_FORWARDED_FOR'] if env.key?('HTTP_X_FORWARDED_FOR')
|
|
121
|
+
report_kvs[:'Forwarded-Host'] = env['HTTP_X_FORWARDED_HOST'] if env.key?('HTTP_X_FORWARDED_HOST')
|
|
122
|
+
report_kvs[:'Forwarded-Proto'] = env['HTTP_X_FORWARDED_PROTO'] if env.key?('HTTP_X_FORWARDED_PROTO')
|
|
123
|
+
report_kvs[:'Forwarded-Port'] = env['HTTP_X_FORWARDED_PORT'] if env.key?('HTTP_X_FORWARDED_PORT')
|
|
124
|
+
|
|
125
|
+
report_kvs[:'Ruby.SolarWindsAPM.Version'] = SolarWindsAPM::Version::STRING
|
|
126
|
+
report_kvs[:ProcessID] = Process.pid
|
|
127
|
+
report_kvs[:ThreadID] = Thread.current.to_s[/0x\w*/]
|
|
128
|
+
rescue StandardError => e
|
|
129
|
+
# Discard any potential exceptions. Debug log and report whatever we can.
|
|
130
|
+
SolarWindsAPM.logger.debug "[solarwinds_apm/debug] Rack KV collection error: #{e.inspect}"
|
|
131
|
+
end
|
|
132
|
+
report_kvs
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# this adds x-trace info to the request and return header
|
|
136
|
+
# if it is not a request for an asset (defined in config file as 'dnt')
|
|
137
|
+
def propagate_tracecontext(env, settings)
|
|
138
|
+
return yield unless settings.do_propagate
|
|
139
|
+
|
|
140
|
+
if SolarWindsAPM.trace_context&.tracestring
|
|
141
|
+
# creating a dup because we are modifying it when setting/unsetting the sampling bit
|
|
142
|
+
tracestring_dup = SolarWindsAPM.trace_context.tracestring.dup
|
|
143
|
+
if settings.do_sample
|
|
144
|
+
SolarWindsAPM::TraceString.set_sampled(tracestring_dup)
|
|
145
|
+
else
|
|
146
|
+
SolarWindsAPM::TraceString.unset_sampled(tracestring_dup)
|
|
147
|
+
end
|
|
148
|
+
env['HTTP_TRACEPARENT'] = tracestring_dup
|
|
149
|
+
env['HTTP_TRACESTATE'] = SolarWindsAPM::TraceState.add_sw_member(
|
|
150
|
+
SolarWindsAPM.trace_context&.tracestate,
|
|
151
|
+
SolarWindsAPM::TraceString.span_id_flags(tracestring_dup)
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
status, headers, response = yield
|
|
156
|
+
|
|
157
|
+
# TODO this will be finalized when we have a spec for w3c response headers
|
|
158
|
+
headers ||= {}
|
|
159
|
+
headers['X-Trace'] = SolarWindsAPM::Context.toString if SolarWindsAPM::Context.isValid
|
|
160
|
+
|
|
161
|
+
[status, headers, response]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def sample(env, settings, options, profile_spans)
|
|
165
|
+
if settings.do_sample
|
|
166
|
+
begin
|
|
167
|
+
report_kvs = collect(env)
|
|
168
|
+
settings.add_kvs(report_kvs)
|
|
169
|
+
options&.add_kvs(report_kvs, settings)
|
|
170
|
+
|
|
171
|
+
SolarWindsAPM::API.log_start(:rack, report_kvs, env, settings)
|
|
172
|
+
# mark the trace as started for this request
|
|
173
|
+
env['SW_APM_TRACE_STARTED'] = 'true'
|
|
174
|
+
|
|
175
|
+
status, headers, response = yield
|
|
176
|
+
|
|
177
|
+
SolarWindsAPM::API.log_exit(:rack, { Status: status,
|
|
178
|
+
TransactionName: SolarWindsAPM.transaction_name,
|
|
179
|
+
ProfileSpans: profile_spans })
|
|
180
|
+
|
|
181
|
+
[status, headers, response]
|
|
182
|
+
rescue Exception => e
|
|
183
|
+
# it is ok to rescue Exception here because we are reraising it (we just need a chance to log_end)
|
|
184
|
+
SolarWindsAPM::API.log_exception(:rack, e)
|
|
185
|
+
SolarWindsAPM::API.log_exit(:rack, { Status: status,
|
|
186
|
+
TransactionName: SolarWindsAPM.transaction_name,
|
|
187
|
+
ProfileSpans: profile_spans
|
|
188
|
+
})
|
|
189
|
+
raise
|
|
190
|
+
end
|
|
191
|
+
else
|
|
192
|
+
SolarWindsAPM::API.create_nontracing_context(SolarWindsAPM.trace_context.tracestring)
|
|
193
|
+
yield
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
else
|
|
200
|
+
module SolarWindsAPM
|
|
201
|
+
class Rack
|
|
202
|
+
attr_reader :app
|
|
203
|
+
|
|
204
|
+
def initialize(app)
|
|
205
|
+
@app = app
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def call(env)
|
|
209
|
+
@app.call(env)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def self.noop?
|
|
213
|
+
true
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copyright (c) 2020 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
module SolarWindsAPM
|
|
5
|
+
module RackCacheContext
|
|
6
|
+
|
|
7
|
+
###
|
|
8
|
+
# This adds a controller.action like transaction name for
|
|
9
|
+
# requests directly served from the cache without involving a controller.
|
|
10
|
+
# The resulting transaction name is `rack-cache.<cache-store>`,
|
|
11
|
+
# e.g. `rack-cache.memcached`
|
|
12
|
+
#
|
|
13
|
+
# It is not a full instrumentation, no span is added.
|
|
14
|
+
#
|
|
15
|
+
def call!(env)
|
|
16
|
+
metastore_type = begin
|
|
17
|
+
if options['rack-cache.metastore']
|
|
18
|
+
options['rack-cache.metastore'].match(/^([^\:]*)\:/)[1]
|
|
19
|
+
end || 'unknown_store'
|
|
20
|
+
rescue
|
|
21
|
+
'unknown_store'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
env['solarwinds_apm.action'] = metastore_type
|
|
25
|
+
env['solarwinds_apm.controller'] = 'rack-cache'
|
|
26
|
+
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if SolarWindsAPM::Config[:rack_cache][:transaction_name] && defined?(Rack::Cache::Context)
|
|
33
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting rack_cache' if SolarWindsAPM::Config[:verbose]
|
|
34
|
+
Rack::Cache::Context.send(:prepend, ::SolarWindsAPM::RackCacheContext)
|
|
35
|
+
end
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
module SolarWindsAPM
|
|
5
|
+
module Inst
|
|
6
|
+
module Redis
|
|
7
|
+
module Client
|
|
8
|
+
# The operations listed in this constant skip collecting KVKey
|
|
9
|
+
NO_KEY_OPS = [:auth, :keys, :randomkey, :scan, :sdiff, :sdiffstore, :sinter,
|
|
10
|
+
:sinterstore, :smove, :sunion, :sunionstore, :zinterstore,
|
|
11
|
+
:zunionstore, :publish, :select, :eval, :evalsha, :script].freeze
|
|
12
|
+
|
|
13
|
+
# Instead of a giant switch statement, we use a hash constant to map out what
|
|
14
|
+
# KVs need to be collected for each of the many many Redis operations
|
|
15
|
+
# Hash formatting by undiagnosed OCD
|
|
16
|
+
KV_COLLECT_MAP = {
|
|
17
|
+
:brpoplpush => { :destination => 2 }, :rpoplpush => { :destination => 2 },
|
|
18
|
+
:sdiffstore => { :destination => 1 }, :sinterstore => { :destination => 1 },
|
|
19
|
+
:sunionstore => { :destination => 1 }, :zinterstore => { :destination => 1 },
|
|
20
|
+
:zunionstore => { :destination => 1 }, :publish => { :channel => 1 },
|
|
21
|
+
:incrby => { :increment => 2 }, :incrbyfloat => { :increment => 2 },
|
|
22
|
+
:pexpire => { :milliseconds => 2 }, :pexpireat => { :milliseconds => 2 },
|
|
23
|
+
:expireat => { :timestamp => 2 }, :decrby => { :decrement => 2 },
|
|
24
|
+
:psetex => { :ttl => 2 }, :restore => { :ttl => 2 },
|
|
25
|
+
:setex => { :ttl => 2 }, :setnx => { :ttl => 2 },
|
|
26
|
+
:move => { :db => 2 }, :select => { :db => 1 },
|
|
27
|
+
:lindex => { :index => 2 }, :getset => { :value => 2 },
|
|
28
|
+
:keys => { :pattern => 1 }, :expire => { :seconds => 2 },
|
|
29
|
+
:rename => { :newkey => 2 }, :renamenx => { :newkey => 2 },
|
|
30
|
+
:getbit => { :offset => 2 }, :setbit => { :offset => 2 },
|
|
31
|
+
:setrange => { :offset => 2 }, :evalsha => { :sha => 1 },
|
|
32
|
+
:getrange => { :start => 2, :end => 3 },
|
|
33
|
+
:zrange => { :start => 2, :end => 3 },
|
|
34
|
+
:bitcount => { :start => 2, :stop => 3 },
|
|
35
|
+
:lrange => { :start => 2, :stop => 3 },
|
|
36
|
+
:zrevrange => { :start => 2, :stop => 3 },
|
|
37
|
+
:hincrby => { :field => 2, :increment => 3 },
|
|
38
|
+
:smove => { :source => 1, :destination => 2 },
|
|
39
|
+
:bitop => { :operation => 1, :destkey => 2 },
|
|
40
|
+
:hincrbyfloat => { :field => 2, :increment => 3 },
|
|
41
|
+
:zremrangebyrank => { :start => 2, :stop => 3 }
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
# The following operations don't require any special handling. For these,
|
|
45
|
+
# we only collect KVKey and KVOp
|
|
46
|
+
#
|
|
47
|
+
# :append, :blpop, :brpop, :decr, :del, :dump, :exists,
|
|
48
|
+
# :hgetall, :hkeys, :hlen, :hvals, :hmset, :incr, :linsert,
|
|
49
|
+
# :llen, :lpop, :lpush, :lpushx, :lrem, :lset, :ltrim,
|
|
50
|
+
# :persist, :pttl, :hscan, :rpop, :rpush, :rpushx, :sadd,
|
|
51
|
+
# :scard, :sismember, :smembers, :strlen, :sort, :spop,
|
|
52
|
+
# :srandmember, :srem, :sscan, :ttl, :type, :zadd, :zcard,
|
|
53
|
+
# :zcount, :zincrby, :zrangebyscore, :zrank, :zrem,
|
|
54
|
+
# :zremrangebyscore, :zrevrank, :zrevrangebyscore, :zscore
|
|
55
|
+
#
|
|
56
|
+
# For the operations in NO_KEY_OPS (above) we only collect
|
|
57
|
+
# KVOp (no KVKey)
|
|
58
|
+
|
|
59
|
+
def self.included(klass)
|
|
60
|
+
# We wrap two of the Redis methods to instrument
|
|
61
|
+
# operations
|
|
62
|
+
SolarWindsAPM::Util.method_alias(klass, :call, ::Redis::Client)
|
|
63
|
+
SolarWindsAPM::Util.method_alias(klass, :call_pipeline, ::Redis::Client)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Given any Redis operation command array, this method
|
|
67
|
+
# extracts the Key/Values to report to the SolarWinds # dashboard.
|
|
68
|
+
#
|
|
69
|
+
# @param command [Array] the Redis operation array
|
|
70
|
+
# @param r [Return] the return value from the operation
|
|
71
|
+
# @return [Hash] the Key/Values to report
|
|
72
|
+
def extract_trace_details(command, r)
|
|
73
|
+
kvs = {}
|
|
74
|
+
op = command.first
|
|
75
|
+
|
|
76
|
+
kvs[:KVOp] = command[0]
|
|
77
|
+
kvs[:RemoteHost] = @options[:host]
|
|
78
|
+
unless NO_KEY_OPS.include?(op) || op == :del && command[1..-1].flatten.count > 1
|
|
79
|
+
if command[1].is_a?(Array)
|
|
80
|
+
kvs[:KVKey] = command[1].first
|
|
81
|
+
else
|
|
82
|
+
kvs[:KVKey] = command[1]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if KV_COLLECT_MAP[op]
|
|
87
|
+
# Extract KVs from command for this op
|
|
88
|
+
KV_COLLECT_MAP[op].each { |k, v| kvs[k] = command[v] }
|
|
89
|
+
else
|
|
90
|
+
# This case statement handle special cases not handled
|
|
91
|
+
# by KV_COLLECT_MAP
|
|
92
|
+
case op
|
|
93
|
+
when :set
|
|
94
|
+
if command.count > 3
|
|
95
|
+
if command[3].is_a?(Hash)
|
|
96
|
+
options = command[3]
|
|
97
|
+
kvs[:ex] = options[:ex] if options.key?(:ex)
|
|
98
|
+
kvs[:px] = options[:px] if options.key?(:px)
|
|
99
|
+
kvs[:nx] = options[:nx] if options.key?(:nx)
|
|
100
|
+
kvs[:xx] = options[:xx] if options.key?(:xx)
|
|
101
|
+
else
|
|
102
|
+
options = command[3..-1]
|
|
103
|
+
until (opts = options.shift(2)).empty?
|
|
104
|
+
case opts[0]
|
|
105
|
+
when 'EX' then; kvs[:ex] = opts[1]
|
|
106
|
+
when 'PX' then; kvs[:px] = opts[1]
|
|
107
|
+
when 'NX' then; kvs[:nx] = opts[1]
|
|
108
|
+
when 'XX' then; kvs[:xx] = opts[1]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
when :get
|
|
115
|
+
kvs[:KVHit] = r.nil? ? 0 : 1
|
|
116
|
+
|
|
117
|
+
when :hdel, :hexists, :hget, :hset, :hsetnx
|
|
118
|
+
kvs[:field] = command[2] unless command[2].is_a?(Array)
|
|
119
|
+
if op == :hget
|
|
120
|
+
kvs[:KVHit] = r.nil? ? 0 : 1
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
when :eval
|
|
124
|
+
if command[1].length > 1024
|
|
125
|
+
kvs[:Script] = command[1][0..1023] + '(...snip...)'
|
|
126
|
+
else
|
|
127
|
+
kvs[:Script] = command[1]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
when :script
|
|
131
|
+
kvs[:subcommand] = command[1]
|
|
132
|
+
kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:redis][:collect_backtraces]
|
|
133
|
+
if command[1] == 'load'
|
|
134
|
+
if command[1].length > 1024
|
|
135
|
+
kvs[:Script] = command[2][0..1023] + '(...snip...)'
|
|
136
|
+
else
|
|
137
|
+
kvs[:Script] = command[2]
|
|
138
|
+
end
|
|
139
|
+
elsif command[1] == :exists
|
|
140
|
+
if command[2].is_a?(Array)
|
|
141
|
+
kvs[:KVKey] = command[2].inspect
|
|
142
|
+
else
|
|
143
|
+
kvs[:KVKey] = command[2]
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
when :mget
|
|
148
|
+
if command[1].is_a?(Array)
|
|
149
|
+
kvs[:KVKeyCount] = command[1].count
|
|
150
|
+
else
|
|
151
|
+
kvs[:KVKeyCount] = command.count - 1
|
|
152
|
+
end
|
|
153
|
+
values = r.select { |i| i }
|
|
154
|
+
kvs[:KVHitCount] = values.count
|
|
155
|
+
|
|
156
|
+
when :hmget
|
|
157
|
+
kvs[:KVKeyCount] = command.count - 2
|
|
158
|
+
values = r.select { |i| i }
|
|
159
|
+
kvs[:KVHitCount] = values.count
|
|
160
|
+
|
|
161
|
+
when :mset, :msetnx
|
|
162
|
+
if command[1].is_a?(Array)
|
|
163
|
+
kvs[:KVKeyCount] = command[1].count / 2
|
|
164
|
+
else
|
|
165
|
+
kvs[:KVKeyCount] = (command.count - 1) / 2
|
|
166
|
+
end
|
|
167
|
+
end # case op
|
|
168
|
+
end # if KV_COLLECT_MAP[op]
|
|
169
|
+
rescue StandardError => e
|
|
170
|
+
SolarWindsAPM.logger.debug "[solarwinds_apm/redis] Error collecting redis KVs: #{e.message}"
|
|
171
|
+
SolarWindsAPM.logger.debug e.backtrace.join('\n')
|
|
172
|
+
ensure
|
|
173
|
+
return kvs
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Extracts the Key/Values to report from a pipelined
|
|
177
|
+
# call to the SolarWinds dashboard.
|
|
178
|
+
#
|
|
179
|
+
# @param pipeline [Redis::Pipeline] the Redis pipeline instance
|
|
180
|
+
# @return [Hash] the Key/Values to report
|
|
181
|
+
def extract_pipeline_details(pipeline)
|
|
182
|
+
kvs = {}
|
|
183
|
+
|
|
184
|
+
kvs[:RemoteHost] = @options[:host]
|
|
185
|
+
kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:redis][:collect_backtraces]
|
|
186
|
+
|
|
187
|
+
command_count = pipeline.commands.count
|
|
188
|
+
kvs[:KVOpCount] = command_count
|
|
189
|
+
|
|
190
|
+
kvs[:KVOp] = if pipeline.commands.first == :multi
|
|
191
|
+
:multi
|
|
192
|
+
else
|
|
193
|
+
:pipeline
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Report pipelined operations if the number
|
|
197
|
+
# of ops is reasonable
|
|
198
|
+
if command_count < 12
|
|
199
|
+
ops = []
|
|
200
|
+
pipeline.commands.each do |c|
|
|
201
|
+
ops << c.first
|
|
202
|
+
end
|
|
203
|
+
kvs[:KVOps] = ops.join(', ')
|
|
204
|
+
end
|
|
205
|
+
rescue StandardError => e
|
|
206
|
+
SolarWindsAPM.logger.debug "[solarwinds_apm/debug] Error extracting pipelined commands: #{e.message}"
|
|
207
|
+
SolarWindsAPM.logger.debug e.backtrace
|
|
208
|
+
ensure
|
|
209
|
+
return kvs
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
#
|
|
213
|
+
# The wrapper method for Redis::Client.call. Here
|
|
214
|
+
# (when tracing) we capture KVs to report and pass
|
|
215
|
+
# the call along
|
|
216
|
+
#
|
|
217
|
+
def call_with_sw_apm(command, &block)
|
|
218
|
+
if SolarWindsAPM.tracing?
|
|
219
|
+
SolarWindsAPM::API.log_entry(:redis, {})
|
|
220
|
+
|
|
221
|
+
begin
|
|
222
|
+
r = call_without_sw_apm(command, &block)
|
|
223
|
+
report_kvs = extract_trace_details(command, r)
|
|
224
|
+
r
|
|
225
|
+
rescue StandardError => e
|
|
226
|
+
SolarWindsAPM::API.log_exception(:redis, e)
|
|
227
|
+
raise
|
|
228
|
+
ensure
|
|
229
|
+
SolarWindsAPM::API.log_exit(:redis, report_kvs)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
else
|
|
233
|
+
call_without_sw_apm(command, &block)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
#
|
|
238
|
+
# The wrapper method for Redis::Client.call_pipeline. Here
|
|
239
|
+
# (when tracing) we capture KVs to report and pass the call along
|
|
240
|
+
#
|
|
241
|
+
def call_pipeline_with_sw_apm(pipeline)
|
|
242
|
+
if SolarWindsAPM.tracing?
|
|
243
|
+
# Fall back to the raw tracing API so we can pass KVs
|
|
244
|
+
# back on exit (a limitation of the SolarWindsAPM::API.trace
|
|
245
|
+
# block method) This removes the need for an info
|
|
246
|
+
# event to send additonal KVs
|
|
247
|
+
SolarWindsAPM::API.log_entry(:redis, {})
|
|
248
|
+
|
|
249
|
+
report_kvs = extract_pipeline_details(pipeline)
|
|
250
|
+
|
|
251
|
+
begin
|
|
252
|
+
call_pipeline_without_sw_apm(pipeline)
|
|
253
|
+
rescue StandardError => e
|
|
254
|
+
SolarWindsAPM::API.log_exception(:redis, e)
|
|
255
|
+
raise
|
|
256
|
+
ensure
|
|
257
|
+
SolarWindsAPM::API.log_exit(:redis, report_kvs)
|
|
258
|
+
end
|
|
259
|
+
else
|
|
260
|
+
call_pipeline_without_sw_apm(pipeline)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
if SolarWindsAPM::Config[:redis][:enabled]
|
|
269
|
+
if defined?(Redis) && Gem::Version.new(Redis::VERSION) >= Gem::Version.new('3.0.0')
|
|
270
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting redis' if SolarWindsAPM::Config[:verbose]
|
|
271
|
+
SolarWindsAPM::Util.send_include(Redis::Client, SolarWindsAPM::Inst::Redis::Client)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
require 'socket'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module SolarWindsAPM
|
|
8
|
+
module Inst
|
|
9
|
+
module ResqueClient
|
|
10
|
+
|
|
11
|
+
self.include SolarWindsAPM::SDK::TraceContextHeaders
|
|
12
|
+
|
|
13
|
+
def extract_trace_details(klass, args)
|
|
14
|
+
report_kvs = {}
|
|
15
|
+
|
|
16
|
+
begin
|
|
17
|
+
report_kvs[:Spec] = :pushq
|
|
18
|
+
report_kvs[:Flavor] = :resque
|
|
19
|
+
report_kvs[:JobName] = klass.to_s
|
|
20
|
+
|
|
21
|
+
if SolarWindsAPM::Config[:resqueclient][:log_args]
|
|
22
|
+
kv_args = args.to_json
|
|
23
|
+
|
|
24
|
+
# Limit the argument json string to 1024 bytes
|
|
25
|
+
if kv_args.length > 1024
|
|
26
|
+
report_kvs[:Args] = kv_args[0..1023] + '...[snipped]'
|
|
27
|
+
else
|
|
28
|
+
report_kvs[:Args] = kv_args
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:resqueclient][:collect_backtraces]
|
|
32
|
+
report_kvs[:Queue] = klass.instance_variable_get(:@queue)
|
|
33
|
+
rescue => e
|
|
34
|
+
SolarWindsAPM.logger.debug "[solarwinds_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" if SolarWindsAPM::Config[:verbose]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
report_kvs
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def push(queue, item)
|
|
41
|
+
if SolarWindsAPM.tracing?
|
|
42
|
+
report_kvs = extract_trace_details(item[:class], item[:args])
|
|
43
|
+
report_kvs[:Queue] = queue.to_s if queue
|
|
44
|
+
|
|
45
|
+
SolarWindsAPM::SDK.trace(:'resque-client', kvs: report_kvs) do
|
|
46
|
+
add_tracecontext_headers(item)
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def dequeue(klass, *args)
|
|
55
|
+
if SolarWindsAPM.tracing?
|
|
56
|
+
report_kvs = extract_trace_details(klass, args)
|
|
57
|
+
SolarWindsAPM::SDK.trace(:'resque-client', kvs: report_kvs) do
|
|
58
|
+
super(klass, *args)
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
super(klass, *args)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
module ResqueJob
|
|
67
|
+
|
|
68
|
+
def perform
|
|
69
|
+
report_kvs = {}
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
report_kvs[:Spec] = :job
|
|
73
|
+
report_kvs[:Flavor] = :resque
|
|
74
|
+
report_kvs[:JobName] = payload['class'].to_s
|
|
75
|
+
report_kvs[:Queue] = queue.to_s
|
|
76
|
+
|
|
77
|
+
# Set these keys for the ability to separate out
|
|
78
|
+
# background tasks into a separate app on the server-side UI
|
|
79
|
+
|
|
80
|
+
report_kvs[:'HTTP-Host'] = Socket.gethostname
|
|
81
|
+
report_kvs[:Controller] = "Resque_#{queue}"
|
|
82
|
+
report_kvs[:Action] = payload['class'].to_s
|
|
83
|
+
report_kvs[:URL] = "/resque/#{queue}/#{payload['class']}"
|
|
84
|
+
|
|
85
|
+
if SolarWindsAPM::Config[:resqueworker][:log_args]
|
|
86
|
+
kv_args = payload['args'].to_json
|
|
87
|
+
|
|
88
|
+
# Limit the argument json string to 1024 bytes
|
|
89
|
+
if kv_args.length > 1024
|
|
90
|
+
report_kvs[:Args] = kv_args[0..1023] + '...[snipped]'
|
|
91
|
+
else
|
|
92
|
+
report_kvs[:Args] = kv_args
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:resqueworker][:collect_backtraces]
|
|
97
|
+
rescue => e
|
|
98
|
+
SolarWindsAPM.logger.debug "[solarwinds_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" if SolarWindsAPM::Config[:verbose]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
SolarWindsAPM::SDK.start_trace('resque-worker', kvs: report_kvs, headers: payload) do
|
|
102
|
+
super
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def fail(exception)
|
|
106
|
+
if SolarWindsAPM.tracing?
|
|
107
|
+
SolarWindsAPM::API.log_exception(:resque, exception)
|
|
108
|
+
end
|
|
109
|
+
super(exception)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if defined?(::Resque)
|
|
117
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting resque' if SolarWindsAPM::Config[:verbose]
|
|
118
|
+
|
|
119
|
+
if SolarWindsAPM::Config[:resqueclient][:enabled]
|
|
120
|
+
::Resque.singleton_class.prepend(SolarWindsAPM::Inst::ResqueClient)
|
|
121
|
+
::Resque.singleton_class.prepend(SolarWindsAPM::Inst::ResqueClient)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
if SolarWindsAPM::Config[:resqueclient][:enabled] || SolarWindsAPM::Config[:resqueworker][:enabled]
|
|
125
|
+
Resque::Job.prepend(SolarWindsAPM::Inst::ResqueJob)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
module SolarWindsAPM
|
|
5
|
+
module Inst
|
|
6
|
+
module RestClientRequest
|
|
7
|
+
include SolarWindsAPM::SDK::TraceContextHeaders
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# execute
|
|
11
|
+
#
|
|
12
|
+
# The wrapper method for RestClient::Request.execute
|
|
13
|
+
#
|
|
14
|
+
def execute(&block)
|
|
15
|
+
unless SolarWindsAPM.tracing?
|
|
16
|
+
add_tracecontext_headers(@processed_headers)
|
|
17
|
+
return super(&block)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
kvs = {}
|
|
22
|
+
kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:rest_client][:collect_backtraces]
|
|
23
|
+
SolarWindsAPM::API.log_entry('rest-client', kvs)
|
|
24
|
+
|
|
25
|
+
add_tracecontext_headers(@processed_headers)
|
|
26
|
+
|
|
27
|
+
# The core rest-client call
|
|
28
|
+
super(&block)
|
|
29
|
+
rescue => e
|
|
30
|
+
SolarWindsAPM::API.log_exception('rest-client', e)
|
|
31
|
+
raise e
|
|
32
|
+
ensure
|
|
33
|
+
SolarWindsAPM::API.log_exit('rest-client')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if defined?(RestClient) && SolarWindsAPM::Config[:rest_client][:enabled]
|
|
41
|
+
SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting rest-client' if SolarWindsAPM::Config[:verbose]
|
|
42
|
+
RestClient::Request.prepend(SolarWindsAPM::Inst::RestClientRequest)
|
|
43
|
+
end
|