appoptics_apm 4.5.2 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) 2016 SolarWinds, LLC.
2
2
  # All rights reserved.
3
3
 
4
+ require_relative 'support/transaction_settings'
5
+
4
6
  module AppOpticsAPM
5
7
  ##
6
8
  # This module exposes a nested configuration hash that can be used to
@@ -48,8 +50,7 @@ module AppOpticsAPM
48
50
  elsif File.exist?(File.join(ENV['APPOPTICS_APM_CONFIG_RUBY'], 'appoptics_apm_config.rb'))
49
51
  config_files << File.join(ENV['APPOPTICS_APM_CONFIG_RUBY'], 'appoptics_apm_config.rb')
50
52
  else
51
- $stderr.puts 'Could not find the configuration file set by the APPOPTICS_APM_CONFIG_RUBY environment variable:'
52
- $stderr.puts "#{ENV['APPOPTICS_APM_CONFIG_RUBY']}"
53
+ AppOpticsAPM.logger.warn "[appoptics_apm/config] Could not find the configuration file set by the APPOPTICS_APM_CONFIG_RUBY environment variable: #{ENV['APPOPTICS_APM_CONFIG_RUBY']}"
53
54
  end
54
55
  end
55
56
 
@@ -60,8 +61,10 @@ module AppOpticsAPM
60
61
  return if config_files.empty? # we use the defaults from the template in this case
61
62
 
62
63
  if config_files.size > 1
63
- $stderr.puts 'Found multiple configuration files, using the first one listed:'
64
- config_files.each { |path| $stderr.puts " #{path}" }
64
+ AppOpticsAPM.logger.warn [
65
+ '[appoptics_apm/config] Multiple configuration files configured, using the first one listed: ',
66
+ config_files.join(', ')
67
+ ].join(' ')
65
68
  end
66
69
  load(config_files[0])
67
70
  check_env_vars
@@ -174,11 +177,12 @@ module AppOpticsAPM
174
177
  #
175
178
  # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
176
179
  def self.[]=(key, value)
177
- @@config[key.to_sym] = value
180
+ key = key.to_sym
181
+ @@config[key] = value
178
182
 
179
183
  if key == :sampling_rate
180
184
  AppOpticsAPM.logger.warn '[appoptics_apm/config] sampling_rate is not a supported setting for AppOpticsAPM::Config. ' \
181
- 'Please use :sample_rate.'
185
+ 'Please use :sample_rate.'
182
186
 
183
187
  elsif key == :sample_rate
184
188
  unless value.is_a?(Integer) || value.is_a?(Float)
@@ -202,6 +206,27 @@ module AppOpticsAPM
202
206
  elsif key == :action_blacklist
203
207
  AppOpticsAPM.logger.warn "[appoptics_apm/config] :action_blacklist has been deprecated and no longer functions."
204
208
 
209
+ elsif key == :dnt_regexp
210
+ if value.nil? || value == ''
211
+ @@config[:dnt_compiled] = nil
212
+ else
213
+ @@config[:dnt_compiled] =
214
+ Regexp.new(AppOpticsAPM::Config[:dnt_regexp], AppOpticsAPM::Config[:dnt_opts] || nil)
215
+ end
216
+
217
+ elsif key == :dnt_opts
218
+ if AppOpticsAPM::Config[:dnt_regexp] && AppOpticsAPM::Config[:dnt_regexp] != ''
219
+ @@config[:dnt_compiled] =
220
+ Regexp.new(AppOpticsAPM::Config[:dnt_regexp], AppOpticsAPM::Config[:dnt_opts] || nil)
221
+ end
222
+
223
+ elsif key == :transaction_settings
224
+ if value.is_a?(Hash)
225
+ AppOpticsAPM::TransactionSettings.compile_url_settings(value[:url])
226
+ else
227
+ AppOpticsAPM::TransactionSettings.reset_url_regexps
228
+ end
229
+
205
230
  elsif key == :resque
206
231
  AppOpticsAPM.logger.warn "[appoptics_apm/config] :resque config is deprecated. It is now split into :resqueclient and :resqueworker."
207
232
  AppOpticsAPM.logger.warn "[appoptics_apm/config] Called from #{Kernel.caller[0]}"
@@ -228,6 +253,8 @@ module AppOpticsAPM
228
253
  end
229
254
  # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
230
255
 
256
+
257
+
231
258
  def self.method_missing(sym, *args)
232
259
  class_var_name = "@@#{sym}"
233
260
 
@@ -291,6 +291,7 @@ module AppOpticsAPM
291
291
  # If we're not tracing or we're already tracing curb, just do a fast return.
292
292
  if !AppOpticsAPM.tracing? || [:curb, :curb_multi].include?(AppOpticsAPM.layer)
293
293
  self.requests.each do |request|
294
+ request = request[1] if request.is_a?(Array)
294
295
  unless AppOpticsAPM::API.blacklisted?(URI(request.url).hostname)
295
296
  request.headers['X-Trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::Context.isValid
296
297
  end
@@ -305,6 +306,7 @@ module AppOpticsAPM
305
306
  AppOpticsAPM::API.log_entry(:curb_multi, kvs)
306
307
 
307
308
  self.requests.each do |request|
309
+ request = request[1] if request.is_a?(Array)
308
310
  unless AppOpticsAPM::API.blacklisted?(URI(request.url).hostname)
309
311
  request.headers['X-Trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::Context.isValid
310
312
  end
@@ -17,7 +17,7 @@ if AppOpticsAPM.loaded
17
17
  # After the rack layer passes on to the following layers (Rails, Sinatra,
18
18
  # Padrino, Grape), then the instrumentation downstream will
19
19
  # automatically detect whether this is a sampled request or not
20
- # and act accordingly. (to instrument or not)
20
+ # and act accordingly.
21
21
  #
22
22
  class Rack
23
23
  attr_reader :app
@@ -27,17 +27,37 @@ if AppOpticsAPM.loaded
27
27
  end
28
28
 
29
29
  def call(env)
30
+ incoming = AppOpticsAPM::Context.isValid
31
+
30
32
  # In the case of nested Ruby apps such as Grape inside of Rails
31
33
  # or Grape inside of Grape, each app has it's own instance
32
- # of rack middleware. We avoid tracing rack more than once and
33
- # instead start instrumenting from the first rack pass.
34
- return call_app(env) if AppOpticsAPM.tracing? && AppOpticsAPM.layer == :rack
34
+ # of rack middleware. We want to avoid tracing rack more than once
35
+ return @app.call(env) if AppOpticsAPM.tracing? && AppOpticsAPM.layer == :rack
35
36
 
36
- # if we already have a context, we don't want to send metrics in the end
37
- return sampling_call(env) if AppOpticsAPM::Context.isValid
37
+ AppOpticsAPM.transaction_name = nil
38
38
 
39
- # else we also send metrics
40
- metrics_sampling_call(env)
39
+ url = env['PATH_INFO']
40
+ xtrace = AppOpticsAPM::XTrace.valid?(env['HTTP_X_TRACE']) ? (env['HTTP_X_TRACE']) : nil
41
+
42
+ settings = AppOpticsAPM::TransactionSettings.new(url, xtrace)
43
+
44
+ # AppOpticsAPM.logger.warn "%%% FILTER: #{settings} %%%"
45
+
46
+ response =
47
+ propagate_xtrace(env, settings, xtrace) do
48
+ sample(env, settings) do
49
+ AppOpticsAPM::TransactionMetrics.metrics(env, settings) do
50
+ @app.call(env)
51
+ end
52
+ end
53
+ end || [500, {}, nil]
54
+ AppOpticsAPM::Context.clear unless incoming
55
+ response
56
+ rescue
57
+ AppOpticsAPM::Context.clear unless incoming
58
+ raise
59
+ # can't use ensure for Context.clearing, because the Grape middleware
60
+ # needs the context in case of an error, it is somewhat convoluted ...
41
61
  end
42
62
 
43
63
  def self.noop?
@@ -46,7 +66,8 @@ if AppOpticsAPM.loaded
46
66
 
47
67
  private
48
68
 
49
- def collect(req, env)
69
+ def collect(env, settings)
70
+ req = ::Rack::Request.new(env)
50
71
  report_kvs = {}
51
72
 
52
73
  begin
@@ -61,7 +82,10 @@ if AppOpticsAPM.loaded
61
82
  report_kvs[:'Query-String'] = ::CGI.unescape(req.query_string) unless req.query_string.empty?
62
83
  end
63
84
 
64
- report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:rack][:collect_backtraces]
85
+ report_kvs[:URL] = AppOpticsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
86
+ report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:rack][:collect_backtraces]
87
+ report_kvs[:SampleRate] = settings.rate
88
+ report_kvs[:SampleSource] = settings.source
65
89
 
66
90
  # Report any request queue'ing headers. Report as 'Request-Start' or the summed Queue-Time
67
91
  report_kvs[:'Request-Start'] = env['HTTP_X_REQUEST_START'] if env.key?('HTTP_X_REQUEST_START')
@@ -83,96 +107,48 @@ if AppOpticsAPM.loaded
83
107
  report_kvs
84
108
  end
85
109
 
86
- def call_app(env)
87
- AppOpticsAPM.logger.debug "[appoptics_apm/rack] Rack skipped!"
88
- @app.call(env)
89
- end
110
+ def propagate_xtrace(env, settings, xtrace)
111
+ return yield unless settings.do_propagate
90
112
 
91
- # in this case we have an existing context
92
- def sampling_call(env)
93
- req = ::Rack::Request.new(env)
94
- report_kvs = {}
95
- report_kvs[:URL] = AppOpticsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
96
-
97
- AppOpticsAPM::API.trace(:rack, report_kvs) do
98
- report_kvs = collect(req, env)
99
-
100
- # We log an info event with the HTTP KVs found in AppOpticsAPM::Rack.collect
101
- # This is done here so in the case of stacks that try/catch/abort
102
- # (looking at you Grape) we're sure the KVs get reported now as
103
- # this code may not be returned to later.
104
- AppOpticsAPM::API.log_info(:rack, report_kvs)
105
- @app.call(env)
113
+ if xtrace
114
+ xtrace_local = xtrace.dup
115
+ AppOpticsAPM::XTrace.unset_sampled(xtrace_local) unless settings.do_sample
116
+ env['HTTP_X_TRACE'] = xtrace_local
106
117
  end
107
- end
108
-
109
- def metrics_sampling_call(env)
110
- start = Time.now
111
- AppOpticsAPM.transaction_name = nil
112
- req = ::Rack::Request.new(env)
113
- req_url = req.url # saving it here because rails3.2 overrides it when there is a 500 error
114
- status = 500 # initialize with 500
115
-
116
- report_kvs = collect(req, env)
117
- report_kvs[:URL] = AppOpticsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
118
118
 
119
- # Check for and validate X-Trace request header to pick up tracing context
120
- xtrace = env.is_a?(Hash) ? env['HTTP_X_TRACE'] : nil
121
- xtrace = AppOpticsAPM::XTrace.valid?(xtrace) ? xtrace : nil
119
+ status, headers, response = yield
122
120
 
123
- # TODO JRUBY
124
- # Under JRuby, JAppOpticsAPM may have already started a trace. Make note of this
125
- # if so and don't clear context on log_end (see appoptics_apm/api/logging.rb)
126
- # AppOpticsAPM.has_incoming_context = AppOpticsAPM.tracing?
127
- # AppOpticsAPM.has_xtrace_header = xtrace
128
- # AppOpticsAPM.is_continued_trace = AppOpticsAPM.has_incoming_context || AppOpticsAPM.has_xtrace_header
129
-
130
- AppOpticsAPM::API.log_start(:rack, xtrace, report_kvs) unless AppOpticsAPM::Util.static_asset?(env['PATH_INFO'])
131
-
132
- status, headers, response = @app.call(env)
133
- confirmed_transaction_name = send_metrics(env, req, req_url, start, status)
134
- xtrace = AppOpticsAPM::API.log_end(:rack, :Status => status, :TransactionName => confirmed_transaction_name)
135
-
136
- # TODO revisit this JRUBY condition
137
- # headers['X-Trace'] = xtrace if headers.is_a?(Hash) unless defined?(JRUBY_VERSION) && AppOpticsAPM.is_continued_trace?
138
- headers['X-Trace'] = xtrace if headers.is_a?(Hash)
121
+ headers ||= {}
122
+ headers['X-Trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::Context.isValid
123
+ headers['X-Trace'] ||= xtrace if xtrace
124
+ headers['X-Trace'] && AppOpticsAPM::XTrace.unset_sampled(headers['X-Trace']) unless settings.do_sample
139
125
 
140
126
  [status, headers, response]
141
- rescue Exception => e
142
- # it is ok to rescue Exception here because we are reraising it (we just need a chance to log_end)
143
- AppOpticsAPM::API.log_exception(:rack, e)
144
- confirmed_transaction_name ||= send_metrics(env, req, req_url, start, status)
145
- xtrace = AppOpticsAPM::API.log_end(:rack, :Status => status, :TransactionName => confirmed_transaction_name)
146
-
147
- # TODO revisit this JRUBY condition
148
- # headers['X-Trace'] = xtrace if headers.is_a?(Hash) unless defined?(JRUBY_VERSION) && AppOpticsAPM.is_continued_trace?
149
- headers['X-Trace'] = xtrace if headers.is_a?(Hash)
150
-
151
- raise
152
- end
153
-
154
-
155
- def send_metrics(env, req, req_url, start, status)
156
- return if AppOpticsAPM::Util.static_asset?(env['PATH_INFO'])
157
-
158
- status = status.to_i
159
- error = status.between?(500,599) ? 1 : 0
160
- duration =(1000 * 1000 * (Time.now - start)).round(0)
161
- method = req.request_method
162
- AppOpticsAPM::Span.createHttpSpan(transaction_name(env), req_url, domain(req), duration, status, method, error) || ''
163
127
  end
164
128
 
165
- def domain(req)
166
- if AppOpticsAPM::Config['transaction_name']['prepend_domain']
167
- [80, 443].include?(req.port) ? req.host : "#{req.host}:#{req.port}"
168
- end
169
- end
170
-
171
- def transaction_name(env)
172
- if AppOpticsAPM.transaction_name
173
- AppOpticsAPM.transaction_name
174
- elsif env['appoptics_apm.controller'] && env['appoptics_apm.action']
175
- [env['appoptics_apm.controller'], env['appoptics_apm.action']].join('.')
129
+ def sample(env, settings)
130
+ xtrace = env['HTTP_X_TRACE']
131
+ if settings.do_sample
132
+ begin
133
+ report_kvs = collect(env, settings)
134
+
135
+ AppOpticsAPM::API.log_start(:rack, xtrace, report_kvs, settings)
136
+
137
+ status, headers, response = yield
138
+
139
+ AppOpticsAPM::API.log_exit(:rack, { Status: status,
140
+ TransactionName: AppOpticsAPM.transaction_name })
141
+ [status, headers, response]
142
+ rescue Exception => e
143
+ # it is ok to rescue Exception here because we are reraising it (we just need a chance to log_end)
144
+ AppOpticsAPM::API.log_exception(:rack, e)
145
+ AppOpticsAPM::API.log_exit(:rack, { Status: status,
146
+ TransactionName: AppOpticsAPM.transaction_name })
147
+ raise
148
+ end
149
+ else
150
+ AppOpticsAPM::API.create_nontracing_context(xtrace)
151
+ yield
176
152
  end
177
153
  end
178
154
 
@@ -8,35 +8,37 @@ module AppOpticsAPM
8
8
  attr_accessor :logger
9
9
  end
10
10
 
11
- class Logger
12
- # Fatal message
13
- def fatal(string, exception = nil)
14
- AppOpticsAPM.logger.fatal(string) if AppOpticsAPM.logger
15
- end
16
-
17
- # Error message
18
- def error(msg, exception = nil)
19
- AppOpticsAPM.logger.error(string) if AppOpticsAPM.logger
20
- end
21
-
22
- # Warn message
23
- def warn(msg, exception = nil)
24
- AppOpticsAPM.logger.warn(string) if AppOpticsAPM.logger
25
- end
26
-
27
- # Info message
28
- def info(msg, exception = nil)
29
- AppOpticsAPM.logger.info(string) if AppOpticsAPM.logger
30
- end
31
-
32
- # Debug message
33
- def debug(msg, exception = nil)
34
- AppOpticsAPM.logger.debug(string) if AppOpticsAPM.logger
35
- end
36
-
37
- end
11
+ # TODO ME currently unused, keeping it around for xtrace logging epic
12
+ # class Logger
13
+ # # Fatal message
14
+ # def fatal(msg, exception = nil)
15
+ # AppOpticsAPM.logger.fatal(msg) if AppOpticsAPM.logger
16
+ # end
17
+ #
18
+ # # Error message
19
+ # def error(msg, exception = nil)
20
+ # AppOpticsAPM.logger.error(msg) if AppOpticsAPM.logger
21
+ # end
22
+ #
23
+ # # Warn message
24
+ # def warn(msg, exception = nil)
25
+ # AppOpticsAPM.logger.warn(msg) if AppOpticsAPM.logger
26
+ # end
27
+ #
28
+ # # Info message
29
+ # def info(msg, exception = nil)
30
+ # AppOpticsAPM.logger.info(msg) if AppOpticsAPM.logger
31
+ # end
32
+ #
33
+ # # Debug message
34
+ # def debug(msg, exception = nil)
35
+ # AppOpticsAPM.logger.debug(msg) if AppOpticsAPM.logger
36
+ # end
37
+ #
38
+ # end
38
39
  end
39
40
 
41
+ # Using the currently defined Logger, e.g. the Rails logger
40
42
  AppOpticsAPM.logger = Logger.new(STDERR)
41
43
  # set log level to INFO to be consistent with the c-lib, DEBUG would be default
42
44
  AppOpticsAPM.logger.level = Logger::INFO
@@ -187,6 +187,7 @@ module AppOpticsAPM
187
187
 
188
188
  # AppOpticsAPM::Event.startTrace creates an Event without an Edge
189
189
  exit_evt = AppOpticsAPM::Event.startTrace(AppOpticsAPM::Context.get)
190
+
190
191
  result = begin
191
192
  AppOpticsAPM::API.send_metrics(span, opts) do
192
193
  target['X-Trace'] = AppOpticsAPM::EventUtil.metadataString(exit_evt)
@@ -0,0 +1,66 @@
1
+ # Copyright (c) 2018 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module AppOpticsAPM
5
+ ##
6
+ # This module sends the duration of the call and
7
+ # sets the transaction_name
8
+ #
9
+ module TransactionMetrics
10
+ class << self
11
+
12
+ ##
13
+ # sends the duration of the call and
14
+ # sets the transaction_name
15
+ def metrics(env, settings)
16
+ if settings.do_metrics
17
+ req = ::Rack::Request.new(env)
18
+ url = req.url # saving it here because rails3.2 overrides it when there is a 500 error
19
+ start = Time.now
20
+
21
+ begin
22
+ status, headers, response = yield
23
+
24
+ AppOpticsAPM.transaction_name = send_metrics(env, req, url, start, status)
25
+ rescue
26
+ AppOpticsAPM.transaction_name = send_metrics(env, req, url, start, status || '500')
27
+ raise
28
+ end
29
+ else
30
+ status, headers, response = yield
31
+ AppOpticsAPM.transaction_name = "#{domain(req)}#{transaction_name(env)}" if settings.do_sample
32
+ end
33
+
34
+ [status, headers, response]
35
+ end
36
+
37
+ private
38
+
39
+ def send_metrics(env, req, url, start, status)
40
+ name = transaction_name(env)
41
+
42
+ status = status.to_i
43
+ error = status.between?(500,599) ? 1 : 0
44
+ duration =(1000 * 1000 * (Time.now - start)).round(0)
45
+ method = req.request_method
46
+ # AppOpticsAPM.logger.warn "%%% Sending metrics: #{name}, #{url}, #{status} %%%"
47
+ AppOpticsAPM::Span.createHttpSpan(name, url, domain(req), duration, status, method, error) || ''
48
+ end
49
+
50
+ def domain(req)
51
+ if AppOpticsAPM::Config['transaction_name']['prepend_domain']
52
+ [80, 443].include?(req.port) ? req.host : "#{req.host}:#{req.port}"
53
+ end
54
+ end
55
+
56
+ def transaction_name(env)
57
+ return AppOpticsAPM.transaction_name if AppOpticsAPM.transaction_name
58
+
59
+ if env['appoptics_apm.controller'] && env['appoptics_apm.action']
60
+ [env['appoptics_apm.controller'], env['appoptics_apm.action']].join('.')
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,186 @@
1
+ # Copyright (c) 2018 SolarWinds, LLC.
2
+ # All rights reserved.
3
+ #
4
+
5
+ AO_TRACING_ENABLED = 1
6
+ AO_TRACING_DISABLED = 0
7
+ AO_TRACING_UNSET = -1
8
+
9
+ AO_TRACING_DECISIONS_TRACING_DISABLED = -2
10
+ AO_TRACING_DECISIONS_XTRACE_NOT_SAMPLED = -1
11
+ AO_TRACING_DECISIONS_OK = 0
12
+ AO_TRACING_DECISIONS_NULL_OUT = 1
13
+ AO_TRACING_DECISIONS_NO_CONFIG = 2
14
+ AO_TRACING_DECISIONS_REPORTER_NOT_READY = 3
15
+ AO_TRACING_DECISIONS_NO_VALID_SETTINGS = 4
16
+ AO_TRACING_DECISIONS_QUEUE_FULL = 5
17
+
18
+ module AppOpticsAPM
19
+ ##
20
+ # This module helps with setting up the filters and applying them
21
+ #
22
+ class TransactionSettings
23
+
24
+ attr_accessor :do_metrics, :do_sample
25
+ attr_reader :do_propagate, :rate, :source
26
+
27
+ def initialize(url = nil, xtrace = '')
28
+ @do_metrics = false
29
+ @do_sample = false
30
+ @do_propagate = true
31
+ tracing_mode = AO_TRACING_ENABLED
32
+
33
+ if AppOpticsAPM::Context.isValid
34
+ @do_sample = AppOpticsAPM.tracing?
35
+ return
36
+ end
37
+
38
+ if url && asset?(url)
39
+ @do_propagate = false
40
+ return
41
+ end
42
+
43
+ if AppOpticsAPM.tracing_disabled? && !tracing_enabled?(url) ||
44
+ tracing_disabled?(url)
45
+ tracing_mode = AO_TRACING_DISABLED
46
+ end
47
+
48
+ args = [xtrace || '']
49
+ args << tracing_mode
50
+ args << AppOpticsAPM::Config[:sample_rate] if AppOpticsAPM::Config[:sample_rate]&. >= 0
51
+
52
+ metrics, sample, @rate, @source, return_code = AppOpticsAPM::Context.getDecisions(*args)
53
+
54
+ puts "return_code class: #{return_code.class}" unless return_code.is_a? Integer
55
+ if return_code > AO_TRACING_DECISIONS_OK
56
+ AppOpticsAPM.logger.warn "[appoptics-apm/sample] Problem getting the sampling decisions, code: #{return_code}"
57
+ end
58
+
59
+ @do_metrics = metrics > 0
60
+ @do_sample = sample > 0
61
+ end
62
+
63
+ def to_s
64
+ "do_propagate: #{do_propagate}, do_sample: #{do_sample}, do_metrics: #{do_metrics} rate: #{rate}, source: #{source}"
65
+ end
66
+
67
+ private
68
+ ##
69
+ # tracing_enabled?
70
+ #
71
+ # Given a path, this method determines whether it matches any of the
72
+ # regexps to exclude it from metrics and traces
73
+ #
74
+ def tracing_enabled?(url)
75
+ return false unless AppOpticsAPM::Config[:url_enabled_regexps].is_a? Array
76
+ # once we only support Ruby >= 2.4.0 use `match?` instead of `=~`
77
+ return AppOpticsAPM::Config[:url_enabled_regexps].any? { |regex| regex =~ url }
78
+ rescue => e
79
+ AppOpticsAPM.logger.warn "[AppOpticsAPM/filter] Could not apply :enabled filter to path. #{e.inspect}"
80
+ true
81
+ end
82
+
83
+ ##
84
+ # tracing_disabled?
85
+ #
86
+ # Given a path, this method determines whether it matches any of the
87
+ # regexps to exclude it from metrics and traces
88
+ #
89
+ def tracing_disabled?(url)
90
+ return false unless AppOpticsAPM::Config[:url_disabled_regexps].is_a? Array
91
+ # once we only support Ruby >= 2.4.0 use `match?` instead of `=~`
92
+ return AppOpticsAPM::Config[:url_disabled_regexps].any? { |regex| regex =~ url }
93
+ rescue => e
94
+ AppOpticsAPM.logger.warn "[AppOpticsAPM/filter] Could not apply :disabled filter to path. #{e.inspect}"
95
+ false
96
+ end
97
+
98
+ ##
99
+ # asset?
100
+ #
101
+ # Given a path, this method determines whether it is a static asset
102
+ #
103
+ def asset?(path)
104
+ return false unless AppOpticsAPM::Config[:dnt_compiled]
105
+ # once we only support Ruby >= 2.4.0 use `match?` instead of `=~`
106
+ return AppOpticsAPM::Config[:dnt_compiled] =~ path
107
+ rescue => e
108
+ AppOpticsAPM.logger.warn "[AppOpticsAPM/filter] Could not apply do-not-trace filter to path. #{e.inspect}"
109
+ false
110
+ end
111
+
112
+ public
113
+
114
+ class << self
115
+
116
+ def asset?(path)
117
+ return false unless AppOpticsAPM::Config[:dnt_compiled]
118
+ # once we only support Ruby >= 2.4.0 use `match?` instead of `=~`
119
+ return AppOpticsAPM::Config[:dnt_compiled] =~ path
120
+ rescue => e
121
+ AppOpticsAPM.logger.warn "[AppOpticsAPM/filter] Could not apply do-not-trace filter to path. #{e.inspect}"
122
+ false
123
+ end
124
+
125
+
126
+ def compile_url_settings(settings)
127
+ if !settings.is_a?(Array) || settings.empty?
128
+ reset_url_regexps
129
+ return
130
+ end
131
+
132
+ # `tracing: disabled` is the default
133
+ disabled = settings.select { |v| !v.has_key?(:tracing) || v[:tracing] == :disabled }
134
+ enabled = settings.select { |v| v[:tracing] == :enabled }
135
+
136
+ AppOpticsAPM::Config[:url_enabled_regexps] = compile_regexp(enabled)
137
+ AppOpticsAPM::Config[:url_disabled_regexps] = compile_regexp(disabled)
138
+ end
139
+
140
+ def compile_regexp(settings)
141
+ regexp_regexp = compile_url_settings_regexp(settings)
142
+ extensions_regexp = compile_url_settings_extensions(settings)
143
+
144
+ regexps = [regexp_regexp, extensions_regexp].flatten.compact
145
+
146
+ regexps.empty? ? nil : regexps
147
+ end
148
+
149
+ def compile_url_settings_regexp(value)
150
+ regexps = value.select do |v|
151
+ v.key?(:regexp) &&
152
+ !(v[:regexp].is_a?(String) && v[:regexp].empty?) &&
153
+ !(v[:regexp].is_a?(Regexp) && v[:regexp].inspect == '//')
154
+ end
155
+
156
+ regexps.map! do |v|
157
+ begin
158
+ v[:regexp].is_a?(String) ? Regexp.new(v[:regexp], v[:opts]) : Regexp.new(v[:regexp])
159
+ rescue
160
+ AppOpticsAPM.logger.warn "[appoptics_apm/config] Problem compiling transaction_settings item #{v}, will ignore."
161
+ nil
162
+ end
163
+ end
164
+ regexps.keep_if { |v| !v.nil?}
165
+ regexps.empty? ? nil : regexps
166
+ end
167
+
168
+ def compile_url_settings_extensions(value)
169
+ extensions = value.select do |v|
170
+ v.key?(:extensions) &&
171
+ v[:extensions].is_a?(Array) &&
172
+ !v[:extensions].empty?
173
+ end
174
+ extensions = extensions.map { |v| v[:extensions] }.flatten
175
+ extensions.keep_if { |v| v.is_a?(String)}
176
+
177
+ extensions.empty? ? nil : Regexp.new("(#{Regexp.union(extensions).source})(\\?.+){0,1}$")
178
+ end
179
+
180
+ def reset_url_regexps
181
+ AppOpticsAPM::Config[:url_enabled_regexps] = nil
182
+ AppOpticsAPM::Config[:url_disabled_regexps] = nil
183
+ end
184
+ end
185
+ end
186
+ end