newrelic_rpm 7.0.0 → 7.2.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.
@@ -80,6 +80,7 @@ module NewRelic
80
80
  was_finished = finished_configuring?
81
81
 
82
82
  invoke_callbacks(:add, source)
83
+
83
84
  case source
84
85
  when SecurityPolicySource then @security_policy_source = source
85
86
  when HighSecuritySource then @high_security_source = source
@@ -159,7 +160,6 @@ module NewRelic
159
160
  def invoke_callbacks(direction, source)
160
161
  return unless source
161
162
  source.keys.each do |key|
162
-
163
163
  if @cache[key] != source[key]
164
164
  @callbacks[key].each do |proc|
165
165
  if direction == :add
@@ -48,8 +48,10 @@ module NewRelic
48
48
  environment_report
49
49
  end
50
50
 
51
- def environment_metadata
52
- ENV.select {|k, v| k =~ /^NEW_RELIC_METADATA_/}
51
+ def environment_metadata
52
+ env_copy = {}
53
+ ENV.keys.each {|k| env_copy[k] = ENV[k] if k =~ /^NEW_RELIC_METADATA_/}
54
+ env_copy
53
55
  end
54
56
 
55
57
  def local_host
@@ -22,23 +22,18 @@ module NewRelic
22
22
  @error_trace_aggregator = ErrorTraceAggregator.new(MAX_ERROR_QUEUE_LENGTH)
23
23
  @error_event_aggregator = ErrorEventAggregator.new events
24
24
 
25
- # lookup of exception class names to ignore. Hash for fast access
26
- @ignore = {}
27
-
28
- initialize_ignored_errors(Agent.config[:'error_collector.ignore_errors'])
29
-
30
- Agent.config.register_callback(:'error_collector.ignore_errors') do |ignore_errors|
31
- initialize_ignored_errors(ignore_errors)
25
+ @error_filter = NewRelic::Agent::ErrorFilter.new
26
+
27
+ %w(
28
+ ignore_errors ignore_classes ignore_messages ignore_status_codes
29
+ expected_classes expected_messages expected_status_codes
30
+ ).each do |w|
31
+ Agent.config.register_callback(:"error_collector.#{w}") do |value|
32
+ @error_filter.load_from_config(w, value)
33
+ end
32
34
  end
33
35
  end
34
36
 
35
- def initialize_ignored_errors(ignore_errors)
36
- @ignore.clear
37
- ignore_errors = ignore_errors.split(",") if ignore_errors.is_a? String
38
- ignore_errors.each { |error| error.strip! }
39
- ignore(ignore_errors)
40
- end
41
-
42
37
  def enabled?
43
38
  error_trace_aggregator.enabled? || error_event_aggregator.enabled?
44
39
  end
@@ -76,30 +71,39 @@ module NewRelic
76
71
  defined?(@ignore_filter) ? @ignore_filter : nil
77
72
  end
78
73
 
79
- # errors is an array of Exception Class Names
80
- #
81
74
  def ignore(errors)
82
- errors.each do |error|
83
- @ignore[error] = true
84
- ::NewRelic::Agent.logger.debug("Ignoring errors of type '#{error}'")
85
- end
75
+ @error_filter.ignore(errors)
76
+ end
77
+
78
+ def ignore?(ex, status_code = nil)
79
+ @error_filter.ignore?(ex, status_code)
80
+ end
81
+
82
+ def expect(errors)
83
+ @error_filter.expect(errors)
84
+ end
85
+
86
+ def expected?(ex, status_code = nil)
87
+ @error_filter.expected?(ex, status_code)
88
+ end
89
+
90
+ def load_error_filters
91
+ @error_filter.load_all
92
+ end
93
+
94
+ def reset_error_filters
95
+ @error_filter.reset
86
96
  end
87
97
 
88
98
  # Checks the provided error against the error filter, if there
89
99
  # is an error filter
90
- def filtered_by_error_filter?(error)
100
+ def ignored_by_filter_proc?(error)
91
101
  respond_to?(:ignore_filter_proc) && !ignore_filter_proc(error)
92
102
  end
93
103
 
94
- # Checks the array of error names and the error filter against
95
- # the provided error
96
- def filtered_error?(error)
97
- @ignore[error.class.name] || filtered_by_error_filter?(error)
98
- end
99
-
100
104
  # an error is ignored if it is nil or if it is filtered
101
- def error_is_ignored?(error)
102
- error && filtered_error?(error)
105
+ def error_is_ignored?(error, status_code = nil)
106
+ error && (@error_filter.ignore?(error, status_code) || ignored_by_filter_proc?(error))
103
107
  rescue => e
104
108
  NewRelic::Agent.logger.error("Error '#{error}' will NOT be ignored. Exception '#{e}' while determining whether to ignore or not.", e)
105
109
  false
@@ -174,11 +178,18 @@ module NewRelic
174
178
  end
175
179
  end
176
180
 
177
- def skip_notice_error?(exception)
181
+ def increment_expected_error_count!(state, exception)
182
+ stats_engine = NewRelic::Agent.agent.stats_engine
183
+ stats_engine.record_unscoped_metrics(state, ['ErrorsExpected/all']) do |stats|
184
+ stats.increment_count
185
+ end
186
+ end
187
+
188
+ def skip_notice_error?(exception, status_code = nil)
178
189
  disabled? ||
179
- error_is_ignored?(exception) ||
180
190
  exception.nil? ||
181
- exception_tagged_with?(EXCEPTION_TAG_IVAR, exception)
191
+ exception_tagged_with?(EXCEPTION_TAG_IVAR, exception) ||
192
+ error_is_ignored?(exception, status_code)
182
193
  end
183
194
 
184
195
  # calls a method on an object, if it responds to it - used for
@@ -210,13 +221,17 @@ module NewRelic
210
221
 
211
222
  # See NewRelic::Agent.notice_error for options and commentary
212
223
  def notice_error(exception, options={}, span_id=nil)
213
- return if skip_notice_error?(exception)
224
+ state = ::NewRelic::Agent::Tracer.state
225
+ transaction = state.current_transaction
226
+ status_code = transaction ? transaction.http_response_code : nil
214
227
 
215
- tag_exception(exception)
228
+ return if skip_notice_error?(exception, status_code)
216
229
 
217
- state = ::NewRelic::Agent::Tracer.state
230
+ tag_exception(exception)
218
231
 
219
- unless options[:expected]
232
+ if options[:expected] || @error_filter.expected?(exception, status_code)
233
+ increment_expected_error_count!(state, exception)
234
+ else
220
235
  increment_error_count!(state, exception, options)
221
236
  end
222
237
 
@@ -258,7 +273,7 @@ module NewRelic
258
273
  noticed_error.line_number = sense_method(exception, :line_number)
259
274
  noticed_error.stack_trace = truncate_trace(extract_stack_trace(exception))
260
275
 
261
- noticed_error.expected = !! options.delete(:expected)
276
+ noticed_error.expected = !!options.delete(:expected) || expected?(exception)
262
277
 
263
278
  noticed_error.attributes_from_notice_error = options.delete(:custom_params) || {}
264
279
 
@@ -0,0 +1,167 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
+
5
+ module NewRelic
6
+ module Agent
7
+
8
+ # Handles loading of ignored and expected errors from the agent configuration, and
9
+ # determining at runtime whether an exception is ignored or expected.
10
+ class ErrorFilter
11
+
12
+ def initialize
13
+ reset
14
+ end
15
+
16
+ def reset
17
+ @ignore_classes, @expected_classes = [], []
18
+ @ignore_messages, @expected_messages = {}, {}
19
+ @ignore_status_codes, @expected_status_codes = [], []
20
+ end
21
+
22
+ def load_all
23
+ %i(
24
+ ignore_errors ignore_classes ignore_messages ignore_status_codes
25
+ expected_classes expected_messages expected_status_codes
26
+ ).each { |setting| load_from_config(setting) }
27
+ end
28
+
29
+ def load_from_config(setting, value = nil)
30
+ errors = nil
31
+ new_value = value || fetch_agent_config(setting.to_sym)
32
+ return if new_value.nil? || new_value.empty?
33
+
34
+ case setting.to_sym
35
+ when :ignore_errors, :ignore_classes
36
+ new_value = new_value.split(',').map!(&:strip) if new_value.is_a?(String)
37
+ errors = @ignore_classes = new_value
38
+ when :ignore_messages
39
+ errors = @ignore_messages = new_value || {}
40
+ when :ignore_status_codes
41
+ errors = @ignore_status_codes = parse_status_codes(new_value) || []
42
+ when :expected_classes
43
+ errors = @expected_classes = new_value || []
44
+ when :expected_messages
45
+ errors = @expected_messages = new_value || {}
46
+ when :expected_status_codes
47
+ errors = @expected_status_codes = parse_status_codes(new_value) || []
48
+ end
49
+ log_filter(setting, errors) if errors
50
+ end
51
+
52
+ def ignore?(ex, status_code = nil)
53
+ @ignore_classes.include?(ex.class.name) ||
54
+ (@ignore_messages.keys.include?(ex.class.name) &&
55
+ @ignore_messages[ex.class.name].any? { |m| ex.message.include?(m) }) ||
56
+ @ignore_status_codes.include?(status_code.to_i)
57
+ end
58
+
59
+ def expected?(ex, status_code = nil)
60
+ @expected_classes.include?(ex.class.name) ||
61
+ (@expected_messages.keys.include?(ex.class.name) &&
62
+ @expected_messages[ex.class.name].any? { |m| ex.message.include?(m) }) ||
63
+ @expected_status_codes.include?(status_code.to_i)
64
+ end
65
+
66
+ def fetch_agent_config(cfg)
67
+ NewRelic::Agent.config[:"error_collector.#{cfg}"]
68
+ end
69
+
70
+ # A generic method for adding ignore filters manually. This is kept for compatibility
71
+ # with the previous ErrorCollector#ignore method, and adds some flexibility for adding
72
+ # different ignore/expected error types by examining each argument.
73
+ def ignore(*args)
74
+ args.each do |errors|
75
+ case errors
76
+ when Array
77
+ errors.each { |e| ignore(e) }
78
+ when Integer
79
+ @ignore_status_codes << errors
80
+ when Hash
81
+ @ignore_messages.update(errors)
82
+ log_filter(:ignore_messages, errors)
83
+ when String
84
+ if errors.match(/^[\d\,\-]+$/)
85
+ @ignore_status_codes |= parse_status_codes(errors)
86
+ log_filter(:ignore_status_codes, errors)
87
+ else
88
+ new_ignore_classes = errors.split(',').map!(&:strip)
89
+ @ignore_classes |= new_ignore_classes
90
+ log_filter(:ignore_classes, new_ignore_classes)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # See #ignore above.
97
+ def expect(*args)
98
+ args.each do |errors|
99
+ case errors
100
+ when Array
101
+ errors.each { |e| expect(e) }
102
+ when Integer
103
+ @expected_status_codes << errors
104
+ when Hash
105
+ @expected_messages.update(errors)
106
+ log_filter(:expected_messages, errors)
107
+ when String
108
+ if errors.match(/^[\d\,\-]+$/)
109
+ @expected_status_codes |= parse_status_codes(errors)
110
+ log_filter(:expected_status_codes, errors)
111
+ else
112
+ new_expected_classes = errors.split(',').map!(&:strip)
113
+ @expected_classes |= new_expected_classes
114
+ log_filter(:expected_classes, new_expected_classes)
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def log_filter(setting, errors)
123
+ case setting
124
+ when :ignore_errors, :ignore_classes
125
+ errors.each do |error|
126
+ ::NewRelic::Agent.logger.debug("Ignoring errors of type '#{error}'")
127
+ end
128
+ when :ignore_messages
129
+ errors.each do |error,messages|
130
+ ::NewRelic::Agent.logger.debug("Ignoring errors of type '#{error}' with messages: #{messages.join(',')}")
131
+ end
132
+ when :ignore_status_codes
133
+ ::NewRelic::Agent.logger.debug("Ignoring errors associated with status codes: #{errors}")
134
+ when :expected_classes
135
+ errors.each do |error|
136
+ ::NewRelic::Agent.logger.debug("Expecting errors of type '#{error}'")
137
+ end
138
+ when :expected_messages
139
+ errors.each do |error,messages|
140
+ ::NewRelic::Agent.logger.debug("Expecting errors of type '#{error}' with messages: #{messages.join(',')}")
141
+ end
142
+ when :expected_status_codes
143
+ ::NewRelic::Agent.logger.debug("Expecting errors associated with status codes: #{errors}")
144
+ end
145
+ end
146
+
147
+ def parse_status_codes(codes)
148
+ code_list = codes.is_a?(String) ? codes.split(',') : codes
149
+ result = []
150
+ code_list.each do |code|
151
+ result << code && next if code.is_a?(Integer)
152
+ m = code.match(/(\d{3})(-\d{3})?/)
153
+ if m.nil? || m[1].nil?
154
+ ::NewRelic::Agent.logger.warn("Invalid HTTP status code: '#{code}'; ignoring config")
155
+ next
156
+ end
157
+ if m[2]
158
+ result += (m[1]..m[2].tr('-', '')).to_a.map(&:to_i)
159
+ else
160
+ result << m[1].to_i
161
+ end
162
+ end
163
+ result.uniq
164
+ end
165
+ end
166
+ end
167
+ end
@@ -84,8 +84,6 @@ module NewRelic
84
84
  end
85
85
 
86
86
  def active_record_config(payload)
87
- return unless payload[:connection_id]
88
-
89
87
  # handle if the notification payload provides the AR connection
90
88
  # available in Rails 6+ & our ActiveRecordNotifications#log extension
91
89
  if payload[:connection]
@@ -93,8 +91,8 @@ module NewRelic
93
91
  return connection_config if connection_config
94
92
  end
95
93
 
94
+ return unless connection_id = payload[:connection_id]
96
95
  connection = nil
97
- connection_id = payload[:connection_id]
98
96
 
99
97
  ::ActiveRecord::Base.connection_handler.connection_pool_list.each do |handler|
100
98
  connection = handler.connections.detect do |conn|
@@ -179,6 +179,7 @@ module NewRelic
179
179
  #{without_method_name}(*args, &block)
180
180
  end
181
181
  end
182
+ ruby2_keywords(:#{with_method_name}) if respond_to?(:ruby2_keywords, true)
182
183
  EOC
183
184
 
184
185
  visibility = NewRelic::Helper.instance_method_visibility self, method
@@ -17,8 +17,13 @@ DependencyDetection.defer do
17
17
  require 'new_relic/agent/http_clients/net_http_wrappers'
18
18
  end
19
19
 
20
+ # Airbrake uses method chaining on Net::HTTP in versions < 10.0.2 (10.0.2 updated to prepend for Net:HTTP)
20
21
  conflicts_with_prepend do
21
- defined?(::Airbrake)
22
+ defined?(::Airbrake) && defined?(::Airbrake::AIRBRAKE_VERSION) && ::Gem::Version.create(::Airbrake::AIRBRAKE_VERSION) < ::Gem::Version.create('10.0.2')
23
+ end
24
+
25
+ conflicts_with_prepend do
26
+ defined?(::ScoutApm)
22
27
  end
23
28
 
24
29
  conflicts_with_prepend do
@@ -31,7 +31,8 @@ module NewRelic::Agent::Instrumentation
31
31
  def use_with_newrelic(middleware_class, *args, &block)
32
32
  use_with_tracing(middleware_class) { |wrapped_class| use_without_newrelic(wrapped_class, *args, &block) }
33
33
  end
34
-
34
+ ruby2_keywords(:use_with_newrelic) if respond_to?(:ruby2_keywords, true)
35
+
35
36
  alias_method :use_without_newrelic, :use
36
37
  alias_method :use, :use_with_newrelic
37
38
  end
@@ -54,4 +55,4 @@ module NewRelic::Agent::Instrumentation
54
55
  end
55
56
  end
56
57
  end
57
- end
58
+ end
@@ -31,6 +31,7 @@ module NewRelic::Agent::Instrumentation
31
31
  def use(middleware_class, *args, &blk)
32
32
  use_with_tracing(middleware_class) { |wrapped_class| super(wrapped_class, *args, &blk) }
33
33
  end
34
+ ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
34
35
  end
35
36
  end
36
- end
37
+ end
@@ -18,6 +18,10 @@ DependencyDetection.defer do
18
18
  defined?(::Redis) && defined?(::Redis::VERSION)
19
19
  end
20
20
 
21
+ conflicts_with_prepend do
22
+ defined?(::PrometheusExporter)
23
+ end
24
+
21
25
  depends_on do
22
26
  NewRelic::Agent::Datastores::Redis.is_supported_version? &&
23
27
  NewRelic::Agent::Datastores::Redis.safe_from_third_party_gem?
@@ -13,6 +13,11 @@ DependencyDetection.defer do
13
13
  defined?(::Resque::Job) && !NewRelic::Agent.config[:disable_resque]
14
14
  end
15
15
 
16
+ # Airbrake uses method chaining on Resque::Job on versions < 11.0.3
17
+ conflicts_with_prepend do
18
+ defined?(::Airbrake) && defined?(::Airbrake::AIRBRAKE_VERSION) && ::Gem::Version.create(::Airbrake::AIRBRAKE_VERSION) < ::Gem::Version.create('11.0.3')
19
+ end
20
+
16
21
  executes do
17
22
  ::NewRelic::Agent.logger.info 'Installing Resque instrumentation'
18
23
  end
@@ -32,7 +37,7 @@ DependencyDetection.defer do
32
37
  chain_instrument NewRelic::Agent::Instrumentation::Resque::Chain
33
38
  end
34
39
 
35
- if NewRelic::LanguageSupport.can_fork?
40
+ if NewRelic::Agent::Instrumentation::Resque::Helper.resque_fork_per_job?
36
41
  ::Resque.before_first_fork do
37
42
  NewRelic::Agent.manual_start(:dispatcher => :resque,
38
43
  :sync_startup => true,
@@ -40,9 +45,7 @@ DependencyDetection.defer do
40
45
  end
41
46
 
42
47
  ::Resque.before_fork do |job|
43
- if ENV['FORK_PER_JOB'] != 'false'
44
- NewRelic::Agent.register_report_channel(job.object_id)
45
- end
48
+ NewRelic::Agent.register_report_channel(job.object_id)
46
49
  end
47
50
 
48
51
  ::Resque.after_fork do |job|
@@ -51,6 +54,12 @@ DependencyDetection.defer do
51
54
  NewRelic::Agent.after_fork(:report_to_channel => job.object_id,
52
55
  :report_instance_busy => false)
53
56
  end
57
+ else
58
+ ::Resque.before_first_fork do
59
+ NewRelic::Agent.manual_start(:dispatcher => :resque,
60
+ :sync_startup => true,
61
+ :start_channel_listener => false)
62
+ end
54
63
  end
55
64
  end
56
65
  end