newrelic_rpm 7.0.0 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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